velconnect-svelte with easy subscribing

dev
Anton Franzluebbers 2023-07-15 16:26:25 -04:00
parent a0ce622c5d
commit ae3b5b3bd0
5 changed files with 158 additions and 52 deletions

View File

@ -96,15 +96,25 @@
<h3>Settings</h3>
<label>
User Name
Device Name
<input
type="text"
placeholder="Enter username..."
placeholder="Enter friendly device name..."
bind:value={$deviceFields.friendly_name}
on:input={delayedSend}
/>
</label>
<label>
Nickname
<input
type="text"
placeholder="Enter nickname..."
bind:value={$deviceData.data.nickname}
on:input={delayedSend}
/>
</label>
<label>
Avatar URL
<a href="https://demo.readyplayer.me" target="blank">

View File

@ -50,7 +50,7 @@ namespace VELConnect
[CanBeNull] public string pairing_code;
[CanBeNull] public string last_online;
[CanBeNull] public DeviceExpand expand;
public DataBlock deviceData => expand.data;
public DataBlock deviceData => expand?.data;
public class DeviceExpand
{
@ -175,7 +175,7 @@ namespace VELConnect
// allows running multiple builds on the same computer
// return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath);
sb.Append(Application.dataPath);
sb.Append("EDITOR");
sb.Append("EDITOR2");
#endif
string id = Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())));
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;
@ -453,6 +505,7 @@ namespace VELConnect
/// <summary>
/// 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>
public static void AddDeviceDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
bool sendInitialState = false)
@ -469,11 +522,10 @@ namespace VELConnect
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);
if (val != null)
{
try
{
callback(val);
@ -484,10 +536,10 @@ namespace VELConnect
}
}
}
}
/// <summary>
/// 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>
public static void AddRoomDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
bool sendInitialState = false)
@ -504,11 +556,10 @@ namespace VELConnect
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);
if (val != null)
{
try
{
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;
}
public static string GetRoomData(string key)
{
return instance != null ? instance.lastState?.room?.TryGetData(key) : null;
return instance != null ? instance.lastState?.room?.TryGetData(key) : defaultValue;
}
/// <summary>
@ -769,6 +819,7 @@ namespace VELConnect
byte[] bodyRaw = Encoding.UTF8.GetBytes(postData);
UploadHandlerRaw uploadHandler = new UploadHandlerRaw(bodyRaw);
webRequest.uploadHandler = uploadHandler;
webRequest.downloadHandler = new DownloadHandlerBuffer();
webRequest.SetRequestHeader("Content-Type", "application/json");
if (headers != null)
{

View File

@ -1,6 +1,6 @@
{
"name": "@velaboratory/velconnect-svelte",
"version": "1.0.1",
"version": "1.0.2",
"description": "Use VEL-Connect with a Svelte dashboard",
"main": "src/index.js",
"files": [

View File

@ -3,6 +3,12 @@ import { writable } from "svelte/store";
import type { Record } from "pocketbase";
import { get } from "svelte/store";
let debugLog = false;
export function setDebugLog(val: boolean) {
debugLog = val;
}
export const pb = new PocketBase();
export const currentUser = writable(pb.authStore.model);
@ -27,6 +33,8 @@ export interface Device extends Record {
expand: { data?: DataBlock };
}
export interface DataBlock extends Record {
block_id: string;
owner: string;
data: { [key: string]: string };
}
@ -53,30 +61,45 @@ export async function startListening(baseUrl: string) {
const d = (await pb.collection("Device").getOne(get(currentDeviceId), {
expand: "data",
})) as Device;
deviceFields.set(d);
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) => {
console.log("current device changed");
log("currentDeviceId subscribe change event");
unsubscribeDeviceFields?.();
unsubscribeDeviceData?.();
if (val != "") {
const d = (await pb
.collection("Device")
.getOne(get(currentDeviceId), { expand: "data" })) as Device;
deviceFields.set(d);
deviceData.set(d.expand.data as DataBlock);
unsubscribeDeviceFields = await pb
.collection("Device")
.subscribe(val, async (data) => {
const d = data.record as Device;
// we don't need expand anymore, since it doesn't work in subscribe()
d.expand = {};
deviceFields.set(d);
unsubscribeDeviceData = await pb
.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);
});
@ -92,6 +115,7 @@ export async function startListening(baseUrl: string) {
});
unsubscribeCurrentUser = currentUser.subscribe((user) => {
log(`currentUser changed ${user}`);
pairedDevices.set(user?.["devices"] ?? []);
currentDeviceId.set(get(pairedDevices)[0] ?? "");
});
@ -110,16 +134,26 @@ async function getRoomData(device: Device) {
unsubscribeRoomData?.();
// create or just fetch room by name
const r = (await pb
let r: DataBlock | null = null;
try {
r = (await pb
.collection("DataBlock")
.getFirstListItem(
`block_id="${device.current_app}_${device.current_room}"`
)) 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);
if (r) {
if (r != null) {
unsubscribeRoomData = await pb
.collection("DataBlock")
.subscribe(r.id, (data) => {
log("roomData subscribe change event");
roomData.set(data.record as DataBlock);
});
} else {
@ -148,9 +182,10 @@ export function send() {
console.log("sending...");
sending = true;
let promises: Promise<any>[] = [];
const room = get(roomData);
const device = get(deviceFields);
const data = get(deviceData);
const room = get(roomData);
// TODO send changes only
if (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
currentDeviceId.set(device.id);
if (!get(pairedDevices).includes(device.id)) {
pairedDevices.set([...get(pairedDevices), device.id]);
}
// add it to my account if logged in
const u = get(currentUser);
@ -242,26 +279,32 @@ export async function pair(pairingCode: string) {
export async function login(username: string, password: string) {
try {
await pb.collection("Users").authWithPassword(username, password);
return "";
} catch (err) {
return err as string;
return {};
} catch (err: any) {
return err;
}
}
export async function signUp(username: string, password: string) {
try {
const data = {
email: username,
username: username,
password,
passwordConfirm: password,
};
await pb.collection("Users").create(data);
return await login(username, password);
} catch (err) {
return err as string;
} catch (err: any) {
return err;
}
}
export function signOut() {
pb.authStore.clear();
}
function log(msg: string) {
if (debugLog) {
console.log(msg);
}
}

View File

@ -147,10 +147,12 @@ func main() {
// apply to the db
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 {
return err
log.Fatalln(err)
return c.String(500, err.Error())
}
return c.JSON(http.StatusOK, deviceRecord)
@ -180,11 +182,11 @@ func main() {
}
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"))
log.Println(deviceRecord.GetString("current_room"))
log.Println(roomErr)
// log.Println(deviceRecord.GetString("current_room"))
// log.Println(roomErr)
output := map[string]interface{}{
"device": deviceRecord,