From 2555c3082f8b66566e0c8e92423a84bf7ad671c3 Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Fri, 7 Jul 2023 22:45:30 -0400 Subject: [PATCH] mostly getting device and rooms correctly --- example_dashboard/package-lock.json | 25 +++ example_dashboard/package.json | 6 +- example_dashboard/src/app.html | 2 +- .../src/lib/components/Login.svelte | 2 +- .../src/lib/components/Pair.svelte | 4 +- example_dashboard/src/lib/js/util.ts | 30 +++ .../src/lib/{ => js}/velconnect.ts | 2 +- example_dashboard/src/routes/+page.svelte | 100 +++++++-- example_dashboard/static/favicon.png | Bin 1571 -> 0 bytes unity_package/Runtime/VELConnectManager.cs | 204 ++++++++++++------ .../migrations/1688782403_updated_Device.go | 54 +++++ .../migrations/1688783036_updated_Device.go | 54 +++++ 12 files changed, 387 insertions(+), 96 deletions(-) create mode 100644 example_dashboard/src/lib/js/util.ts rename example_dashboard/src/lib/{ => js}/velconnect.ts (93%) delete mode 100644 example_dashboard/static/favicon.png create mode 100644 velconnect/migrations/1688782403_updated_Device.go create mode 100644 velconnect/migrations/1688783036_updated_Device.go diff --git a/example_dashboard/package-lock.json b/example_dashboard/package-lock.json index 3326ff8..8dc4b44 100644 --- a/example_dashboard/package-lock.json +++ b/example_dashboard/package-lock.json @@ -8,6 +8,8 @@ "name": "example-dashboard", "version": "0.0.1", "dependencies": { + "humanize-duration": "^3.28.0", + "luxon": "^3.3.0", "pocketbase": "^0.15.2" }, "devDependencies": { @@ -1802,6 +1804,11 @@ "node": ">=8" } }, + "node_modules/humanize-duration": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.28.0.tgz", + "integrity": "sha512-jMAxraOOmHuPbffLVDKkEKi/NeG8dMqP8lGRd6Tbf7JgAeG33jjgPWDbXXU7ypCI0o+oNKJFgbSB9FKVdWNI2A==" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -2034,6 +2041,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", @@ -4383,6 +4398,11 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "humanize-duration": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.28.0.tgz", + "integrity": "sha512-jMAxraOOmHuPbffLVDKkEKi/NeG8dMqP8lGRd6Tbf7JgAeG33jjgPWDbXXU7ypCI0o+oNKJFgbSB9FKVdWNI2A==" + }, "ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -4563,6 +4583,11 @@ "yallist": "^4.0.0" } }, + "luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==" + }, "magic-string": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", diff --git a/example_dashboard/package.json b/example_dashboard/package.json index 2786ad6..213b694 100644 --- a/example_dashboard/package.json +++ b/example_dashboard/package.json @@ -21,16 +21,18 @@ "eslint-plugin-svelte": "^2.30.0", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", + "sass": "^1.63.6", "svelte": "^4.0.0", "svelte-check": "^3.4.3", "svelte-preprocess": "^5.0.4", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^4.3.6", - "sass": "^1.63.6" + "vite": "^4.3.6" }, "type": "module", "dependencies": { + "humanize-duration": "^3.28.0", + "luxon": "^3.3.0", "pocketbase": "^0.15.2" } } diff --git a/example_dashboard/src/app.html b/example_dashboard/src/app.html index effe0d0..c2c3773 100644 --- a/example_dashboard/src/app.html +++ b/example_dashboard/src/app.html @@ -2,7 +2,7 @@ - + %sveltekit.head% diff --git a/example_dashboard/src/lib/components/Login.svelte b/example_dashboard/src/lib/components/Login.svelte index 62226fa..446db14 100644 --- a/example_dashboard/src/lib/components/Login.svelte +++ b/example_dashboard/src/lib/components/Login.svelte @@ -1,5 +1,5 @@ + + VEL-Connect + +

VEL-Connect

logo

@@ -119,20 +155,22 @@

Devices:

- {#each $pairedDevices as d} -
- - -
- {/each} +
+ {#each $pairedDevices as d} +
+ + +
+ {/each} +
{#if $pairedDevices.length == 0}

No devices paired. Enter a pairing code above.

{/if} @@ -158,11 +196,11 @@
First Seen
-

{deviceData.created}

+

{prettyDate(deviceData.created)}

Last Seen
-

{deviceData.updated}

+

{prettyDate(deviceData.updated)}

@@ -218,8 +256,11 @@ -
Raw JSON:
+

Raw JSON:

+
Device Data
{JSON.stringify(deviceData, null, 2)}
+
Room Data
+
{JSON.stringify(roomData, null, 2)}
{/if} diff --git a/example_dashboard/static/favicon.png b/example_dashboard/static/favicon.png deleted file mode 100644 index 825b9e65af7c104cfb07089bb28659393b4f2097..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH data; /// @@ -88,9 +89,9 @@ namespace VELConnect public class UserCount { - public readonly string id; - public readonly DateTime created; - public readonly DateTime updated; + [CanBeNull] public readonly string id; + public readonly DateTime? created; + public readonly DateTime? updated; public string device_id; public string app_id; public string room_id; @@ -100,7 +101,20 @@ namespace VELConnect public string platform; } + public enum DeviceField + { + device_id, + os_info, + friendly_name, + modified_by, + current_app, + current_room, + pairing_code, + last_online + } + public State lastState; + public State state; public static Action OnInitialState; public static Action OnDeviceFieldChanged; @@ -136,7 +150,7 @@ namespace VELConnect get { Hash128 hash = new Hash128(); - hash.Append(DeviceId); + hash.Append(deviceId); // change once a day hash.Append(DateTime.UtcNow.DayOfYear); // between 1000 and 9999 inclusive (any 4 digit number) @@ -144,52 +158,52 @@ namespace VELConnect } } - private static string DeviceId - { - get - { -#if UNITY_EDITOR - // allows running multiple builds on the same computer - // return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath); - return SystemInfo.deviceUniqueIdentifier + "_EDITOR"; -#else - return SystemInfo.deviceUniqueIdentifier; -#endif - } - } + private static string deviceId; private void Awake() { if (_instance != null) Debug.LogError("VELConnectManager instance already exists", this); _instance = this; + + // Compute device id + MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); + StringBuilder sb = new StringBuilder(SystemInfo.deviceUniqueIdentifier); + sb.Append(Application.productName); +#if UNITY_EDITOR + // allows running multiple builds on the same computer + // return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath); + sb.Append(Application.dataPath); + sb.Append("EDITOR"); +#endif + string id = Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()))); + deviceId = id[..15]; } // Start is called before the first frame update private void Start() { - SetDeviceField(new State.Device + SetDeviceField(new Dictionary { - os_info = SystemInfo.operatingSystem, - friendly_name = SystemInfo.deviceName, - current_app = Application.productName, - pairing_code = PairingCode, + { DeviceField.os_info, SystemInfo.operatingSystem }, + { DeviceField.friendly_name, SystemInfo.deviceName }, + { DeviceField.current_app, Application.productName }, + { DeviceField.pairing_code, PairingCode }, }); - UpdateUserCount(); + // UpdateUserCount(); StartCoroutine(SlowLoop()); VelNetManager.OnJoinedRoom += room => { - SetDeviceField(new State.Device + SetDeviceField(new Dictionary { - current_app = Application.productName, - current_room = room, + { DeviceField.current_app, Application.productName }, + { DeviceField.current_room, room }, }); }; } - private void UpdateUserCount(bool leaving = false) { if (!VelNetManager.InRoom) return; @@ -198,7 +212,7 @@ namespace VELConnect { UserCount postData = new UserCount { - device_id = DeviceId, + device_id = deviceId, app_id = Application.productName, room_id = VelNetManager.Room ?? "", total_users = rooms.rooms.Sum(r => r.numUsers) - (leaving ? 1 : 0), @@ -219,9 +233,9 @@ namespace VELConnect { try { - GetRequestCallback(velConnectUrl + "/state/device/" + DeviceId, json => + GetRequestCallback(velConnectUrl + "/state/device/" + deviceId, json => { - State state = JsonConvert.DeserializeObject(json); + state = JsonConvert.DeserializeObject(json); if (state == null) return; bool isInitialState = false; @@ -380,6 +394,10 @@ namespace VELConnect } lastState = state; + if (lastState?.device?.pairing_code == null) + { + Debug.LogError("Pairing code nulllll"); + } }); } catch (Exception e) @@ -510,33 +528,46 @@ namespace VELConnect return _instance != null ? _instance.lastState?.room?.TryGetData(key) : null; } - /// /// Sets data on the device keys themselves /// These are fixed fields defined for every application /// - public static void SetDeviceField(State.Device device) + public static void SetDeviceField(Dictionary device) { - device.last_online = DateTime.UtcNow; + device[DeviceField.last_online] = DateTime.UtcNow.ToLongDateString(); - // update our local state, so we don't get change events on our own updates - if (_instance.lastState?.device != null) + if (_instance.state?.device != null) { - FieldInfo[] fields = device.GetType().GetFields(); - // loop through all the fields in the device - foreach (FieldInfo fieldInfo in fields) + foreach (DeviceField key in device.Keys.ToArray()) { - fieldInfo.SetValue(_instance.lastState.device, fieldInfo.GetValue(device)); + FieldInfo field = _instance.state.device.GetType().GetField(key.ToString()); + if ((string)field.GetValue(_instance.state.device) != device[key]) + { + if (_instance.lastState?.device != null) + { + // update our local state, so we don't get change events on our own updates + field.SetValue(_instance.lastState.device, device[key]); + } + } + else + { + // don't send this field, since it's the same + device.Remove(key); + } + } + + // last_online field always changes + if (device.Keys.Count <= 1) + { + // nothing changed, don't send + return; } } PostRequestCallback( - _instance.velConnectUrl + "/device/" + DeviceId, - JsonConvert.SerializeObject(device, Formatting.None, new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore - }) + _instance.velConnectUrl + "/device/" + deviceId, + JsonConvert.SerializeObject(device) ); } @@ -545,23 +576,44 @@ namespace VELConnect /// public static void SetDeviceData(Dictionary data) { - State.Device device = new State.Device + if (_instance.state?.device != null) { - last_online = DateTime.UtcNow, - data = data, - }; - - // update our local state, so we don't get change events on our own updates - if (_instance.lastState?.device != null) - { - foreach (KeyValuePair kvp in data) + foreach (string key in data.Keys.ToList()) { - _instance.lastState.device.data[kvp.Key] = kvp.Value; + // if the value is unchanged from the current state, remove it so we don't double-update + if (_instance.state.device.data.TryGetValue(key, out string val) && val == data[key]) + { + data.Remove(key); + } + else + { + // update our local state, so we don't get change events on our own updates + if (_instance.lastState?.device?.data != null) + { + _instance.lastState.device.data[key] = data[key]; + } + } } + + // nothing was changed + if (data.Keys.Count == 0) + { + return; + } + + // if we have no data, just set the whole thing + if (_instance.lastState?.device != null) _instance.lastState.device.data ??= data; } + + Dictionary device = new Dictionary + { + { "last_online", DateTime.UtcNow.ToLongDateString() }, + { "data", data }, + }; + PostRequestCallback( - _instance.velConnectUrl + "/device/" + DeviceId, + _instance.velConnectUrl + "/device/" + deviceId, JsonConvert.SerializeObject(device, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore @@ -589,6 +641,24 @@ namespace VELConnect data = data }; + // remove keys that already match our current state + if (_instance.state?.room != null) + { + foreach (string key in data.Keys.ToArray()) + { + if (_instance.state.room.data[key] == data[key]) + { + data.Remove(key); + } + } + } + + // if we have no changed values + if (data.Keys.Count == 0) + { + return; + } + // update our local state, so we don't get change events on our own updates if (_instance.lastState?.room != null) { @@ -727,7 +797,7 @@ namespace VELConnect private void OnApplicationFocus(bool focus) { - UpdateUserCount(!focus); + // UpdateUserCount(!focus); } } } \ No newline at end of file diff --git a/velconnect/migrations/1688782403_updated_Device.go b/velconnect/migrations/1688782403_updated_Device.go new file mode 100644 index 0000000..1e793db --- /dev/null +++ b/velconnect/migrations/1688782403_updated_Device.go @@ -0,0 +1,54 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("fupstz47c55s69f") + if err != nil { + return err + } + + // add + new_current_room_id := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "wvpaovjo", + "name": "current_room_id", + "type": "relation", + "required": false, + "unique": false, + "options": { + "collectionId": "3qwwkz4wb0lyi78", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": [] + } + }`), new_current_room_id) + collection.Schema.AddField(new_current_room_id) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("fupstz47c55s69f") + if err != nil { + return err + } + + // remove + collection.Schema.RemoveField("wvpaovjo") + + return dao.SaveCollection(collection) + }) +} diff --git a/velconnect/migrations/1688783036_updated_Device.go b/velconnect/migrations/1688783036_updated_Device.go new file mode 100644 index 0000000..42098fc --- /dev/null +++ b/velconnect/migrations/1688783036_updated_Device.go @@ -0,0 +1,54 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("fupstz47c55s69f") + if err != nil { + return err + } + + // remove + collection.Schema.RemoveField("wvpaovjo") + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("fupstz47c55s69f") + if err != nil { + return err + } + + // add + del_current_room_id := &schema.SchemaField{} + json.Unmarshal([]byte(`{ + "system": false, + "id": "wvpaovjo", + "name": "current_room_id", + "type": "relation", + "required": false, + "unique": false, + "options": { + "collectionId": "3qwwkz4wb0lyi78", + "cascadeDelete": false, + "minSelect": null, + "maxSelect": 1, + "displayFields": [] + } + }`), del_current_room_id) + collection.Schema.AddField(del_current_room_id) + + return dao.SaveCollection(collection) + }) +}