@ibgib/space-gib 0.0.5 → 0.0.7

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 (58) hide show
  1. package/README.md +20 -0
  2. package/dist/client/bootstrap.mjs +31 -31
  3. package/dist/client/bootstrap.mjs.map +3 -3
  4. package/dist/client/chunk-RXFIBFRK.mjs +42 -0
  5. package/dist/client/chunk-RXFIBFRK.mjs.map +7 -0
  6. package/dist/client/chunk-ULFNE26Y.mjs +2216 -0
  7. package/dist/client/chunk-ULFNE26Y.mjs.map +7 -0
  8. package/dist/client/index.html +35 -14
  9. package/dist/client/index.mjs +1 -1
  10. package/dist/client/script.mjs +1 -1
  11. package/dist/client/style.css +29 -5
  12. package/dist/server/server.mjs +1447 -270
  13. package/dist/server/server.mjs.map +4 -4
  14. package/package.json +5 -5
  15. package/space-gib.localhost-1783713259760.log +45 -0
  16. package/src/client/AUTO-GENERATED-version.mts +1 -1
  17. package/src/client/bootstrap.mts +9 -0
  18. package/src/client/components/identity-header/identity-header.css +40 -2
  19. package/src/client/components/identity-header/identity-header.html +3 -2
  20. package/src/client/components/identity-header/identity-header.mts +127 -49
  21. package/src/client/components/identity-manager/identity-manager.css +57 -353
  22. package/src/client/components/identity-manager/identity-manager.html +0 -94
  23. package/src/client/components/identity-manager/identity-manager.mts +356 -484
  24. package/src/client/components/keystone-creator/keystone-creator.mts +90 -13
  25. package/src/client/components/keystone-details/keystone-details.css +472 -0
  26. package/src/client/components/keystone-details/keystone-details.html +99 -0
  27. package/src/client/components/keystone-details/keystone-details.mts +821 -0
  28. package/src/client/components/keystone-scrubber/SCRUBBER_IMPLEMENTATION.md +33 -0
  29. package/src/client/components/keystone-scrubber/keystone-scrubber.css +46 -0
  30. package/src/client/components/keystone-scrubber/keystone-scrubber.html +15 -0
  31. package/src/client/components/keystone-scrubber/keystone-scrubber.mts +356 -0
  32. package/src/client/dev-tools/common.mts +505 -10
  33. package/src/client/dev-tools/phase-4-1.mts +19 -4
  34. package/src/client/dev-tools/phase-4-10.mts +77 -305
  35. package/src/client/dev-tools/phase-4-2.mts +46 -212
  36. package/src/client/dev-tools/phase-4-3.mts +57 -198
  37. package/src/client/dev-tools/phase-4-4.mts +57 -170
  38. package/src/client/dev-tools/phase-4-5.mts +48 -204
  39. package/src/client/dev-tools/phase-4-6.mts +37 -103
  40. package/src/client/dev-tools/phase-4-7.mts +44 -228
  41. package/src/client/dev-tools/phase-4-8.mts +33 -136
  42. package/src/client/dev-tools/phase-4-9.mts +33 -137
  43. package/src/client/dev-tools/phase-4.mts +30 -6
  44. package/src/client/dev-tools.mts +18 -0
  45. package/src/client/index.html +35 -14
  46. package/src/client/style.css +29 -5
  47. package/src/client/types.mts +4 -1
  48. package/src/client/ui/shell/space-gib-shell-service.mts +4 -0
  49. package/src/server/path-constants.mts +12 -0
  50. package/src/server/serve-gib/handlers/api/keystone/sso-config.handler.mts +66 -0
  51. package/src/server/serve-gib/handlers/api/keystone/sso-link.handler.mts +119 -0
  52. package/src/server/serve-gib/handlers/api/keystone/sso-login.handler.mts +189 -0
  53. package/src/server/server.mts +6 -0
  54. package/dist/client/chunk-ANGVYAEK.mjs +0 -42
  55. package/dist/client/chunk-ANGVYAEK.mjs.map +0 -7
  56. package/dist/client/chunk-IRGFDQRD.mjs +0 -1920
  57. package/dist/client/chunk-IRGFDQRD.mjs.map +0 -7
  58. package/dist/respec-gib.node.mjs +0 -5
@@ -0,0 +1,821 @@
1
+ import styleCss from "../../style.css";
2
+ import thisCss from "./keystone-details.css";
3
+ import thisHtml from "./keystone-details.html";
4
+
5
+ import { extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
6
+ import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
7
+ import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
8
+ import { getIbAndGib, getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
9
+ import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
10
+ import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
11
+ import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
12
+ import {
13
+ IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
14
+ } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
15
+ import {
16
+ ElementsBase, IbGibDynamicComponentInstance,
17
+ IbGibDynamicComponentInstanceInitOpts,
18
+ } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
19
+ import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_IDENTITY_CHANGED } from "@ibgib/web-gib/dist/ui/ui-constants.mjs";
20
+ import { spaceGibApiBridge } from "../../api/space-gib-api-bridge.mjs";
21
+ import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
22
+ import { toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
23
+
24
+ import { GLOBAL_LOG_A_LOT, APP_CONFIG, } from "../../constants.mjs";
25
+ import { getComponentCtorArg, getIbGibGlobalThis_SpaceGib } from "../../helpers.web.mjs";
26
+ import { devLog } from "../../dev-tools.mjs";
27
+
28
+ const logalot = GLOBAL_LOG_A_LOT || true;
29
+
30
+ export const KEYSTONE_DETAILS_COMPONENT_NAME = 'ibgib-keystone-details';
31
+
32
+ export class KeystoneDetailsComponentMeta extends IbGibDynamicComponentMetaBase {
33
+ protected override lc: string = `[KeystoneDetailsComponentMeta]`;
34
+
35
+ routeRegExp?: RegExp = new RegExp(`^${KEYSTONE_DETAILS_COMPONENT_NAME}$`);
36
+ componentName = KEYSTONE_DETAILS_COMPONENT_NAME;
37
+
38
+ constructor() {
39
+ super(getComponentCtorArg());
40
+ if (!customElements.get(this.componentName)) {
41
+ customElements.define(this.componentName, KeystoneDetailsComponentInstance);
42
+ }
43
+ }
44
+
45
+ async createInstance({
46
+ path,
47
+ ibGibAddr
48
+ }: {
49
+ path: string;
50
+ ibGibAddr: IbGibAddr;
51
+ }): Promise<IbGibDynamicComponentInstance> {
52
+ const lc = `${this.lc}[${this.createInstance.name}]`;
53
+ const component = document.createElement(this.componentName) as KeystoneDetailsComponentInstance;
54
+ await component.initialize({
55
+ ibGibAddr,
56
+ meta: this,
57
+ html: thisHtml,
58
+ css: [styleCss, thisCss],
59
+ });
60
+ return component;
61
+ }
62
+ }
63
+
64
+ interface KeystoneDetailsElements extends ElementsBase {
65
+ identityDetailsViewEl: HTMLDivElement;
66
+ identityAddrEl: HTMLElement;
67
+ identityGenEl: HTMLElement;
68
+ identityTimestampEl: HTMLElement;
69
+ identityUuidEl: HTMLElement;
70
+ identityNameEl: HTMLElement;
71
+ identityDescriptionEl: HTMLElement;
72
+ identityFrameDetailsEl: HTMLPreElement;
73
+ identityAggrDetailsEl: HTMLPreElement;
74
+
75
+ poolsContainerEl: HTMLDivElement;
76
+ btnVerifyChainEl: HTMLButtonElement;
77
+ btnSetActiveEl: HTMLButtonElement;
78
+ verificationStatusEl: HTMLDivElement;
79
+ rawAccordionHeaderEl: HTMLDivElement;
80
+ rawAccordionContentEl: HTMLDivElement;
81
+ rawKeystoneJsonEl: HTMLPreElement;
82
+ delegatesCardEl: HTMLDivElement;
83
+ delegatesListEl: HTMLTableSectionElement;
84
+ historicalWarningBannerEl: HTMLDivElement;
85
+ containerEl: HTMLDivElement;
86
+ ssoLinkCardEl: HTMLDivElement;
87
+ btnLinkGoogleEl: HTMLButtonElement;
88
+ btnLinkGithubEl: HTMLButtonElement;
89
+ ssoStatusMsgEl: HTMLDivElement;
90
+ }
91
+
92
+ export class KeystoneDetailsComponentInstance
93
+ extends IbGibDynamicComponentInstanceBase<KeystoneIbGib_V1, KeystoneDetailsElements>
94
+ implements IbGibDynamicComponentInstance<KeystoneIbGib_V1, KeystoneDetailsElements> {
95
+
96
+ protected override lc: string = `[KeystoneDetailsComponentInstance]`;
97
+
98
+ private staticIbGib: KeystoneIbGib_V1 | undefined;
99
+
100
+ override get ibGib(): KeystoneIbGib_V1 | undefined {
101
+ return this.staticIbGib;
102
+ }
103
+
104
+ private globalActiveAddr: string | null = null;
105
+ private isHistoricalFrame: boolean = false;
106
+ private _onIdentityChanged: ((e: any) => void) | null = null;
107
+ private keystoneCache: Map<string, { username: string, description: string, isPrimary: boolean, n: number }> = new Map();
108
+
109
+ constructor() {
110
+ super();
111
+ }
112
+
113
+ override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise<void> {
114
+ const lc = `${this.lc}[${this.initialize.name}]`;
115
+ try {
116
+ if (logalot) { console.log(`${lc} starting... (I: 4c9328643a18e56b56e57292c87bd226)`); }
117
+
118
+ await super.initialize(opts);
119
+ // await this.loadIbGib({ getLatest: false });
120
+ await this.loadIbGib({ getLatest: false });
121
+
122
+ } catch (error) {
123
+ console.error(`${lc} ${extractErrorMsg(error)}`);
124
+ throw error;
125
+ } finally {
126
+ if (logalot) { console.log(`${lc} complete.`); }
127
+ }
128
+ }
129
+
130
+ private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string, isPrimary: boolean, n: number }> {
131
+ const cached = this.keystoneCache.get(addr);
132
+ if (cached) return cached;
133
+
134
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
135
+ if (!metaspace) return { username: '', description: '', isPrimary: false, n: 0 };
136
+ const space = await metaspace.getLocalUserSpace({ lock: false });
137
+ if (!space) return { username: '', description: '', isPrimary: false, n: 0 };
138
+
139
+ try {
140
+ const keystoneSvc = new KeystoneService_V1();
141
+ const info = await keystoneSvc.getKeystoneCommonInfo({
142
+ addr,
143
+ metaspace,
144
+ space
145
+ });
146
+ if (info) {
147
+ const details = {
148
+ username: info.name,
149
+ description: info.description,
150
+ isPrimary: info.isPrimary,
151
+ n: info.n
152
+ };
153
+ this.keystoneCache.set(addr, details);
154
+ return details;
155
+ }
156
+ } catch (err) {
157
+ console.warn(`Error getting details for keystone ${addr}: ${extractErrorMsg(err)}`);
158
+ }
159
+
160
+ return { username: '', description: '', isPrimary: false, n: 0 };
161
+ }
162
+
163
+ override async created(): Promise<void> {
164
+ const lc = `${this.lc}[${this.created.name}]`;
165
+ try {
166
+ if (logalot) { console.log(`${lc} starting... (I: 0b2c6debcdac9db9b7928aa8695ed826)`); }
167
+
168
+ this.elements = {} as KeystoneDetailsElements;
169
+
170
+ const shadow = this.shadowRoot!;
171
+ this.elements.identityDetailsViewEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
172
+ this.elements.identityAddrEl = shadow.getElementById('identity-addr') as HTMLElement;
173
+ this.elements.identityGenEl = shadow.getElementById('identity-gen') as HTMLSpanElement;
174
+ this.elements.identityTimestampEl = shadow.getElementById('identity-timestamp') as HTMLSpanElement;
175
+ this.elements.identityUuidEl = shadow.getElementById('identity-uuid') as HTMLSpanElement;
176
+ this.elements.identityNameEl = shadow.getElementById('identity-name') as HTMLElement;
177
+ this.elements.identityDescriptionEl = shadow.getElementById('identity-description') as HTMLElement;
178
+ this.elements.identityFrameDetailsEl = shadow.getElementById('identity-frame-details') as HTMLPreElement;
179
+ this.elements.identityAggrDetailsEl = shadow.getElementById('identity-aggr-details') as HTMLPreElement;
180
+
181
+ this.elements.poolsContainerEl = shadow.getElementById('pools-container') as HTMLDivElement;
182
+ this.elements.btnVerifyChainEl = shadow.getElementById('btn-verify-chain') as HTMLButtonElement;
183
+ this.elements.btnSetActiveEl = shadow.getElementById('btn-set-active') as HTMLButtonElement;
184
+ this.elements.verificationStatusEl = shadow.getElementById('verification-status') as HTMLDivElement;
185
+ this.elements.rawAccordionHeaderEl = shadow.getElementById('raw-accordion-header') as HTMLDivElement;
186
+ this.elements.rawAccordionContentEl = shadow.getElementById('raw-accordion-content') as HTMLDivElement;
187
+ this.elements.rawKeystoneJsonEl = shadow.getElementById('raw-keystone-json') as HTMLPreElement;
188
+ this.elements.delegatesCardEl = shadow.getElementById('delegates-card') as HTMLDivElement;
189
+ this.elements.delegatesListEl = shadow.getElementById('delegates-list') as HTMLTableSectionElement;
190
+ this.elements.containerEl = shadow.getElementById('container') as HTMLDivElement;
191
+ this.elements.historicalWarningBannerEl = shadow.getElementById('historical-warning-banner') as HTMLDivElement;
192
+ this.elements.ssoLinkCardEl = shadow.getElementById('sso-link-card') as HTMLDivElement;
193
+ this.elements.btnLinkGoogleEl = shadow.getElementById('btn-link-google') as HTMLButtonElement;
194
+ this.elements.btnLinkGithubEl = shadow.getElementById('btn-link-github') as HTMLButtonElement;
195
+ this.elements.ssoStatusMsgEl = shadow.getElementById('sso-status-msg') as HTMLDivElement;
196
+
197
+ // ElementBase requires contentEl
198
+ this.elements.contentEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
199
+
200
+ this.initHandlers();
201
+
202
+ // Subscribe to global active identity changes
203
+ this._onIdentityChanged = (e: any) => {
204
+ this.globalActiveAddr = e.detail?.activeIdentityAddr || null;
205
+ this.updateActiveButtonState();
206
+ };
207
+ window.addEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
208
+
209
+ await this.resolveActiveIdentity();
210
+ await this.loadIbGib({ getLatest: false });
211
+ } catch (error) {
212
+ console.error(`${lc} ${extractErrorMsg(error)}`);
213
+ throw error;
214
+ } finally {
215
+ if (logalot) { console.log(`${lc} complete.`); }
216
+ }
217
+ }
218
+
219
+ override async disconnected(): Promise<void> {
220
+ if (this._onIdentityChanged) {
221
+ window.removeEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
222
+ }
223
+ }
224
+
225
+ private initHandlers() {
226
+ // Setup raw JSON accordion toggle
227
+ this.elements!.rawAccordionHeaderEl.addEventListener('click', () => this.toggleAccordion());
228
+
229
+ // Buttons
230
+ this.elements!.btnVerifyChainEl.addEventListener('click', () => this.handleVerifyIdentityChain());
231
+ this.elements!.btnSetActiveEl.addEventListener('click', () => this.handleSetActiveIdentity());
232
+ this.elements!.btnLinkGoogleEl.addEventListener('click', () => this.handleSSOLink('google'));
233
+ this.elements!.btnLinkGithubEl.addEventListener('click', () => this.handleSSOLink('github'));
234
+ }
235
+
236
+ private toggleAccordion() {
237
+ const header = this.elements!.rawAccordionHeaderEl;
238
+ const content = this.elements!.rawAccordionContentEl;
239
+ header.classList.toggle('expanded');
240
+ content.classList.toggle('expanded');
241
+ }
242
+
243
+ protected async handleVerifyIdentityChain() {
244
+ const lc = `${this.lc}[${this.handleVerifyIdentityChain.name}]`;
245
+ devLog(`${lc} Verifying identity keystone timeline...`);
246
+ this.setVerificationStatus("Verification request sent...", "info");
247
+ try {
248
+ this.setVerificationStatus("Verification complete. Root Keystone is verified (Genesis phase 1).", "success");
249
+ } catch (error) {
250
+ this.setVerificationStatus(`Verification failed: ${extractErrorMsg(error)}`, "error");
251
+ }
252
+ }
253
+
254
+ protected async handleSetActiveIdentity() {
255
+ if (!this.ibGibAddr) { return; }
256
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
257
+ detail: { activeIdentityAddr: this.ibGibAddr },
258
+ bubbles: true,
259
+ composed: true
260
+ }));
261
+ }
262
+
263
+ private setVerificationStatus(msg: string, type: 'info' | 'success' | 'error') {
264
+ const statusEl = this.elements!.verificationStatusEl;
265
+ statusEl.textContent = msg;
266
+ statusEl.className = `status-msg ${type}`;
267
+ statusEl.classList.remove('hidden');
268
+ }
269
+
270
+ private async resolveActiveIdentity() {
271
+ const lc = `${this.lc}[resolveActiveIdentity]`;
272
+ try {
273
+ const domainIdentity = getIbGibGlobalThis_SpaceGib(APP_CONFIG).identity?.domainIdentity;
274
+ this.globalActiveAddr = domainIdentity ? getIbGibAddr({ ibGib: domainIdentity }) : null;
275
+ } catch (error) {
276
+ console.error(`${lc} ${extractErrorMsg(error)}`);
277
+ this.globalActiveAddr = null;
278
+ }
279
+ }
280
+
281
+ override async loadIbGib(opts?: { getLatest?: boolean }): Promise<void> {
282
+ const lc = `${this.lc}[${this.loadIbGib.name}]`;
283
+ try {
284
+ if (logalot) { console.log(`${lc} starting... (I: f0f7e8d52cbdc37bec844b2875996e26)`); }
285
+
286
+ if (!this.ibGibAddr) { return; }
287
+
288
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
289
+ if (!metaspace) {
290
+ throw new Error(`(UNEXPECTED) metaspace falsy? (E: a9864dbbaefc1082ce7bd228ebd67c26)`);
291
+ }
292
+ let space = await metaspace.getLocalUserSpace({ lock: false });
293
+ if (!space) {
294
+ throw new Error(`(UNEXPECTED) space falsy and we couldn't get default local user space from metaspace? (E: fa298edcecbda889cfd7b228fbd57c26)`);
295
+ }
296
+
297
+ const resGet = await metaspace.get({ addrs: [this.ibGibAddr], space });
298
+
299
+ if (resGet.success && resGet.ibGibs && resGet.ibGibs.length === 1) {
300
+ this.staticIbGib = resGet.ibGibs[0] as KeystoneIbGib_V1;
301
+ } else {
302
+ const resIbGib = resGet.rawResultIbGib as any;
303
+ const addrsNotFound = resIbGib?.data?.addrsNotFound ?? 'unknown';
304
+ throw new Error(`couldn't find address ${this.ibGibAddr}. addrsNotFound: ${addrsNotFound}? resGet.errorMsg: ${resGet.errorMsg} (E: e0e5c9b7de2837bc902fa88390cd9f26)`);
305
+ }
306
+
307
+ await this.updateFrameDetailsView();
308
+ } catch (error) {
309
+ console.error(`${lc} ${extractErrorMsg(error)}`);
310
+ throw error;
311
+ } finally {
312
+ if (logalot) { console.log(`${lc} complete.`); }
313
+ }
314
+ }
315
+ protected override async renderUI(): Promise<void> {
316
+ const lc = `${this.lc}[${this.renderUI.name}]`;
317
+ try {
318
+ if (logalot) { console.log(`${lc} starting... (I: c8aa086352d8079708e00f78b6ac0826)`); }
319
+ await super.renderUI();
320
+ // await this.updateFrameDetailsView();
321
+ } catch (error) {
322
+ console.error(`${lc} ${extractErrorMsg(error)}`);
323
+ throw error;
324
+ } finally {
325
+ if (logalot) { console.log(`${lc} complete.`); }
326
+ }
327
+ }
328
+
329
+ private updateActiveButtonState() {
330
+ if (!this.elements) { return; }
331
+
332
+ const tjpGib = getGibInfo({ ibGibAddr: this.ibGibAddr }).tjpGib ?? getIbAndGib({ ibGibAddr: this.ibGibAddr }).gib;
333
+ const activeTjpGib = this.globalActiveAddr
334
+ ? (getGibInfo({ ibGibAddr: this.globalActiveAddr }).tjpGib ?? getIbAndGib({ ibGibAddr: this.globalActiveAddr }).gib)
335
+ : null;
336
+
337
+ const isTimelineActive = !!(this.globalActiveAddr && tjpGib === activeTjpGib);
338
+
339
+ if (isTimelineActive) {
340
+ this.elements.btnSetActiveEl.textContent = "Active Identity";
341
+ this.elements.btnSetActiveEl.classList.add('active-primary');
342
+ } else {
343
+ this.elements.btnSetActiveEl.textContent = "Set as Active Identity";
344
+ this.elements.btnSetActiveEl.classList.remove('active-primary');
345
+ }
346
+
347
+ // Disable buttons if historical
348
+ if (this.isHistoricalFrame) {
349
+ this.elements.btnSetActiveEl.disabled = true;
350
+ this.elements.btnVerifyChainEl.disabled = true;
351
+ } else {
352
+ this.elements.btnVerifyChainEl.disabled = false;
353
+ this.elements.btnSetActiveEl.disabled = isTimelineActive;
354
+ }
355
+ }
356
+
357
+ private async updateFrameDetailsView(): Promise<void> {
358
+ const lc = `${this.lc}[${this.updateFrameDetailsView.name}]`;
359
+ try {
360
+ if (logalot) { console.log(`${lc} starting... (I: 1fa9e66d2e78fb100874573e2db71d26)`); }
361
+
362
+ if (!this.elements) { return; }
363
+
364
+ const keystone = this.ibGib;
365
+ if (!keystone) {
366
+ this.clearActiveDetails();
367
+ return;
368
+ }
369
+ const keystoneAddr = this.ibGibAddr;
370
+
371
+ // Update Verifiable Address
372
+ this.elements.identityAddrEl.textContent = keystoneAddr;
373
+
374
+ // Update Generation
375
+ this.elements.identityGenEl.textContent = keystone.data?.n !== undefined ? String(keystone.data.n) : '0 (Genesis)';
376
+
377
+ // Update Timestamp
378
+ const timestampStr = keystone.data?.timestamp;
379
+ let formattedTimestamp = 'N/A';
380
+ if (timestampStr) {
381
+ const parsed = parseInt(timestampStr);
382
+ if (!isNaN(parsed)) {
383
+ formattedTimestamp = new Date(parsed).toLocaleString();
384
+ } else {
385
+ formattedTimestamp = new Date(timestampStr).toLocaleString();
386
+ }
387
+ }
388
+ this.elements.identityTimestampEl.textContent = formattedTimestamp;
389
+
390
+ // Update UUID
391
+ this.elements.identityUuidEl.textContent = keystone.data?.uuid || 'N/A';
392
+
393
+ // Show Frame Details
394
+ const frameDetails = keystone.data?.frameDetails;
395
+ this.elements.identityFrameDetailsEl.textContent = frameDetails ? pretty(frameDetails) : '{}';
396
+
397
+ // Get and Show Aggregated details up to the scrolled-to frame
398
+ let isHistorical = false;
399
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
400
+ if (metaspace) {
401
+ const space = await metaspace.getLocalUserSpace({ lock: false });
402
+ if (space) {
403
+ try {
404
+ const latestAddr = await metaspace.getLatestAddr({ addr: this.ibGibAddr, space });
405
+ if (latestAddr && latestAddr !== this.ibGibAddr) {
406
+ isHistorical = true;
407
+ }
408
+ } catch (err) {
409
+ console.warn(`${lc} Error checking latest address: ${extractErrorMsg(err)}`);
410
+ }
411
+
412
+ try {
413
+ const keystoneSvc = new KeystoneService_V1();
414
+ const aggregated = await keystoneSvc.getAggregateDetails({
415
+ latestKeystone: keystone,
416
+ metaspace,
417
+ space
418
+ });
419
+
420
+ this.elements.identityAggrDetailsEl.textContent = aggregated ? pretty(aggregated) : '{}';
421
+
422
+ // Update Username and Description fields
423
+ this.elements.identityNameEl.textContent = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '-';
424
+ this.elements.identityDescriptionEl.textContent = aggregated?.description || aggregated?.profile?.description || '-';
425
+ } catch (err) {
426
+ console.warn(`${lc} Error aggregating details: ${extractErrorMsg(err)}`);
427
+ this.elements.identityAggrDetailsEl.textContent = '{}';
428
+ this.elements.identityNameEl.textContent = '-';
429
+ this.elements.identityDescriptionEl.textContent = '-';
430
+ }
431
+ } else {
432
+ this.elements.identityAggrDetailsEl.textContent = '{}';
433
+ this.elements.identityNameEl.textContent = '-';
434
+ this.elements.identityDescriptionEl.textContent = '-';
435
+ }
436
+ } else {
437
+ this.elements.identityAggrDetailsEl.textContent = '{}';
438
+ this.elements.identityNameEl.textContent = '-';
439
+ this.elements.identityDescriptionEl.textContent = '-';
440
+ }
441
+
442
+ // Update historical warning state
443
+ this.isHistoricalFrame = isHistorical;
444
+ const banner = this.elements.historicalWarningBannerEl;
445
+ const container = this.elements.containerEl;
446
+ if (banner && container) {
447
+ if (isHistorical) {
448
+ banner.classList.remove('hidden');
449
+ container.classList.add('is-historical');
450
+ } else {
451
+ banner.classList.add('hidden');
452
+ container.classList.remove('is-historical');
453
+ }
454
+ }
455
+
456
+ // Render Raw Timeline JSON
457
+ this.elements.rawKeystoneJsonEl.textContent = pretty(keystone);
458
+
459
+ // Render Challenge Pools
460
+ this.renderChallengePools(keystone.data?.challengePools || []);
461
+
462
+ // Render Delegates & SSO link card (only if this is a primary keystone, hide delegates table & SSO card for delegate/session keystones)
463
+ const details = await this.getKeystoneDetails(this.ibGibAddr);
464
+ if (details.isPrimary) {
465
+ this.elements.delegatesCardEl.classList.remove('hidden');
466
+ await this.renderDelegates(keystone.data?.delegates);
467
+ this.elements.ssoLinkCardEl.classList.remove('hidden');
468
+ } else {
469
+ this.elements.delegatesCardEl.classList.add('hidden');
470
+ this.elements.ssoLinkCardEl.classList.add('hidden');
471
+ }
472
+
473
+ this.updateActiveButtonState();
474
+ } catch (error) {
475
+ console.error(`${lc} ${extractErrorMsg(error)}`);
476
+ throw error;
477
+ } finally {
478
+ if (logalot) { console.log(`${lc} complete.`); }
479
+ }
480
+ }
481
+
482
+ private clearActiveDetails() {
483
+ if (!this.elements) { return; }
484
+ this.isHistoricalFrame = false;
485
+ if (this.elements.historicalWarningBannerEl) {
486
+ this.elements.historicalWarningBannerEl.classList.add('hidden');
487
+ }
488
+ if (this.elements.containerEl) {
489
+ this.elements.containerEl.classList.remove('is-historical');
490
+ }
491
+ this.elements.identityGenEl.textContent = '-';
492
+ this.elements.identityTimestampEl.textContent = '-';
493
+ this.elements.identityUuidEl.textContent = '-';
494
+ this.elements.identityNameEl.textContent = '-';
495
+ this.elements.identityDescriptionEl.textContent = '-';
496
+ this.elements.identityFrameDetailsEl.textContent = '{}';
497
+ this.elements.identityAggrDetailsEl.textContent = '{}';
498
+ this.elements.poolsContainerEl.innerHTML = '';
499
+ if (this.elements.delegatesListEl) {
500
+ this.elements.delegatesListEl.innerHTML = '';
501
+ }
502
+ }
503
+
504
+ private renderChallengePools(pools: any[]) {
505
+ if (!this.elements) { return; }
506
+ const container = this.elements.poolsContainerEl;
507
+ container.innerHTML = '';
508
+
509
+ if (!pools || pools.length === 0) {
510
+ container.innerHTML = `<p class="description">No challenge pools found in this keystone.</p>`;
511
+ return;
512
+ }
513
+
514
+ for (const pool of pools) {
515
+ const card = document.createElement('div');
516
+ card.className = 'pool-card';
517
+
518
+ const config = pool.config || {};
519
+ const behavior = config.behavior || {};
520
+ const allowedVerbs = config.allowedVerbs || [];
521
+ const activeCount = pool.challenges ? Object.keys(pool.challenges).length : 0;
522
+ const isForeign = !!pool.isForeign;
523
+
524
+ // Generate HTML for the challenges list
525
+ const challenges = pool.challenges || {};
526
+ const challengeListHtml = Object.entries(challenges).map(([id, challenge]: [string, any]) => {
527
+ const truncatedHash = challenge.hash && challenge.hash.length > 20
528
+ ? challenge.hash.substring(0, 10) + '...' + challenge.hash.substring(challenge.hash.length - 8)
529
+ : challenge.hash || 'N/A';
530
+ return `
531
+ <div class="pool-challenge-item">
532
+ <span class="challenge-id">${id}</span>
533
+ <span class="challenge-hash" title="${challenge.hash || ''}">${truncatedHash}</span>
534
+ </div>
535
+ `;
536
+ }).join('');
537
+
538
+ card.innerHTML = `
539
+ <div class="pool-header">
540
+ <span class="pool-title">${pool.id || 'Unnamed Pool'}</span>
541
+ <span class="pool-tag ${isForeign ? 'foreign' : 'native'}">${isForeign ? 'Foreign' : 'Native'}</span>
542
+ </div>
543
+ <div class="pool-grid">
544
+ <div class="pool-field">
545
+ <label>Verbs Allowed</label>
546
+ <span>${allowedVerbs.length > 0 ? allowedVerbs.join(', ') : 'Any'}</span>
547
+ </div>
548
+ <div class="pool-field">
549
+ <label>FIFO / Random cost</label>
550
+ <span>FIFO: ${behavior.selectSequentially ?? 0}, Rand: ${behavior.selectRandomly ?? 0}</span>
551
+ </div>
552
+ </div>
553
+
554
+ <!-- Collapsible Challenges List -->
555
+ <details class="pool-details">
556
+ <summary class="pool-details-summary">Challenges (${activeCount})</summary>
557
+ <div class="pool-challenges-list">
558
+ ${challengeListHtml || '<p class="placeholder-text" style="padding: 0.5rem; text-align: center;">No challenges active.</p>'}
559
+ </div>
560
+ </details>
561
+ `;
562
+ container.appendChild(card);
563
+ }
564
+ }
565
+
566
+ private async renderDelegates(delegates: Record<string, any> | undefined) {
567
+ if (!this.elements) { return; }
568
+ const listEl = this.elements.delegatesListEl;
569
+ listEl.innerHTML = '';
570
+
571
+ if (!delegates || Object.keys(delegates).length === 0) {
572
+ listEl.innerHTML = `
573
+ <tr>
574
+ <td colspan="4" style="text-align: center; color: var(--clr-text-secondary, #b3b3b3);">
575
+ No registered delegates.
576
+ </td>
577
+ </tr>
578
+ `;
579
+ return;
580
+ }
581
+
582
+ for (const [delegateTjpAddr, info] of Object.entries(delegates)) {
583
+ const tr = document.createElement('tr');
584
+
585
+ const tdName = document.createElement('td');
586
+ tdName.textContent = 'Loading...';
587
+
588
+ const tdTjp = document.createElement('td');
589
+ tdTjp.textContent = delegateTjpAddr;
590
+ tdTjp.title = delegateTjpAddr;
591
+
592
+ const tdVerbs = document.createElement('td');
593
+ tdVerbs.textContent = info.allowedVerbs ? info.allowedVerbs.join(', ') : '-';
594
+ tdVerbs.title = tdVerbs.textContent;
595
+
596
+ const tdActions = document.createElement('td');
597
+ const btnView = document.createElement('button');
598
+ btnView.className = 'action-btn secondary small';
599
+ btnView.textContent = 'View Details';
600
+ btnView.addEventListener('click', () => {
601
+ if (info.delegateAddr) {
602
+ this.dispatchEvent(new CustomEvent('ibgib-view-keystone-details', {
603
+ detail: { addr: info.delegateAddr },
604
+ bubbles: true,
605
+ composed: true
606
+ }));
607
+ }
608
+ });
609
+ tdActions.appendChild(btnView);
610
+
611
+ tr.appendChild(tdName);
612
+ tr.appendChild(tdTjp);
613
+ tr.appendChild(tdVerbs);
614
+ tr.appendChild(tdActions);
615
+ listEl.appendChild(tr);
616
+
617
+ if (info.delegateAddr) {
618
+ this.getKeystoneDetails(info.delegateAddr).then(details => {
619
+ const textParts: string[] = [];
620
+ if (details.username) textParts.push(details.username);
621
+ if (details.description) textParts.push(details.description);
622
+ tdName.textContent = textParts.join(' / ') || 'Unnamed Delegate';
623
+ tdName.title = `Addr: ${info.delegateAddr}\n` + textParts.join('\n');
624
+ }).catch(err => {
625
+ tdName.textContent = 'Unknown Delegate';
626
+ });
627
+ } else {
628
+ tdName.textContent = 'No Address';
629
+ }
630
+ }
631
+ }
632
+
633
+ override async handleContextUpdated(): Promise<void> {
634
+ const lc = `${this.lc}[${this.handleContextUpdated.name}]`;
635
+ try {
636
+ if (logalot) { console.log(`${lc} starting... (I: 243a88c1d4689c779bbbf2a4abd5da26)`); }
637
+
638
+ // don't do anything when the context is updated?
639
+
640
+ // is this hit? no. leave this as a no-op
641
+ console.warn(`${lc} this component currently designed expecting that this does not hit (W: 95dc018b87027287e860de0826146826)`);
642
+
643
+ await super.handleContextUpdated();
644
+ } catch (error) {
645
+ console.error(`${lc} ${extractErrorMsg(error)}`);
646
+ throw error;
647
+ } finally {
648
+ if (logalot) { console.log(`${lc} complete.`); }
649
+ }
650
+ }
651
+
652
+ protected override async handleNewContextChild({ childIbGib }: { childIbGib: IbGib_V1; }): Promise<void> {
653
+ const lc = `${this.lc}[${this.handleNewContextChild.name}]`;
654
+ try {
655
+ if (logalot) { console.log(`${lc} starting... (I: b04dc63842f871658840233485659726)`); }
656
+ // is this hit? no
657
+ await super.handleNewContextChild({ childIbGib });
658
+
659
+ console.warn(`${lc} this component currently designed expecting that this does not hit (W: 95dc018b87027287e860de0826146826)`);
660
+ } catch (error) {
661
+ console.error(`${lc} ${extractErrorMsg(error)}`);
662
+ throw error;
663
+ } finally {
664
+ if (logalot) { console.log(`${lc} complete.`); }
665
+ }
666
+ }
667
+
668
+ protected async handleSSOLink(providerId: 'google' | 'github') {
669
+ const lc = `${this.lc}[${this.handleSSOLink.name}]`;
670
+ this.setSSOStatus(`Linking ${providerId}...`, "info");
671
+
672
+ try {
673
+ // 1. Fetch public SSO configs from server
674
+ const res = await fetch('/api/identity/sso/config');
675
+ if (!res.ok) {
676
+ throw new Error(`Failed to fetch SSO config from server: ${res.statusText}`);
677
+ }
678
+ const data = await res.json();
679
+ if (!data.success || !data.providers) {
680
+ throw new Error(`SSO configuration fetch failed: ${data.message || 'unknown error'}`);
681
+ }
682
+
683
+ const providerConfig = data.providers.find((p: any) => p.providerId === providerId);
684
+ if (!providerConfig) {
685
+ throw new Error(`SSO provider '${providerId}' is not configured on the server.`);
686
+ }
687
+
688
+ // 2. Generate a secure, high-entropy user nonce
689
+ const userNonce = (Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2)).substring(0, 32);
690
+
691
+ // 3. Construct state payload
692
+ const stateObj = {
693
+ providerId,
694
+ parentTjpAddr: this.ibGibAddr,
695
+ userNonce
696
+ };
697
+ const state = btoa(JSON.stringify(stateObj));
698
+
699
+ // 4. Construct Auth URL
700
+ const authUrl = `${providerConfig.authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
701
+
702
+ // 5. Open popup window
703
+ const popup = window.open(
704
+ authUrl,
705
+ 'sso-oauth-popup',
706
+ 'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
707
+ );
708
+ if (!popup) {
709
+ throw new Error("Popup blocked by browser. Please enable popups for this site.");
710
+ }
711
+
712
+ // 6. Listen for callback from pop-up
713
+ const messageListener = async (event: MessageEvent) => {
714
+ if (event.origin !== window.location.origin) return;
715
+ const callbackData = event.data;
716
+ if (callbackData && callbackData.type === 'sso-oauth-callback') {
717
+ window.removeEventListener('message', messageListener);
718
+ await this.handleSSOLinkCallback(callbackData.code, stateObj, providerConfig.redirectUri);
719
+ }
720
+ };
721
+ window.addEventListener('message', messageListener);
722
+
723
+ } catch (error) {
724
+ console.error(`${lc} SSO link initiation failed: ${extractErrorMsg(error)}`);
725
+ this.setSSOStatus(`Link failed: ${extractErrorMsg(error)}`, "error");
726
+ }
727
+ }
728
+
729
+ private setSSOStatus(msg: string, type: 'info' | 'success' | 'error') {
730
+ const statusEl = this.elements!.ssoStatusMsgEl;
731
+ statusEl.textContent = msg;
732
+ statusEl.className = `status-msg ${type}`;
733
+ statusEl.classList.remove('hidden');
734
+ }
735
+
736
+ protected async handleSSOLinkCallback(code: string, stateObj: any, redirectUri: string) {
737
+ const lc = `${this.lc}[${this.handleSSOLinkCallback.name}]`;
738
+ this.setSSOStatus("Retrieving challenge pool configuration from server...", "info");
739
+
740
+ try {
741
+ // 1. Exchange OAuth code for custodian public pool config
742
+ const res = await fetch('/api/identity/sso/link', {
743
+ method: 'POST',
744
+ headers: { 'Content-Type': 'application/json' },
745
+ body: JSON.stringify({
746
+ code,
747
+ providerId: stateObj.providerId,
748
+ parentTjpAddr: stateObj.parentTjpAddr,
749
+ userNonce: stateObj.userNonce,
750
+ redirectUri
751
+ })
752
+ });
753
+ if (!res.ok) {
754
+ const errBody = await res.json().catch(() => ({}));
755
+ throw new Error(`Server link exchange failed: ${errBody.message || res.statusText}`);
756
+ }
757
+
758
+ const linkData = await res.json();
759
+ if (!linkData.success || !linkData.custodianPool) {
760
+ throw new Error(`Failed to retrieve custodian challenge pool: ${linkData.message || 'unknown error'}`);
761
+ }
762
+
763
+ const custodianPool = linkData.custodianPool;
764
+
765
+ // 2. Prompt user for master secret to evolve locally
766
+ const masterSecret = prompt("Link successful! Enter your local Master Secret (Passphrase) to sign and register the custodian pool:");
767
+ if (!masterSecret) {
768
+ this.setSSOStatus("Link cancelled by user.", "info");
769
+ return;
770
+ }
771
+
772
+ this.setSSOStatus("Evolving identity locally and generating cryptographic signatures...", "info");
773
+
774
+ const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
775
+ if (!metaspace) throw new Error("Metaspace not found");
776
+ const space = await metaspace.getLocalUserSpace({ lock: false });
777
+ if (!space) throw new Error("Local space not found");
778
+
779
+ // 3. Evolve parent keystone to include the custodian pool
780
+ const keystoneService = new KeystoneService_V1();
781
+ const evolvedParent = await keystoneService.addPools({
782
+ latestKeystone: this.ibGib!,
783
+ masterSecret,
784
+ newPools: [custodianPool],
785
+ metaspace,
786
+ space
787
+ });
788
+
789
+ // 4. Sync evolved parent keystone back to server
790
+ this.setSSOStatus("Publishing evolved identity keystone to server...", "info");
791
+ const syncRes = await spaceGibApiBridge.putEvolveKeystone(
792
+ stateObj.parentTjpAddr,
793
+ evolvedParent,
794
+ [] // No outward delegate timelines
795
+ );
796
+ if (!syncRes.success) {
797
+ throw new Error(`Server synchronization rejected: ${syncRes.message}`);
798
+ }
799
+
800
+ // 5. Update index locally
801
+ await updateSpecialIndex({
802
+ type: "keystones",
803
+ rel8nInfos: [
804
+ {
805
+ rel8nName: "keystone",
806
+ ibGibs: [toDto({ ibGib: evolvedParent })]
807
+ }
808
+ ],
809
+ metaspace,
810
+ space
811
+ });
812
+
813
+ this.setSSOStatus(`Successfully linked with ${stateObj.providerId}!`, "success");
814
+ await this.loadIbGib({ getLatest: false });
815
+
816
+ } catch (error) {
817
+ console.error(`${lc} SSO callback processing failed: ${extractErrorMsg(error)}`);
818
+ this.setSSOStatus(`Link failed: ${extractErrorMsg(error)}`, "error");
819
+ }
820
+ }
821
+ }