velconnect-svelte with easy subscribing
parent
a0ce622c5d
commit
ae3b5b3bd0
|
|
@ -96,15 +96,25 @@
|
||||||
<h3>Settings</h3>
|
<h3>Settings</h3>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
User Name
|
Device Name
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Enter username..."
|
placeholder="Enter friendly device name..."
|
||||||
bind:value={$deviceFields.friendly_name}
|
bind:value={$deviceFields.friendly_name}
|
||||||
on:input={delayedSend}
|
on:input={delayedSend}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Nickname
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter nickname..."
|
||||||
|
bind:value={$deviceData.data.nickname}
|
||||||
|
on:input={delayedSend}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Avatar URL
|
Avatar URL
|
||||||
<a href="https://demo.readyplayer.me" target="blank">
|
<a href="https://demo.readyplayer.me" target="blank">
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ namespace VELConnect
|
||||||
[CanBeNull] public string pairing_code;
|
[CanBeNull] public string pairing_code;
|
||||||
[CanBeNull] public string last_online;
|
[CanBeNull] public string last_online;
|
||||||
[CanBeNull] public DeviceExpand expand;
|
[CanBeNull] public DeviceExpand expand;
|
||||||
public DataBlock deviceData => expand.data;
|
public DataBlock deviceData => expand?.data;
|
||||||
|
|
||||||
public class DeviceExpand
|
public class DeviceExpand
|
||||||
{
|
{
|
||||||
|
|
@ -175,7 +175,7 @@ namespace VELConnect
|
||||||
// allows running multiple builds on the same computer
|
// allows running multiple builds on the same computer
|
||||||
// return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath);
|
// return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath);
|
||||||
sb.Append(Application.dataPath);
|
sb.Append(Application.dataPath);
|
||||||
sb.Append("EDITOR");
|
sb.Append("EDITOR2");
|
||||||
#endif
|
#endif
|
||||||
string id = Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())));
|
string id = Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())));
|
||||||
deviceId = id[..15];
|
deviceId = id[..15];
|
||||||
|
|
@ -349,6 +349,32 @@ namespace VELConnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// on the initial state, also activate callbacks for null values
|
||||||
|
if (isInitialState)
|
||||||
|
{
|
||||||
|
foreach ((string deviceDataKey, List<CallbackListener> callbackList) in deviceDataCallbacks)
|
||||||
|
{
|
||||||
|
if (!state.device.deviceData.data.ContainsKey(deviceDataKey))
|
||||||
|
{
|
||||||
|
// send the callbacks
|
||||||
|
callbackList.ForEach(e =>
|
||||||
|
{
|
||||||
|
if (e.sendInitialState)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
e.callback(null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,6 +420,32 @@ namespace VELConnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// on the initial state, also activate callbacks for null values
|
||||||
|
if (isInitialState)
|
||||||
|
{
|
||||||
|
foreach ((string key, List<CallbackListener> callbackList) in roomDataCallbacks)
|
||||||
|
{
|
||||||
|
if (!state.room.data.ContainsKey(key))
|
||||||
|
{
|
||||||
|
// send the callbacks
|
||||||
|
callbackList.ForEach(e =>
|
||||||
|
{
|
||||||
|
if (e.sendInitialState)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
e.callback(null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastState = state;
|
lastState = state;
|
||||||
|
|
@ -453,6 +505,7 @@ namespace VELConnect
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a change listener callback to a particular field name within the Device data JSON.
|
/// Adds a change listener callback to a particular field name within the Device data JSON.
|
||||||
|
/// If the initial state doesn't contain this key, this sends back null
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void AddDeviceDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
|
public static void AddDeviceDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
|
||||||
bool sendInitialState = false)
|
bool sendInitialState = false)
|
||||||
|
|
@ -469,11 +522,10 @@ namespace VELConnect
|
||||||
sendInitialState = sendInitialState
|
sendInitialState = sendInitialState
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sendInitialState)
|
// if we have already received data, and we should send right away
|
||||||
|
if (sendInitialState && instance.state != null)
|
||||||
{
|
{
|
||||||
string val = GetDeviceData(key);
|
string val = GetDeviceData(key);
|
||||||
if (val != null)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
callback(val);
|
callback(val);
|
||||||
|
|
@ -484,10 +536,10 @@ namespace VELConnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a change listener callback to a particular field name within the Room data JSON.
|
/// Adds a change listener callback to a particular field name within the Room data JSON.
|
||||||
|
/// If the initial state doesn't contain this key, this sends back null
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void AddRoomDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
|
public static void AddRoomDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
|
||||||
bool sendInitialState = false)
|
bool sendInitialState = false)
|
||||||
|
|
@ -504,11 +556,10 @@ namespace VELConnect
|
||||||
sendInitialState = sendInitialState
|
sendInitialState = sendInitialState
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sendInitialState)
|
// if we have already received data, and we should send right away
|
||||||
|
if (sendInitialState && instance.state != null)
|
||||||
{
|
{
|
||||||
string val = GetRoomData(key);
|
string val = GetRoomData(key);
|
||||||
if (val != null)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
callback(val);
|
callback(val);
|
||||||
|
|
@ -519,16 +570,15 @@ namespace VELConnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetDeviceData(string key, string defaultValue = null)
|
||||||
|
{
|
||||||
|
return instance != null ? instance.lastState?.device?.TryGetData(key) : defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDeviceData(string key)
|
public static string GetRoomData(string key, string defaultValue = null)
|
||||||
{
|
{
|
||||||
return instance != null ? instance.lastState?.device?.TryGetData(key) : null;
|
return instance != null ? instance.lastState?.room?.TryGetData(key) : defaultValue;
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetRoomData(string key)
|
|
||||||
{
|
|
||||||
return instance != null ? instance.lastState?.room?.TryGetData(key) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -769,6 +819,7 @@ namespace VELConnect
|
||||||
byte[] bodyRaw = Encoding.UTF8.GetBytes(postData);
|
byte[] bodyRaw = Encoding.UTF8.GetBytes(postData);
|
||||||
UploadHandlerRaw uploadHandler = new UploadHandlerRaw(bodyRaw);
|
UploadHandlerRaw uploadHandler = new UploadHandlerRaw(bodyRaw);
|
||||||
webRequest.uploadHandler = uploadHandler;
|
webRequest.uploadHandler = uploadHandler;
|
||||||
|
webRequest.downloadHandler = new DownloadHandlerBuffer();
|
||||||
webRequest.SetRequestHeader("Content-Type", "application/json");
|
webRequest.SetRequestHeader("Content-Type", "application/json");
|
||||||
if (headers != null)
|
if (headers != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@velaboratory/velconnect-svelte",
|
"name": "@velaboratory/velconnect-svelte",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "Use VEL-Connect with a Svelte dashboard",
|
"description": "Use VEL-Connect with a Svelte dashboard",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@ import { writable } from "svelte/store";
|
||||||
import type { Record } from "pocketbase";
|
import type { Record } from "pocketbase";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
|
let debugLog = false;
|
||||||
|
|
||||||
|
export function setDebugLog(val: boolean) {
|
||||||
|
debugLog = val;
|
||||||
|
}
|
||||||
|
|
||||||
export const pb = new PocketBase();
|
export const pb = new PocketBase();
|
||||||
|
|
||||||
export const currentUser = writable(pb.authStore.model);
|
export const currentUser = writable(pb.authStore.model);
|
||||||
|
|
@ -27,6 +33,8 @@ export interface Device extends Record {
|
||||||
expand: { data?: DataBlock };
|
expand: { data?: DataBlock };
|
||||||
}
|
}
|
||||||
export interface DataBlock extends Record {
|
export interface DataBlock extends Record {
|
||||||
|
block_id: string;
|
||||||
|
owner: string;
|
||||||
data: { [key: string]: string };
|
data: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,30 +61,45 @@ export async function startListening(baseUrl: string) {
|
||||||
const d = (await pb.collection("Device").getOne(get(currentDeviceId), {
|
const d = (await pb.collection("Device").getOne(get(currentDeviceId), {
|
||||||
expand: "data",
|
expand: "data",
|
||||||
})) as Device;
|
})) as Device;
|
||||||
deviceFields.set(d);
|
|
||||||
deviceData.set(d.expand.data as DataBlock);
|
deviceData.set(d.expand.data as DataBlock);
|
||||||
|
// we don't need expand anymore, since it doesn't work in subscribe()
|
||||||
|
d.expand = {};
|
||||||
|
deviceFields.set(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribeCurrentDevice = currentDeviceId.subscribe(async (val) => {
|
unsubscribeCurrentDevice = currentDeviceId.subscribe(async (val) => {
|
||||||
console.log("current device changed");
|
log("currentDeviceId subscribe change event");
|
||||||
unsubscribeDeviceFields?.();
|
unsubscribeDeviceFields?.();
|
||||||
unsubscribeDeviceData?.();
|
unsubscribeDeviceData?.();
|
||||||
if (val != "") {
|
if (val != "") {
|
||||||
const d = (await pb
|
const d = (await pb
|
||||||
.collection("Device")
|
.collection("Device")
|
||||||
.getOne(get(currentDeviceId), { expand: "data" })) as Device;
|
.getOne(get(currentDeviceId), { expand: "data" })) as Device;
|
||||||
deviceFields.set(d);
|
|
||||||
deviceData.set(d.expand.data as DataBlock);
|
deviceData.set(d.expand.data as DataBlock);
|
||||||
|
// we don't need expand anymore, since it doesn't work in subscribe()
|
||||||
unsubscribeDeviceFields = await pb
|
d.expand = {};
|
||||||
.collection("Device")
|
|
||||||
.subscribe(val, async (data) => {
|
|
||||||
const d = data.record as Device;
|
|
||||||
deviceFields.set(d);
|
deviceFields.set(d);
|
||||||
|
|
||||||
unsubscribeDeviceData = await pb
|
unsubscribeDeviceData = await pb
|
||||||
.collection("DataBlock")
|
.collection("DataBlock")
|
||||||
.subscribe(d.id, async (data) => {
|
.subscribe(d.data, async (data) => {
|
||||||
|
log("deviceData subscribe change event");
|
||||||
|
deviceData.set(data.record as DataBlock);
|
||||||
|
});
|
||||||
|
|
||||||
|
unsubscribeDeviceFields = await pb
|
||||||
|
.collection("Device")
|
||||||
|
.subscribe(val, async (data) => {
|
||||||
|
log("deviceFields subscribe change event");
|
||||||
|
const d = data.record as Device;
|
||||||
|
deviceFields.set(d);
|
||||||
|
|
||||||
|
// if the devie changes, the devicedata could change, so we need to resubscribe
|
||||||
|
unsubscribeDeviceData?.();
|
||||||
|
unsubscribeDeviceData = await pb
|
||||||
|
.collection("DataBlock")
|
||||||
|
.subscribe(d.data, async (data) => {
|
||||||
|
log("deviceData subscribe change event");
|
||||||
deviceData.set(data.record as DataBlock);
|
deviceData.set(data.record as DataBlock);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -92,6 +115,7 @@ export async function startListening(baseUrl: string) {
|
||||||
});
|
});
|
||||||
|
|
||||||
unsubscribeCurrentUser = currentUser.subscribe((user) => {
|
unsubscribeCurrentUser = currentUser.subscribe((user) => {
|
||||||
|
log(`currentUser changed ${user}`);
|
||||||
pairedDevices.set(user?.["devices"] ?? []);
|
pairedDevices.set(user?.["devices"] ?? []);
|
||||||
currentDeviceId.set(get(pairedDevices)[0] ?? "");
|
currentDeviceId.set(get(pairedDevices)[0] ?? "");
|
||||||
});
|
});
|
||||||
|
|
@ -110,16 +134,26 @@ async function getRoomData(device: Device) {
|
||||||
unsubscribeRoomData?.();
|
unsubscribeRoomData?.();
|
||||||
|
|
||||||
// create or just fetch room by name
|
// create or just fetch room by name
|
||||||
const r = (await pb
|
let r: DataBlock | null = null;
|
||||||
|
try {
|
||||||
|
r = (await pb
|
||||||
.collection("DataBlock")
|
.collection("DataBlock")
|
||||||
.getFirstListItem(
|
.getFirstListItem(
|
||||||
`block_id="${device.current_app}_${device.current_room}"`
|
`block_id="${device.current_app}_${device.current_room}"`
|
||||||
)) as DataBlock;
|
)) as DataBlock;
|
||||||
|
} catch (e: any) {
|
||||||
|
r = (await pb.collection("DataBlock").create({
|
||||||
|
block_id: `${device.current_app}_${device.current_room}`,
|
||||||
|
category: "room",
|
||||||
|
data: {},
|
||||||
|
})) as DataBlock;
|
||||||
|
}
|
||||||
roomData.set(r);
|
roomData.set(r);
|
||||||
if (r) {
|
if (r != null) {
|
||||||
unsubscribeRoomData = await pb
|
unsubscribeRoomData = await pb
|
||||||
.collection("DataBlock")
|
.collection("DataBlock")
|
||||||
.subscribe(r.id, (data) => {
|
.subscribe(r.id, (data) => {
|
||||||
|
log("roomData subscribe change event");
|
||||||
roomData.set(data.record as DataBlock);
|
roomData.set(data.record as DataBlock);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -148,9 +182,10 @@ export function send() {
|
||||||
console.log("sending...");
|
console.log("sending...");
|
||||||
sending = true;
|
sending = true;
|
||||||
let promises: Promise<any>[] = [];
|
let promises: Promise<any>[] = [];
|
||||||
const room = get(roomData);
|
|
||||||
const device = get(deviceFields);
|
const device = get(deviceFields);
|
||||||
const data = get(deviceData);
|
const data = get(deviceData);
|
||||||
|
const room = get(roomData);
|
||||||
|
// TODO send changes only
|
||||||
if (device) {
|
if (device) {
|
||||||
promises.push(pb.collection("Device").update(device.id, device));
|
promises.push(pb.collection("Device").update(device.id, device));
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +230,9 @@ export async function pair(pairingCode: string) {
|
||||||
|
|
||||||
// add it to the local data
|
// add it to the local data
|
||||||
currentDeviceId.set(device.id);
|
currentDeviceId.set(device.id);
|
||||||
|
if (!get(pairedDevices).includes(device.id)) {
|
||||||
pairedDevices.set([...get(pairedDevices), device.id]);
|
pairedDevices.set([...get(pairedDevices), device.id]);
|
||||||
|
}
|
||||||
|
|
||||||
// add it to my account if logged in
|
// add it to my account if logged in
|
||||||
const u = get(currentUser);
|
const u = get(currentUser);
|
||||||
|
|
@ -242,26 +279,32 @@ export async function pair(pairingCode: string) {
|
||||||
export async function login(username: string, password: string) {
|
export async function login(username: string, password: string) {
|
||||||
try {
|
try {
|
||||||
await pb.collection("Users").authWithPassword(username, password);
|
await pb.collection("Users").authWithPassword(username, password);
|
||||||
return "";
|
return {};
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
return err as string;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signUp(username: string, password: string) {
|
export async function signUp(username: string, password: string) {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const data = {
|
||||||
email: username,
|
username: username,
|
||||||
password,
|
password,
|
||||||
passwordConfirm: password,
|
passwordConfirm: password,
|
||||||
};
|
};
|
||||||
await pb.collection("Users").create(data);
|
await pb.collection("Users").create(data);
|
||||||
return await login(username, password);
|
return await login(username, password);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
return err as string;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function signOut() {
|
export function signOut() {
|
||||||
pb.authStore.clear();
|
pb.authStore.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function log(msg: string) {
|
||||||
|
if (debugLog) {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,10 +147,12 @@ func main() {
|
||||||
|
|
||||||
// apply to the db
|
// apply to the db
|
||||||
if err := dao.SaveRecord(deviceRecord); err != nil {
|
if err := dao.SaveRecord(deviceRecord); err != nil {
|
||||||
return err
|
log.Fatalln(err)
|
||||||
|
return c.String(500, err.Error())
|
||||||
}
|
}
|
||||||
if err := dao.SaveRecord(deviceDataRecord); err != nil {
|
if err := dao.SaveRecord(deviceDataRecord); err != nil {
|
||||||
return err
|
log.Fatalln(err)
|
||||||
|
return c.String(500, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, deviceRecord)
|
return c.JSON(http.StatusOK, deviceRecord)
|
||||||
|
|
@ -180,11 +182,11 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
apis.EnrichRecord(c, app.Dao(), deviceRecord, "data")
|
apis.EnrichRecord(c, app.Dao(), deviceRecord, "data")
|
||||||
room, roomErr := app.Dao().FindFirstRecordByData("DataBlock", "block_id", deviceRecord.GetString("current_app")+"_"+deviceRecord.GetString("current_room"))
|
room, _ := app.Dao().FindFirstRecordByData("DataBlock", "block_id", deviceRecord.GetString("current_app")+"_"+deviceRecord.GetString("current_room"))
|
||||||
user, _ := app.Dao().FindRecordById("Users", deviceRecord.GetString("owner"))
|
user, _ := app.Dao().FindRecordById("Users", deviceRecord.GetString("owner"))
|
||||||
|
|
||||||
log.Println(deviceRecord.GetString("current_room"))
|
// log.Println(deviceRecord.GetString("current_room"))
|
||||||
log.Println(roomErr)
|
// log.Println(roomErr)
|
||||||
|
|
||||||
output := map[string]interface{}{
|
output := map[string]interface{}{
|
||||||
"device": deviceRecord,
|
"device": deviceRecord,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue