@ibgib/web-gib 0.0.59 → 0.0.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/helpers.d.mts.map +1 -1
  4. package/dist/helpers.mjs +1 -0
  5. package/dist/helpers.mjs.map +1 -1
  6. package/dist/identity/device-delegate-helper.d.mts +43 -0
  7. package/dist/identity/device-delegate-helper.d.mts.map +1 -0
  8. package/dist/identity/device-delegate-helper.mjs +106 -0
  9. package/dist/identity/device-delegate-helper.mjs.map +1 -0
  10. package/dist/identity/device-delegate.respec.d.mts +2 -0
  11. package/dist/identity/device-delegate.respec.d.mts.map +1 -0
  12. package/dist/identity/device-delegate.respec.mjs +106 -0
  13. package/dist/identity/device-delegate.respec.mjs.map +1 -0
  14. package/dist/identity/identity-constants.mjs +2 -2
  15. package/dist/identity/identity-constants.mjs.map +1 -1
  16. package/dist/identity/identity-types.d.mts +4 -4
  17. package/dist/identity/identity-types.d.mts.map +1 -1
  18. package/dist/identity/keystone-sync-service.d.mts +18 -0
  19. package/dist/identity/keystone-sync-service.d.mts.map +1 -1
  20. package/dist/identity/keystone-sync-service.mjs +77 -1
  21. package/dist/identity/keystone-sync-service.mjs.map +1 -1
  22. package/dist/index.d.mts +1 -1
  23. package/dist/index.d.mts.map +1 -1
  24. package/dist/index.mjs +1 -2
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/storage/storage-helpers.web.d.mts +9 -0
  27. package/dist/storage/storage-helpers.web.d.mts.map +1 -1
  28. package/dist/storage/storage-helpers.web.mjs +42 -0
  29. package/dist/storage/storage-helpers.web.mjs.map +1 -1
  30. package/dist/ui/component/identity/add-device/add-device.css +93 -17
  31. package/dist/ui/component/identity/add-device/add-device.d.mts +24 -7
  32. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  33. package/dist/ui/component/identity/add-device/add-device.html +97 -23
  34. package/dist/ui/component/identity/add-device/add-device.mjs +489 -198
  35. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  36. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  37. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +48 -61
  38. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  39. package/dist/ui/component/identity/keystone-details/keystone-details.css +1 -1
  40. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +11 -8
  41. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  42. package/dist/ui/component/identity/keystone-details/keystone-details.html +29 -20
  43. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +358 -97
  44. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  45. package/dist/ui/component/identity/remove-device/remove-device.d.mts +1 -1
  46. package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
  47. package/dist/ui/component/identity/remove-device/remove-device.html +3 -3
  48. package/dist/ui/component/identity/remove-device/remove-device.mjs +7 -7
  49. package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
  50. package/dist/ui/ui-constants.d.mts +1 -0
  51. package/dist/ui/ui-constants.d.mts.map +1 -1
  52. package/dist/ui/ui-constants.mjs +1 -0
  53. package/dist/ui/ui-constants.mjs.map +1 -1
  54. package/dist/ui/ui-types.d.mts +5 -0
  55. package/dist/ui/ui-types.d.mts.map +1 -1
  56. package/dist/util/device-helper.d.mts +30 -0
  57. package/dist/util/device-helper.d.mts.map +1 -0
  58. package/dist/util/device-helper.mjs +90 -0
  59. package/dist/util/device-helper.mjs.map +1 -0
  60. package/dist/util/device-helper.respec.d.mts +2 -0
  61. package/dist/util/device-helper.respec.d.mts.map +1 -0
  62. package/dist/util/device-helper.respec.mjs +83 -0
  63. package/dist/util/device-helper.respec.mjs.map +1 -0
  64. package/package.json +3 -3
  65. package/src/AUTO-GENERATED-version.mts +1 -1
  66. package/src/helpers.mts +1 -0
  67. package/src/identity/custodian-delegate-helper.mts.bak +7 -0
  68. package/src/identity/device-delegate-helper.mts +135 -0
  69. package/src/identity/device-delegate.respec.mts +125 -0
  70. package/src/identity/identity-constants.mts +2 -2
  71. package/src/identity/identity-types.mts +4 -4
  72. package/src/identity/keystone-sync-service.mts +95 -2
  73. package/src/index.mts +1 -2
  74. package/src/storage/storage-helpers.web.mts +47 -0
  75. package/src/ui/component/identity/add-device/add-device.css +93 -17
  76. package/src/ui/component/identity/add-device/add-device.html +97 -23
  77. package/src/ui/component/identity/add-device/add-device.mts +579 -208
  78. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +55 -69
  79. package/src/ui/component/identity/keystone-details/keystone-details.css +1 -1
  80. package/src/ui/component/identity/keystone-details/keystone-details.html +29 -20
  81. package/src/ui/component/identity/keystone-details/keystone-details.mts +374 -106
  82. package/src/ui/component/identity/remove-device/remove-device.html +3 -3
  83. package/src/ui/component/identity/remove-device/remove-device.mts +8 -8
  84. package/src/ui/ui-constants.mts +1 -0
  85. package/src/ui/ui-types.mts +6 -0
  86. package/src/util/device-helper.mts +104 -0
  87. package/src/util/device-helper.respec.mts +90 -0
  88. package/dist/identity/custodian-delegate-helper.d.mts +0 -22
  89. package/dist/identity/custodian-delegate-helper.d.mts.map +0 -1
  90. package/dist/identity/custodian-delegate-helper.mjs +0 -50
  91. package/dist/identity/custodian-delegate-helper.mjs.map +0 -1
  92. package/src/identity/custodian-delegate-helper.mts +0 -61
@@ -0,0 +1,83 @@
1
+ import { respecfully, iReckon, ifWe // DO NOT REMOVE ifWeMight or ifWe even if no longer used. We constantly change these when focusing tests
2
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
3
+ const maam = `[${import.meta.url}]`, sir = maam;
4
+ import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
5
+ import { generateDefaultDeviceTag } from './device-helper.mjs';
6
+ await respecfully(sir, 'device-helper', async () => {
7
+ await ifWe(sir, 'generate clean tag for Desktop Windows Chrome', async () => {
8
+ const lc = `[generate clean tag for Desktop Windows Chrome]`;
9
+ try {
10
+ const tag = generateDefaultDeviceTag({
11
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
12
+ userAgentData: { platform: 'Windows', mobile: false },
13
+ randomSuffix: 'a1b2'
14
+ });
15
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('chrome-win-a1b2');
16
+ }
17
+ catch (error) {
18
+ console.error(`${lc} ${extractErrorMsg(error)}`);
19
+ throw error;
20
+ }
21
+ });
22
+ await ifWe(sir, 'generate clean tag for Mobile iOS Safari (iPhone)', async () => {
23
+ const lc = `[generate clean tag for Mobile iOS Safari (iPhone)]`;
24
+ try {
25
+ const tag = generateDefaultDeviceTag({
26
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
27
+ userAgentData: { platform: 'iOS', mobile: true },
28
+ randomSuffix: '3c4d'
29
+ });
30
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('mobile-safari-ios-3c4d');
31
+ }
32
+ catch (error) {
33
+ console.error(`${lc} ${extractErrorMsg(error)}`);
34
+ throw error;
35
+ }
36
+ });
37
+ await ifWe(sir, 'generate clean tag for Android Chrome Phone', async () => {
38
+ const lc = `[generate clean tag for Android Chrome Phone]`;
39
+ try {
40
+ const tag = generateDefaultDeviceTag({
41
+ userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
42
+ userAgentData: { platform: 'Android', mobile: true },
43
+ randomSuffix: '9e8f'
44
+ });
45
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('mobile-chrome-android-9e8f');
46
+ }
47
+ catch (error) {
48
+ console.error(`${lc} ${extractErrorMsg(error)}`);
49
+ throw error;
50
+ }
51
+ });
52
+ await ifWe(sir, 'generate clean tag for iPad / Tablet', async () => {
53
+ const lc = `[generate clean tag for iPad / Tablet]`;
54
+ try {
55
+ const tag = generateDefaultDeviceTag({
56
+ userAgent: 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
57
+ maxTouchPoints: 5,
58
+ screenWidth: 1024,
59
+ randomSuffix: '7a8b'
60
+ });
61
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('tablet-safari-ios-7a8b');
62
+ }
63
+ catch (error) {
64
+ console.error(`${lc} ${extractErrorMsg(error)}`);
65
+ throw error;
66
+ }
67
+ });
68
+ await ifWe(sir, 'generate fallback tag with random 4-char suffix when no hints are present', async () => {
69
+ const lc = `[generate fallback tag with random 4-char suffix]`;
70
+ try {
71
+ const tag = generateDefaultDeviceTag({
72
+ userAgent: '',
73
+ randomSuffix: '1234'
74
+ });
75
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('device-1234');
76
+ }
77
+ catch (error) {
78
+ console.error(`${lc} ${extractErrorMsg(error)}`);
79
+ throw error;
80
+ }
81
+ });
82
+ });
83
+ //# sourceMappingURL=device-helper.respec.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-helper.respec.mjs","sourceRoot":"","sources":["../../src/util/device-helper.respec.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibgib/web-gib",
3
- "version": "0.0.59",
3
+ "version": "0.0.61",
4
4
  "description": "Framework for creating agentic ibGib web apps. Contains plumbing for ibgib components, agentic framework (currently only Gemini implemented), web-based IndexedDB storage substrate, and more.",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -29,10 +29,10 @@
29
29
  "license": "ISC",
30
30
  "dependencies": {
31
31
  "@google/genai": "^1.33.0",
32
- "@ibgib/core-gib": "^0.1.73",
32
+ "@ibgib/core-gib": "^0.1.75",
33
33
  "@ibgib/encrypt-gib": "^0.3.1",
34
34
  "@ibgib/helper-gib": "^0.0.37",
35
- "@ibgib/ts-gib": "^0.5.33"
35
+ "@ibgib/ts-gib": "^0.5.34"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=19.0.0"
@@ -11,4 +11,4 @@
11
11
  /**
12
12
  * this is the version of this package, auto-updated in the build process
13
13
  */
14
- export const AUTO_GENERATED_VERSION = '0.0.59';
14
+ export const AUTO_GENERATED_VERSION = '0.0.61';
package/src/helpers.mts CHANGED
@@ -716,6 +716,7 @@ export function getIbGibGlobalThis_IbGib(): IbGibGlobalThisInfo {
716
716
  if (!result) {
717
717
  console.log(`${lc} initializing globalThis.${GLOBAL_THIS_IBGIB_KEY}... (I: 9d9ff1813448d9a1b82b0e5790a74825)`);
718
718
  result = {};
719
+ globalThis[GLOBAL_THIS_IBGIB_KEY] = result;
719
720
  }
720
721
 
721
722
  return result;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @module identity/custodian-delegate-helper
3
+ *
4
+ * Re-exports from {@link ./device-delegate-helper.mjs} for backward compatibility.
5
+ */
6
+
7
+ export * from './device-delegate-helper.mjs';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @module identity/device-delegate-helper
3
+ *
4
+ * Client-side helper functions for deriving, retrieving, persisting, and clearing local
5
+ * cryptographic device delegate signing tokens ($S_D$) in IndexedDB.
6
+ *
7
+ * Applicable symmetrically to Sovereign, Custodial, and Hybrid identities.
8
+ */
9
+
10
+ import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
11
+ import { deriveDeviceDelegateSecret } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
12
+ import { storageGet, storagePut, storageDelete } from "../storage/storage-helpers.web.mjs";
13
+ import { getGlobalDbName, getGlobalStoreName } from "../helpers.web.mjs";
14
+
15
+ export const DEVICE_DELEGATE_SECRET_PREFIX = 'ibgib_device_delegate_secret_';
16
+
17
+ /**
18
+ * Builds the IndexedDB storage key for a device delegate secret given its primary identity TJP address.
19
+ */
20
+ export function getDeviceDelegateSecretKey({ primaryTjpAddr }: { primaryTjpAddr: string }): string {
21
+ if (!primaryTjpAddr) {
22
+ throw new Error('primaryTjpAddr is required to get device delegate secret key (E: 1a2b3c4d5e6f7890123456789a000101)');
23
+ }
24
+ return `${DEVICE_DELEGATE_SECRET_PREFIX}${primaryTjpAddr}`;
25
+ }
26
+
27
+ /**
28
+ * Retrieves the stored device delegate secret for the given primary identity from IndexedDB, if present.
29
+ */
30
+ export async function getDeviceDelegateSecret({
31
+ primaryTjpAddr,
32
+ }: {
33
+ primaryTjpAddr: string;
34
+ }): Promise<string | undefined> {
35
+ const lc = `[${getDeviceDelegateSecret.name}]`;
36
+ try {
37
+ if (!primaryTjpAddr) {
38
+ throw new Error('primaryTjpAddr is required to get device delegate secret (E: 2a2b3c4d5e6f7890123456789a000102)');
39
+ }
40
+
41
+ const dbName = getGlobalDbName();
42
+ const storeName = getGlobalStoreName();
43
+ const key = getDeviceDelegateSecretKey({ primaryTjpAddr });
44
+
45
+ return await storageGet({ dbName, storeName, key });
46
+ } catch (error) {
47
+ console.error(`${lc} ${extractErrorMsg(error)}`);
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Persists a device delegate secret for the given primary identity into IndexedDB.
54
+ */
55
+ export async function setDeviceDelegateSecret({
56
+ primaryTjpAddr,
57
+ delegateSecret,
58
+ }: {
59
+ primaryTjpAddr: string;
60
+ delegateSecret: string;
61
+ }): Promise<void> {
62
+ const lc = `[${setDeviceDelegateSecret.name}]`;
63
+ try {
64
+ if (!primaryTjpAddr) {
65
+ throw new Error('primaryTjpAddr is required to set device delegate secret (E: 3a2b3c4d5e6f7890123456789a000103)');
66
+ }
67
+ if (!delegateSecret) {
68
+ throw new Error('delegateSecret is required to set device delegate secret (E: 4a2b3c4d5e6f7890123456789a000104)');
69
+ }
70
+
71
+ const dbName = getGlobalDbName();
72
+ const storeName = getGlobalStoreName();
73
+ const key = getDeviceDelegateSecretKey({ primaryTjpAddr });
74
+
75
+ await storagePut({ dbName, storeName, key, value: delegateSecret });
76
+ } catch (error) {
77
+ console.error(`${lc} ${extractErrorMsg(error)}`);
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Retrieves an existing device delegate secret from IndexedDB, or generates a high-entropy 512-bit
84
+ * secret, persists it, and returns it.
85
+ */
86
+ export async function getOrCreateDeviceDelegateSecret({
87
+ primaryTjpAddr,
88
+ deviceTag,
89
+ }: {
90
+ primaryTjpAddr: string;
91
+ deviceTag?: string;
92
+ }): Promise<string> {
93
+ const lc = `[${getOrCreateDeviceDelegateSecret.name}]`;
94
+ try {
95
+ if (!primaryTjpAddr) {
96
+ throw new Error('primaryTjpAddr is required to get or create device delegate secret (E: 5a2b3c4d5e6f7890123456789a000105)');
97
+ }
98
+
99
+ const existingSecret = await getDeviceDelegateSecret({ primaryTjpAddr });
100
+ if (existingSecret) {
101
+ return existingSecret;
102
+ }
103
+
104
+ const newSecret = await deriveDeviceDelegateSecret({ primaryTjpAddr, deviceTag });
105
+ await setDeviceDelegateSecret({ primaryTjpAddr, delegateSecret: newSecret });
106
+
107
+ return newSecret;
108
+ } catch (error) {
109
+ console.error(`${lc} ${extractErrorMsg(error)}`);
110
+ throw error;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Removes the stored local device delegate secret from IndexedDB.
116
+ */
117
+ export async function clearDeviceDelegateSecret({
118
+ primaryTjpAddr,
119
+ }: {
120
+ primaryTjpAddr: string;
121
+ }): Promise<void> {
122
+ const lc = `[${clearDeviceDelegateSecret.name}]`;
123
+ try {
124
+ if (!primaryTjpAddr) { return; }
125
+
126
+ const dbName = getGlobalDbName();
127
+ const storeName = getGlobalStoreName();
128
+ const key = getDeviceDelegateSecretKey({ primaryTjpAddr });
129
+
130
+ await storageDelete({ dbName, storeName, key });
131
+ } catch (error) {
132
+ console.error(`${lc} ${extractErrorMsg(error)}`);
133
+ throw error;
134
+ }
135
+ }
@@ -0,0 +1,125 @@
1
+ import { respecfully, ifWeMight, iReckon } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
2
+ import { getIbGibGlobalThis_IbGib } from '../helpers.mjs';
3
+ import {
4
+ getDeviceDelegateSecretKey,
5
+ getDeviceDelegateSecret,
6
+ setDeviceDelegateSecret,
7
+ getOrCreateDeviceDelegateSecret,
8
+ clearDeviceDelegateSecret,
9
+ DEVICE_DELEGATE_SECRET_PREFIX
10
+ } from './device-delegate-helper.mjs';
11
+
12
+ const sir = `[${import.meta.url}]`;
13
+
14
+ // Initialize global db & store names for tests
15
+ const globalThisIbGib = getIbGibGlobalThis_IbGib();
16
+ globalThisIbGib.dbName_hack = 'space_gib_db';
17
+ globalThisIbGib.storeName_hack = 'space_gib_store';
18
+
19
+ // Mock storage substrate for Node test environment
20
+ const inMemoryStore = new Map<string, string>();
21
+
22
+ if (typeof globalThis.indexedDB === 'undefined') {
23
+ (globalThis as any).indexedDB = {
24
+ open: (dbName: string) => {
25
+ const req: any = {};
26
+ setTimeout(() => {
27
+ const db = {
28
+ objectStoreNames: { contains: () => true },
29
+ transaction: (storeName: string) => ({
30
+ objectStore: (sName: string) => ({
31
+ get: (key: string) => {
32
+ const getReq: any = {};
33
+ setTimeout(() => {
34
+ const val = inMemoryStore.get(key);
35
+ getReq.result = val !== undefined ? { value: val } : undefined;
36
+ if (getReq.onsuccess) getReq.onsuccess({ target: getReq });
37
+ }, 0);
38
+ return getReq;
39
+ },
40
+ put: (datum: any) => {
41
+ const putReq: any = {};
42
+ setTimeout(() => {
43
+ inMemoryStore.set(datum.key, datum.value);
44
+ if (putReq.onsuccess) putReq.onsuccess({ target: putReq });
45
+ }, 0);
46
+ return putReq;
47
+ },
48
+ delete: (key: string) => {
49
+ const delReq: any = {};
50
+ setTimeout(() => {
51
+ inMemoryStore.delete(key);
52
+ if (delReq.onsuccess) delReq.onsuccess({ target: delReq });
53
+ }, 0);
54
+ return delReq;
55
+ }
56
+ })
57
+ }),
58
+ close: () => {}
59
+ };
60
+ req.result = db;
61
+ if (req.onsuccess) req.onsuccess({ target: req });
62
+ }, 0);
63
+ return req;
64
+ }
65
+ };
66
+ }
67
+
68
+ await respecfully(sir, 'Device Delegate Storage & Secret Lifecycle', async () => {
69
+
70
+ await ifWeMight(sir, 'getDeviceDelegateSecretKey constructs deterministic storage key', async () => {
71
+ const primaryTjpAddr = 'keystone^alice_tjp_12345';
72
+ const key = getDeviceDelegateSecretKey({ primaryTjpAddr });
73
+
74
+ iReckon(sir, key).willEqual(`${DEVICE_DELEGATE_SECRET_PREFIX}${primaryTjpAddr}`);
75
+ });
76
+
77
+ await ifWeMight(sir, 'setDeviceDelegateSecret and getDeviceDelegateSecret persist and retrieve secret', async () => {
78
+ const primaryTjpAddr = 'keystone^bob_tjp_999';
79
+ const secret = 'super-secret-512-token-abc';
80
+
81
+ await setDeviceDelegateSecret({ primaryTjpAddr, delegateSecret: secret });
82
+ const retrieved = await getDeviceDelegateSecret({ primaryTjpAddr });
83
+
84
+ iReckon(sir, retrieved).willEqual(secret);
85
+ });
86
+
87
+ await ifWeMight(sir, 'getOrCreateDeviceDelegateSecret generates high entropy token on first call and caches it', async () => {
88
+ const primaryTjpAddr = 'keystone^charlie_tjp_888';
89
+
90
+ const token1 = await getOrCreateDeviceDelegateSecret({ primaryTjpAddr, deviceTag: 'laptop' });
91
+ iReckon(sir, typeof token1 === 'string' && token1.length === 128).willEqual(true);
92
+
93
+ const token2 = await getOrCreateDeviceDelegateSecret({ primaryTjpAddr, deviceTag: 'laptop' });
94
+ iReckon(sir, token1).willEqual(token2);
95
+ });
96
+
97
+ await ifWeMight(sir, 'clearDeviceDelegateSecret removes stored secret', async () => {
98
+ const primaryTjpAddr = 'keystone^david_tjp_777';
99
+
100
+ await getOrCreateDeviceDelegateSecret({ primaryTjpAddr });
101
+ const beforeClear = await getDeviceDelegateSecret({ primaryTjpAddr });
102
+ iReckon(sir, !!beforeClear).willEqual(true);
103
+
104
+ await clearDeviceDelegateSecret({ primaryTjpAddr });
105
+ const afterClear = await getDeviceDelegateSecret({ primaryTjpAddr });
106
+ iReckon(sir, afterClear).willEqual(undefined);
107
+ });
108
+
109
+ await ifWeMight(sir, 'fails fast when global db or store name is uninitialized', async () => {
110
+ const primaryTjpAddr = 'keystone^failfast_tjp_000';
111
+ const savedDb = globalThisIbGib.dbName_hack;
112
+ globalThisIbGib.dbName_hack = undefined as any;
113
+
114
+ let threw = false;
115
+ try {
116
+ await getDeviceDelegateSecret({ primaryTjpAddr });
117
+ } catch (error) {
118
+ threw = true;
119
+ } finally {
120
+ globalThisIbGib.dbName_hack = savedDb;
121
+ }
122
+
123
+ iReckon(sir, threw).willEqual(true);
124
+ });
125
+ });
@@ -65,8 +65,8 @@ export const IDENTITY_POOL_ID_APP_EPOCH = 'identity-app-epoch';
65
65
 
66
66
  // ===========================================================================
67
67
  // KDF SALTS
68
- // Used to derive tier-specific MasterSecrets from a root passphrase.
69
- // Different salt per tier ensures independent secrets even with same passphrase.
68
+ // Used to derive tier-specific MasterSecrets from a root password.
69
+ // Different salt per tier ensures independent secrets even with same password.
70
70
  // ===========================================================================
71
71
 
72
72
  export const IDENTITY_KDF_SALT_META = 'ibgib-identity-meta-v1';
@@ -221,17 +221,17 @@ export interface IIdentityService {
221
221
  // ===========================================================================
222
222
 
223
223
  /**
224
- * Inputs for deriving a tier-specific MasterSecret from a root passphrase.
224
+ * Inputs for deriving a tier-specific MasterSecret from a root password.
225
225
  *
226
226
  * Each tier uses a different salt to ensure independent MasterSecrets
227
- * even when the same passphrase is used for all tiers.
227
+ * even when the same password is used for all tiers.
228
228
  *
229
229
  * @see docs/identity/KEYSTONES.md for per-tier salt conventions
230
230
  * @see identity-helpers.mts for derivation functions
231
231
  */
232
232
  export interface MasterSecretDerivationContext {
233
- /** The root passphrase (sovereign path) or server secret (custodial path). */
234
- passphrase: string;
233
+ /** The root password (sovereign path) or server secret (custodial path). */
234
+ password: string;
235
235
 
236
236
  /**
237
237
  * Tier-specific salt (e.g., `IDENTITY_KDF_SALT_META`, `IDENTITY_KDF_SALT_LOCAL`).
@@ -9,12 +9,15 @@ import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs
9
9
  import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
10
10
  import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
11
11
  import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
12
+ import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
12
13
  import { getTimelinesGroupedByTjp, getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
13
14
  import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
15
+ import { isDelegateExpired } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.mjs";
14
16
  import { getApiBridge } from "../api/ibgib-api-bridge.mjs";
15
17
  import { getGlobalMetaspace_waitIfNeeded } from "../helpers.mjs";
16
- import { EVENT_IBGIB_IDENTITY_UPDATE_AVAILABLE } from "../ui/ui-constants.mjs";
17
- import { EventIbGibIdentityUpdateAvailableDetail } from "../ui/ui-types.mjs";
18
+ import { getDeviceDelegateSecret } from "./device-delegate-helper.mjs";
19
+ import { EVENT_IBGIB_IDENTITY_UPDATE_AVAILABLE, EVENT_IBGIB_IDENTITY_RENEWAL_REQUIRED } from "../ui/ui-constants.mjs";
20
+ import { EventIbGibIdentityUpdateAvailableDetail, EventIbGibIdentityRenewalRequiredDetail } from "../ui/ui-types.mjs";
18
21
 
19
22
  export interface KeystoneUpdateCheckResult {
20
23
  hasUpdate: boolean;
@@ -37,6 +40,96 @@ export class KeystoneSyncService {
37
40
  }
38
41
  }
39
42
 
43
+ private dispatchRenewalRequiredEvent(detail: EventIbGibIdentityRenewalRequiredDetail): void {
44
+ if (typeof window !== 'undefined') {
45
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_RENEWAL_REQUIRED, {
46
+ detail,
47
+ bubbles: true,
48
+ composed: true,
49
+ }));
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Retrieves the stored device delegate secret ($S_D$) for an active primary identity from IndexedDB.
55
+ */
56
+ public async getDeviceDelegateSecret({
57
+ primaryTjpAddr
58
+ }: {
59
+ primaryTjpAddr: string;
60
+ }): Promise<string | undefined> {
61
+ return getDeviceDelegateSecret({ primaryTjpAddr });
62
+ }
63
+
64
+ /**
65
+ * Checks if the active identity has a valid, unexpired sync delegate on this device.
66
+ */
67
+ public async checkDelegateStatus({
68
+ activePrimaryAddr,
69
+ metaspace,
70
+ space
71
+ }: {
72
+ activePrimaryAddr: string;
73
+ metaspace?: any;
74
+ space?: any;
75
+ }): Promise<{ isValid: boolean; reason?: 'expired' | 'missing_secret' }> {
76
+ const lc = `${this.lc}[${this.checkDelegateStatus.name}]`;
77
+ try {
78
+ if (!metaspace) metaspace = await getGlobalMetaspace_waitIfNeeded();
79
+ if (!metaspace) {
80
+ throw new Error(`${lc} (UNEXPECTED) metaspace falsy? (E: a1b2c3d4e5f60718293a4b5c6d7e8f90)`);
81
+ }
82
+ if (!space) space = await metaspace.getLocalUserSpace({ lock: false });
83
+ if (!space) {
84
+ throw new Error(`${lc} (UNEXPECTED) space falsy? (E: b2c3d4e5f60718293a4b5c6d7e8f90a1)`);
85
+ }
86
+
87
+ const info = getGibInfo({ ibGibAddr: activePrimaryAddr });
88
+ const { ib } = getIbAndGib({ ibGibAddr: activePrimaryAddr });
89
+ const primaryTjpAddr = info.tjpGib ? `${ib}^${info.tjpGib}` : activePrimaryAddr;
90
+
91
+ // 1. Check if local secret exists in IndexedDB
92
+ const secret = await getDeviceDelegateSecret({ primaryTjpAddr });
93
+ if (!secret) {
94
+ this.dispatchRenewalRequiredEvent({
95
+ activePrimaryAddr,
96
+ primaryTjpAddr,
97
+ reason: 'missing_secret'
98
+ });
99
+ return { isValid: false, reason: 'missing_secret' };
100
+ }
101
+
102
+ // 2. Check if primary keystone has registered delegates and if any are expired
103
+ const keystoneService = new KeystoneService_V1();
104
+ const keystone = await keystoneService.getLatestKeystone({
105
+ addr: activePrimaryAddr,
106
+ metaspace,
107
+ space
108
+ });
109
+ if (!keystone) {
110
+ throw new Error(`${lc} (UNEXPECTED) latest primary keystone not found for addr: ${activePrimaryAddr} (E: c3d4e5f60718293a4b5c6d7e8f90a1b2)`);
111
+ }
112
+
113
+ if (keystone?.data?.delegates) {
114
+ const delegates = Object.values(keystone.data.delegates);
115
+ const isAnyExpired = delegates.some(d => isDelegateExpired({ delegateInfo: d }));
116
+ if (isAnyExpired) {
117
+ this.dispatchRenewalRequiredEvent({
118
+ activePrimaryAddr,
119
+ primaryTjpAddr,
120
+ reason: 'expired'
121
+ });
122
+ return { isValid: false, reason: 'expired' };
123
+ }
124
+ }
125
+
126
+ return { isValid: true };
127
+ } catch (error) {
128
+ console.error(`${lc} ${extractErrorMsg(error)}`);
129
+ return { isValid: false };
130
+ }
131
+ }
132
+
40
133
  /**
41
134
  * Checks if a server update exists for the given active primary keystone address.
42
135
  */
package/src/index.mts CHANGED
@@ -18,8 +18,7 @@ export * from "./api/ibgib-api-bridge.mjs";
18
18
 
19
19
  // Identity & Sync services
20
20
  export * from "./identity/keystone-sync-service.mjs";
21
- export * from "./identity/custodian-delegate-helper.mjs";
22
- // export * from "./identity/sso-callback-helper.mjs";
21
+ export * from "./identity/device-delegate-helper.mjs";
23
22
  export * from "./identity/sso-popup-helper.mjs";
24
23
 
25
24
  // Identity UI components
@@ -726,3 +726,50 @@ export async function storageReaddir({
726
726
  }
727
727
  }
728
728
  }
729
+
730
+ /**
731
+ * Deletes a single key from indexeddb storage.
732
+ */
733
+ export async function storageDelete({
734
+ dbName,
735
+ storeName,
736
+ key,
737
+ logalot = false,
738
+ }: {
739
+ dbName: string;
740
+ storeName: string;
741
+ key: string;
742
+ logalot?: boolean;
743
+ }): Promise<void> {
744
+ const lc = `[${storageDelete.name}]`;
745
+ let db: IDBDatabase | undefined = undefined;
746
+ try {
747
+ if (logalot) { console.log(`${lc} starting... (I: 8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d)`); }
748
+ await new Promise<void>((resolve, reject) => {
749
+ const openDBRequest = indexedDB.open(dbName);
750
+ openDBRequest.onerror = () => {
751
+ reject(openDBRequest.error);
752
+ };
753
+ openDBRequest.onsuccess = () => {
754
+ db = openDBRequest.result;
755
+ const transaction = db.transaction(storeName, "readwrite");
756
+ const objectStore = transaction.objectStore(storeName);
757
+ const deleteRequest = objectStore.delete(key);
758
+ deleteRequest.onerror = () => {
759
+ reject(deleteRequest.error);
760
+ };
761
+ deleteRequest.onsuccess = () => {
762
+ resolve();
763
+ };
764
+ };
765
+ });
766
+ } catch (error) {
767
+ console.error(`${lc} ${extractErrorMsg(error)}`);
768
+ throw error;
769
+ } finally {
770
+ if (db) {
771
+ (db as any).close();
772
+ }
773
+ if (logalot) { console.log(`${lc} complete.`); }
774
+ }
775
+ }