@ibgib/web-gib 0.0.56 → 0.0.58

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 (65) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/api/api-index.mjs +1 -1
  4. package/dist/api/api-index.mjs.map +1 -1
  5. package/dist/api/ibgib-api-bridge.d.mts +97 -0
  6. package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
  7. package/dist/api/ibgib-api-bridge.mjs +205 -3
  8. package/dist/api/ibgib-api-bridge.mjs.map +1 -1
  9. package/dist/identity/custodian-delegate-helper.d.mts +22 -0
  10. package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
  11. package/dist/identity/custodian-delegate-helper.mjs +50 -0
  12. package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
  13. package/dist/identity/sso-popup-helper.d.mts +33 -0
  14. package/dist/identity/sso-popup-helper.d.mts.map +1 -0
  15. package/dist/identity/sso-popup-helper.mjs +124 -0
  16. package/dist/identity/sso-popup-helper.mjs.map +1 -0
  17. package/dist/index.d.mts +2 -0
  18. package/dist/index.d.mts.map +1 -1
  19. package/dist/index.mjs +3 -0
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  22. package/dist/ui/component/identity/add-device/add-device.html +6 -7
  23. package/dist/ui/component/identity/add-device/add-device.mjs +13 -10
  24. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  25. package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
  26. package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
  27. package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
  28. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +114 -9
  29. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +23 -0
  30. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  31. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -50
  32. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +529 -83
  33. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  34. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  35. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +19 -61
  36. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  37. package/package.json +4 -4
  38. package/src/AUTO-GENERATED-version.mts +1 -1
  39. package/src/api/api-index.mts +1 -1
  40. package/src/api/ibgib-api-bridge.mts +288 -4
  41. package/src/identity/custodian-delegate-helper.mts +61 -0
  42. package/src/identity/sso/{sso-custodian-service.mts → sso-custodian-service.mts.bak} +65 -38
  43. package/src/identity/sso/{sso-custodian-service.respec.mts → sso-custodian-service.respec.mts.bak} +15 -14
  44. package/src/identity/sso-popup-helper.mts +155 -0
  45. package/src/index.mts +3 -0
  46. package/src/ui/component/identity/add-device/add-device.html +6 -7
  47. package/src/ui/component/identity/add-device/add-device.mts +17 -16
  48. package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
  49. package/src/ui/component/identity/keystone-creator/keystone-creator.css +114 -9
  50. package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -50
  51. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +599 -100
  52. package/src/ui/component/identity/keystone-details/keystone-details.mts +26 -77
  53. package/dist/identity/sso/sso-config-helper.d.mts +0 -7
  54. package/dist/identity/sso/sso-config-helper.d.mts.map +0 -1
  55. package/dist/identity/sso/sso-config-helper.mjs +0 -42
  56. package/dist/identity/sso/sso-config-helper.mjs.map +0 -1
  57. package/dist/identity/sso/sso-custodian-service.d.mts +0 -139
  58. package/dist/identity/sso/sso-custodian-service.d.mts.map +0 -1
  59. package/dist/identity/sso/sso-custodian-service.mjs +0 -443
  60. package/dist/identity/sso/sso-custodian-service.mjs.map +0 -1
  61. package/dist/identity/sso/sso-custodian-service.respec.d.mts +0 -2
  62. package/dist/identity/sso/sso-custodian-service.respec.d.mts.map +0 -1
  63. package/dist/identity/sso/sso-custodian-service.respec.mjs +0 -254
  64. package/dist/identity/sso/sso-custodian-service.respec.mjs.map +0 -1
  65. /package/src/identity/sso/{sso-config-helper.mts → sso-config-helper.mts.bak} +0 -0
@@ -2,39 +2,32 @@ import thisCss from "./keystone-creator.css";
2
2
  import thisHtml from "./keystone-creator.html";
3
3
  import commonComponentStylesCss from "../../common-component-styles.css";
4
4
 
5
- import { delay, extractErrorMsg, hash } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
6
- import { HashAlgorithm } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
5
+ import { delay, extractErrorMsg, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
7
6
  import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
8
7
  import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
9
8
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
9
+ import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
10
10
  import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
11
- import {
12
- KeystonePoolConfig, KeystoneChallengeType, KeystoneReplenishStrategy
13
- } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
14
11
  import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
15
- import { getGlobalMetaspace_waitIfNeeded, getLocalCoupledIbGibForDomainIbGib } from "../../../../helpers.mjs";
16
- import {
17
- IbGibDynamicComponentMetaBase, IbGibFormInstanceBase,
18
- } from "../../ibgib-dynamic-component-bases.mjs";
12
+ import { updateSpecialIndex, } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
13
+ import { toDto, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
14
+ import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
15
+ import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
16
+ import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
17
+ import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
18
+
19
+ import { getGlobalMetaspace_waitIfNeeded, } from "../../../../helpers.mjs";
20
+ import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
19
21
  import {
20
22
  ElementsBase, IbGibDynamicComponentInstance,
21
23
  IbGibDynamicComponentInstanceInitOpts,
22
24
  } from "../../component-types.mjs";
23
-
24
25
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
25
26
  import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
26
-
27
- import {
28
- createSettings, getSectionName, getSettingsScope
29
- } from "../../../../common/settings/settings-helpers.mjs";
30
- import { SettingsType } from "../../../../common/settings/settings-constants.mjs";
31
- import { SettingsIbGib_V1 } from "../../../../common/settings/settings-types.mjs";
32
- import { updateSpecialIndex, mut8Timeline } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
33
- import { toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
34
- import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
35
- import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_DESCRIPTION_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
27
+ import { deriveLocalCustodianDelegateSecret } from "../../../../identity/custodian-delegate-helper.mjs";
28
+ import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
36
29
  import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
37
- import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
30
+ import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
38
31
 
39
32
  export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
40
33
 
@@ -174,6 +167,59 @@ export class KeystoneCreatorComponentInstance
174
167
  }
175
168
 
176
169
  private initHandlers() {
170
+ // Mode Tab Switching (Quick vs Password-based)
171
+ const tabQuick = this.shadowRoot?.getElementById('tab-quick') as HTMLButtonElement | null;
172
+ const tabPassword = this.shadowRoot?.getElementById('tab-password') as HTMLButtonElement | null;
173
+ const panelQuick = this.shadowRoot?.getElementById('panel-quick') as HTMLDivElement | null;
174
+ const panelPassword = this.shadowRoot?.getElementById('panel-password') as HTMLDivElement | null;
175
+ const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick') as HTMLParagraphElement | null;
176
+ const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password') as HTMLParagraphElement | null;
177
+
178
+ tabQuick?.addEventListener('click', () => {
179
+ tabQuick.classList.add('active');
180
+ tabPassword?.classList.remove('active');
181
+ panelQuick?.classList.remove('hidden');
182
+ panelPassword?.classList.add('hidden');
183
+ subtitleQuick?.classList.remove('hidden');
184
+ subtitlePassword?.classList.add('hidden');
185
+ });
186
+
187
+ tabPassword?.addEventListener('click', () => {
188
+ tabPassword.classList.add('active');
189
+ tabQuick?.classList.remove('active');
190
+ panelPassword?.classList.remove('hidden');
191
+ panelQuick?.classList.add('hidden');
192
+ subtitlePassword?.classList.remove('hidden');
193
+ subtitleQuick?.classList.add('hidden');
194
+ });
195
+
196
+ const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso') as HTMLButtonElement | null;
197
+ btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
198
+
199
+ const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso') as HTMLButtonElement | null;
200
+ btnGithubSso?.addEventListener('click', () => this.handleSsoClick('github'));
201
+
202
+ // Email Sign-In Info Button
203
+ const btnEmailInfo = this.shadowRoot?.getElementById('btn-email-info') as HTMLButtonElement | null;
204
+ btnEmailInfo?.addEventListener('click', (e) => {
205
+ e.preventDefault();
206
+ alertUser({
207
+ title: 'Custom Email Sign-In',
208
+ msg: 'When you enter your email address, we dispatch a 6-digit one-time verification code to your inbox.\n\nEntering the code creates your cryptographic identity delegate on your device and signs you in seamlessly without needing a password!'
209
+ });
210
+ });
211
+
212
+ // Quick Email Verification Code Handlers
213
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code') as HTMLButtonElement | null;
214
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
215
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
216
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
217
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
218
+
219
+ btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
220
+
221
+ btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
222
+
177
223
  this.elements!.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
178
224
 
179
225
  this.shadowRoot!.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
@@ -186,16 +232,16 @@ export class KeystoneCreatorComponentInstance
186
232
  });
187
233
 
188
234
  // Clear invalid styling when user inputs text
189
- this.elements!.inputUsername.addEventListener('input', () => {
235
+ this.elements!.inputUsername?.addEventListener('input', () => {
190
236
  this.elements!.inputUsername.classList.remove('invalid');
191
237
  });
192
- this.elements!.inputDescription.addEventListener('input', () => {
238
+ this.elements!.inputDescription?.addEventListener('input', () => {
193
239
  this.elements!.inputDescription.classList.remove('invalid');
194
240
  });
195
- this.elements!.inputEmail.addEventListener('input', () => {
241
+ this.elements!.inputEmail?.addEventListener('input', () => {
196
242
  this.elements!.inputEmail.classList.remove('invalid');
197
243
  });
198
- this.elements!.inputSecret.addEventListener('input', () => {
244
+ this.elements!.inputSecret?.addEventListener('input', () => {
199
245
  this.elements!.inputSecret.classList.remove('invalid');
200
246
  });
201
247
  }
@@ -208,6 +254,245 @@ export class KeystoneCreatorComponentInstance
208
254
  }
209
255
  }
210
256
 
257
+ /**
258
+ * Triggered by Google / GitHub SSO buttons.
259
+ * Opens OAuth popup, exchanges authorization code, builds local delegate keystone,
260
+ * registers delegate on server, and finalizes onboarding.
261
+ */
262
+ protected async handleSsoClick(providerId: 'google' | 'github'): Promise<void> {
263
+ const lc = `${this.lc}[${this.handleSsoClick.name}]`;
264
+ this.setStatus(`Opening ${providerId.toUpperCase()} SSO authorization window...`, 'info');
265
+ const resPopup = await executeSsoOAuthPopup({ providerId });
266
+ if (!resPopup.success || !resPopup.code) {
267
+ this.setStatus(resPopup.message || `${providerId.toUpperCase()} sign-in cancelled.`, 'error');
268
+ return;
269
+ }
270
+
271
+ this.setStatus(`Exchanging ${providerId.toUpperCase()} authorization code...`, 'info');
272
+ const apiBridge = getApiBridge();
273
+ const resLogin = await apiBridge.postSsoLogin({
274
+ code: resPopup.code,
275
+ providerId,
276
+ redirectUri: resPopup.redirectUri!
277
+ });
278
+
279
+ if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
280
+ this.setStatus(resLogin.message || `${providerId.toUpperCase()} login failed.`, 'error');
281
+ return;
282
+ }
283
+
284
+ const { primaryTjpAddr, custodianReqs } = resLogin;
285
+ this.setStatus('Deriving local device delegate keys...', 'info');
286
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
287
+ const email = resLogin.userInfo?.email || '';
288
+ const username = email ? email.split('@')[0] : 'user';
289
+
290
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
291
+ .withUsername(`${username}-sync`)
292
+ .withDescription(`Sync Delegate for ${username}`)
293
+ .withDetails({
294
+ client: 'space-gib-web',
295
+ role: 'sync-delegate',
296
+ primaryTjpAddr,
297
+ custodianReqs,
298
+ });
299
+
300
+ const configs = await delegateBuilder.compileConfigs();
301
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
302
+ const space = await metaspace.getLocalUserSpace({ lock: false });
303
+ if (!space) {
304
+ this.setStatus('Could not access local user space.', 'error');
305
+ return;
306
+ }
307
+
308
+ const keystoneService = new KeystoneService_V1();
309
+ const delegateKeystone = await keystoneService.genesis({
310
+ masterSecret: delegateSecret,
311
+ configs,
312
+ metaspace,
313
+ space,
314
+ frameDetails: delegateBuilder.getFrameDetails(),
315
+ isPrimary: false
316
+ });
317
+
318
+ this.setStatus('Registering device delegate with custodian server...', 'info');
319
+ const resRegister = await apiBridge.postCustodianRegisterDelegate({ delegateKeystone });
320
+
321
+ if (!resRegister.success) {
322
+ this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
323
+ return;
324
+ }
325
+
326
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
327
+ await this.finalizeIdentityOnboarding({
328
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resLogin.primaryKeystone,
329
+ primaryGraph: resRegister.primaryGraph || resLogin.primaryGraph,
330
+ delegateKeystone,
331
+ primaryTjpAddr,
332
+ delegateSecret,
333
+ makeActive: true
334
+ });
335
+ }
336
+
337
+ /**
338
+ * Triggered by the "Send 6-Digit Verification Code" button.
339
+ * Validates quick email input and dispatches a 6-digit OTP code to the user's inbox.
340
+ */
341
+ protected async handleQuickSendCode(): Promise<void> {
342
+ const lc = `${this.lc}[${this.handleQuickSendCode.name}]`;
343
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code') as HTMLButtonElement | null;
344
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
345
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp') as HTMLDivElement | null;
346
+
347
+ const email = (inputQuickEmail?.value || '').trim();
348
+ if (!email || !validateEmail(email)) {
349
+ this.setStatus("Please enter a valid email address.", "error");
350
+ return;
351
+ }
352
+
353
+ if (btnQuickSendCode) {
354
+ btnQuickSendCode.disabled = true;
355
+ btnQuickSendCode.textContent = "Sending Verification Code...";
356
+ }
357
+ this.setStatus("Requesting verification code...", "info");
358
+
359
+ try {
360
+ const resCode = await getApiBridge().postVerificationCode({ email });
361
+ if (resCode.success) {
362
+ secQuickOtp?.classList.remove('hidden');
363
+ this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
364
+ } else {
365
+ this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
366
+ }
367
+ } catch (error) {
368
+ const emsg = extractErrorMsg(error);
369
+ console.error(`${lc} ${emsg}`);
370
+ this.setStatus(`Failed to dispatch verification code: ${emsg}`, "error");
371
+ } finally {
372
+ if (btnQuickSendCode) {
373
+ btnQuickSendCode.disabled = false;
374
+ btnQuickSendCode.textContent = "Send 6-Digit Verification Code";
375
+ }
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Triggered by the "Verify Code" button.
381
+ * Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
382
+ * registers delegate on server, and finalizes onboarding.
383
+ */
384
+ protected async handleQuickVerifyCode(): Promise<void> {
385
+ const lc = `${this.lc}[${this.handleQuickVerifyCode.name}]`;
386
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email') as HTMLInputElement | null;
387
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp') as HTMLInputElement | null;
388
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code') as HTMLButtonElement | null;
389
+
390
+ const email = (inputQuickEmail?.value || '').trim();
391
+ const code = (inputQuickOtp?.value || '').trim();
392
+ if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
393
+ this.setStatus("Please enter a valid 6-digit verification code.", "error");
394
+ return;
395
+ }
396
+
397
+ if (btnQuickVerifyCode) {
398
+ btnQuickVerifyCode.disabled = true;
399
+ btnQuickVerifyCode.textContent = "Verifying...";
400
+ }
401
+ this.setStatus("Verifying code & completing onboarding...", "info");
402
+ this.showBusy({
403
+ isBusy: true,
404
+ title: 'Verifying Code...',
405
+ msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
406
+ animationEmoji: '🔐'
407
+ });
408
+
409
+ try {
410
+ const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
411
+ if (!resGenesis.success || !resGenesis.primaryTjpAddr || !resGenesis.custodianReqs) {
412
+ this.setStatus(resGenesis.message || "Custodian verification failed.", "error");
413
+ return;
414
+ }
415
+
416
+ const primaryTjpAddr = resGenesis.primaryTjpAddr;
417
+ const custodianReqs = resGenesis.custodianReqs;
418
+
419
+ // Construct local delegate keystone
420
+ this.setStatus("Constructing local sync delegate...", "info");
421
+ this.showBusy({
422
+ isBusy: true,
423
+ title: 'Creating Sync Delegate...',
424
+ msg: 'Generating cryptographic sync delegate keys...',
425
+ animationEmoji: '🔑'
426
+ });
427
+
428
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
429
+ const username = email.split('@')[0];
430
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
431
+ .withUsername(`${username}-sync`)
432
+ .withDescription(`Sync Delegate for ${username}`)
433
+ .withDetails({
434
+ client: 'space-gib-web',
435
+ role: 'sync-delegate',
436
+ primaryTjpAddr,
437
+ custodianReqs,
438
+ });
439
+
440
+ const configs = await delegateBuilder.compileConfigs();
441
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
442
+ const space = await metaspace.getLocalUserSpace({ lock: false });
443
+ if (!space) {
444
+ this.setStatus("Failed to access local user space.", "error");
445
+ return;
446
+ }
447
+
448
+ const keystoneService = new KeystoneService_V1();
449
+ const delegateKeystone = await keystoneService.genesis({
450
+ masterSecret: delegateSecret,
451
+ configs,
452
+ metaspace,
453
+ space,
454
+ frameDetails: delegateBuilder.getFrameDetails(),
455
+ isPrimary: false
456
+ });
457
+
458
+ // Register delegate with server
459
+ this.setStatus("Registering device delegate with custodian server...", "info");
460
+ this.showBusy({
461
+ isBusy: true,
462
+ title: 'Registering Delegate...',
463
+ msg: 'Registering local delegate with custodian server...',
464
+ animationEmoji: '📝'
465
+ });
466
+
467
+ const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
468
+ if (!resRegister.success) {
469
+ this.setStatus(resRegister.message || "Failed to register delegate with custodian server.", "error");
470
+ return;
471
+ }
472
+
473
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
474
+ await this.finalizeIdentityOnboarding({
475
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resGenesis.primaryKeystone,
476
+ primaryGraph: resRegister.primaryGraph || resGenesis.primaryGraph,
477
+ delegateKeystone,
478
+ primaryTjpAddr,
479
+ delegateSecret,
480
+ makeActive: true
481
+ });
482
+
483
+ } catch (error) {
484
+ const emsg = extractErrorMsg(error);
485
+ console.error(`${lc} ${emsg}`, error);
486
+ this.setStatus(`Error: ${emsg}`, "error");
487
+ } finally {
488
+ this.showBusy({ isBusy: false });
489
+ if (btnQuickVerifyCode) {
490
+ btnQuickVerifyCode.disabled = false;
491
+ btnQuickVerifyCode.textContent = "Verify Code";
492
+ }
493
+ }
494
+ }
495
+
211
496
  /**
212
497
  * Triggered by the "Generate" button.
213
498
  * Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
@@ -293,7 +578,12 @@ export class KeystoneCreatorComponentInstance
293
578
  });
294
579
 
295
580
  // 1. Prepare Primary/Domain Keystone Configs
296
- const builder = KeystoneProfileBuilder.buildKeystone('domain')
581
+ const isProd = process.env.NODE_ENV === 'production';
582
+ const requestedProfile = (process.env.KEYSTONE_PROFILE || 'domain').toLowerCase();
583
+ const profileName = (isProd && requestedProfile === 'test') ? 'domain' : requestedProfile;
584
+ console.log(`${lc} Using keystone policy profile '${profileName}' (I: 90123456789abcdef0123456789abcde)`);
585
+
586
+ const builder = KeystoneProfileBuilder.buildKeystone(profileName as any)
297
587
  .withUsername(username)
298
588
  .withDescription(description);
299
589
  if (email) {
@@ -328,11 +618,11 @@ export class KeystoneCreatorComponentInstance
328
618
  isPrimary: true
329
619
  });
330
620
 
331
- const addr = getIbGibAddr({ ibGib: keystoneIbGib });
332
- console.log(`${lc} local genesis complete: ${addr}`);
621
+ const primaryTjpAddr = getTjpAddr({ ibGib: keystoneIbGib, defaultIfNone: 'incomingAddr' })!;
622
+ console.log(`${lc} local genesis complete: ${primaryTjpAddr}`);
333
623
  await delay(humanEyeDelay); // for human eyes, not needed for functionality
334
624
  this.setStatus("Local genesis complete. Creating sync delegate...", "info");
335
- console.log(`✓ Local genesis complete: ${addr}`);
625
+ console.log(`✓ Local genesis complete: ${primaryTjpAddr}`);
336
626
 
337
627
  // Automatically generate Sync Delegate Keystone
338
628
  this.showBusy({
@@ -343,12 +633,13 @@ export class KeystoneCreatorComponentInstance
343
633
  });
344
634
 
345
635
  const delegateSecret = await deriveDelegateSecret({ masterSecret });
346
- const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
636
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(profileName as any)
347
637
  .withUsername(`${username}-sync`)
348
638
  .withDescription(`Sync Delegate for ${username}`)
349
639
  .withDetails({
350
640
  client: 'space-gib-web',
351
- role: 'sync-delegate'
641
+ role: 'sync-delegate',
642
+ primaryTjpAddr: primaryTjpAddr,
352
643
  });
353
644
  const delegateConfigs = await delegateBuilder.compileConfigs();
354
645
 
@@ -385,15 +676,11 @@ export class KeystoneCreatorComponentInstance
385
676
  await delay(humanEyeDelay);
386
677
 
387
678
  // 4. Post to Server
388
- console.log(`Posting genesis keystone to server...`);
389
- this.setStatus("Syncing genesis to server...", "info");
679
+ this.setStatus("Syncing genesis keystone to server...", "info");
390
680
  this.showBusy({
391
681
  isBusy: true,
392
682
  title: 'Syncing to Server...',
393
- msg: [
394
- 'Posting genesis keystone to server sync node...',
395
- '(may take awhile, depending on strength of keystone...)',
396
- ].join('\n'),
683
+ msg: 'Staging pending genesis keystone on server...',
397
684
  animationEmoji: '🔄'
398
685
  });
399
686
  const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
@@ -406,74 +693,128 @@ export class KeystoneCreatorComponentInstance
406
693
  return;
407
694
  }
408
695
 
409
- // Post evolved keystone + delegate keystone
410
- this.setStatus("Syncing delegate registration to server...", "info");
411
- this.showBusy({
412
- isBusy: true,
413
- title: 'Syncing Delegate...',
414
- msg: 'Posting evolved domain identity and sync delegate to server...',
415
- animationEmoji: '🔄'
416
- });
417
- const resEvolve = await getApiBridge().putEvolveKeystone({
418
- domainAddr: addr,
419
- keystoneIbGib: evolvedParentKeystoneIbGib,
420
- relatedKeystoneIbGibs: [delegateKeystoneIbGib]
421
- });
422
- if (!resEvolve.success) {
423
- const msg = `Delegate registration sync failed: ${resEvolve.message}`;
424
- console.error(`${lc} ${msg}`);
425
- this.setStatus(msg, "error");
696
+ const isPending = (resSync as any).pending || (resSync as any).statusCode === 202;
697
+
698
+ if (isPending) {
699
+ console.log(`${lc} Genesis keystone quarantined in pending staging area. Prompting for 6-digit verification code.`);
426
700
  this.showBusy({ isBusy: false });
427
- this.setButtonLoading(false);
428
- return;
701
+ this.setFormDisabled(true);
702
+
703
+ if (!this.shadowRoot) { throw new Error(`(UNEXPECTED) this.shadowRoot falsy? (E: 6d316f5a2bf8e6a0286306468d35c826)`); }
704
+ const secVerify = shadowRoot_getElementById<HTMLDivElement>(this.shadowRoot, 'section-code-verification');
705
+ const inputVerifyCode =
706
+ shadowRoot_getElementById<HTMLInputElement>(this.shadowRoot, 'input-verify-code');
707
+ const btnSubmitCode =
708
+ shadowRoot_getElementById<HTMLButtonElement>(this.shadowRoot, 'btn-submit-code');
709
+ const btnResendCode =
710
+ shadowRoot_getElementById<HTMLButtonElement>(this.shadowRoot, 'btn-resend-code');
711
+
712
+ secVerify.classList.remove('hidden');
713
+ secVerify.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
714
+ this.setStatus("✉️ Verification code sent to your email. Enter 6-digit code below:", "info");
715
+
716
+ const onResendCode = async () => {
717
+ btnResendCode.disabled = true;
718
+ btnResendCode.textContent = "Sending...";
719
+ this.setStatus("Resending verification code to your email...", "info");
720
+ const resResend = await getApiBridge().postVerificationCode({ email, primaryTjpAddr: primaryTjpAddr });
721
+ if (resResend.success) {
722
+ this.setStatus("✉️ New verification code sent to your email!", "success");
723
+ } else {
724
+ this.setStatus(resResend.message || "Failed to resend verification code.", "error");
725
+ }
726
+ btnResendCode.disabled = false;
727
+ btnResendCode.textContent = "Resend Code";
728
+ };
729
+ btnResendCode.addEventListener('click', onResendCode);
730
+
731
+ await new Promise<void>((resolve, reject) => {
732
+ const onSubmitCode = async () => {
733
+ const code = inputVerifyCode.value.trim();
734
+ if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
735
+ this.setStatus("Please enter a valid 6-digit numeric verification code (e.g. 482901).", "error");
736
+ return;
737
+ }
738
+
739
+ btnSubmitCode.disabled = true;
740
+ btnSubmitCode.textContent = "Verifying...";
741
+ this.showBusy({
742
+ isBusy: true,
743
+ title: 'Verifying & Activating...',
744
+ msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
745
+ animationEmoji: '🔐'
746
+ });
747
+
748
+ const resVerify = await getApiBridge().postVerifyCode({ email, primaryTjpAddr: primaryTjpAddr, code });
749
+ if (!resVerify.success) {
750
+ this.showBusy({ isBusy: false });
751
+ this.setStatus(resVerify.message || "Incorrect verification code.", "error");
752
+ btnSubmitCode.disabled = false;
753
+ btnSubmitCode.textContent = "Verify Code";
754
+ return;
755
+ }
756
+
757
+ console.log(`${lc} Code verified & server domain promoted! Syncing delegate...`);
758
+ this.showBusy({
759
+ isBusy: true,
760
+ title: 'Syncing Delegate...',
761
+ msg: 'Posting evolved domain identity and sync delegate to server...',
762
+ animationEmoji: '⚡'
763
+ });
764
+
765
+ btnSubmitCode.removeEventListener('click', onSubmitCode);
766
+ secVerify.classList.add('hidden');
767
+ this.setFormDisabled(false);
768
+
769
+ // Now that server domain space is promoted, post evolved keystone + delegate
770
+ const resEvolve = await getApiBridge().putEvolveKeystone({
771
+ domainAddr: primaryTjpAddr,
772
+ keystoneIbGib: evolvedParentKeystoneIbGib,
773
+ relatedKeystoneIbGibs: [delegateKeystoneIbGib]
774
+ });
775
+ if (!resEvolve.success) {
776
+ console.warn(`${lc} Delegate sync warning after code verification: ${resEvolve.message}`);
777
+ }
778
+
779
+ resolve();
780
+ };
781
+
782
+ btnSubmitCode.addEventListener('click', onSubmitCode);
783
+ });
784
+ } else {
785
+ // Post evolved keystone + delegate keystone immediately (no email or pre-verified)
786
+ this.setStatus("Syncing delegate registration to server...", "info");
787
+ this.showBusy({
788
+ isBusy: true,
789
+ title: 'Syncing Delegate...',
790
+ msg: 'Posting evolved domain identity and sync delegate to server...',
791
+ animationEmoji: '🔄'
792
+ });
793
+ const resEvolve = await getApiBridge().putEvolveKeystone({
794
+ domainAddr: primaryTjpAddr,
795
+ keystoneIbGib: evolvedParentKeystoneIbGib,
796
+ relatedKeystoneIbGibs: [delegateKeystoneIbGib]
797
+ });
798
+ if (!resEvolve.success) {
799
+ const msg = `Delegate registration sync failed: ${resEvolve.message}`;
800
+ console.error(`${lc} ${msg}`);
801
+ this.setStatus(msg, "error");
802
+ this.showBusy({ isBusy: false });
803
+ this.setButtonLoading(false);
804
+ return;
805
+ }
429
806
  }
430
807
 
431
- // 5. Register with the keystones special index
432
- this.showBusy({
433
- isBusy: true,
434
- title: 'Registering Identity...',
435
- msg: 'Server sync succeeded!\nUpdating local identity indexes and scope settings...',
436
- animationEmoji: '💾'
437
- });
438
- await delay(humanEyeDelay); // for human eyes, not needed for functionality
439
- await updateSpecialIndex({
440
- type: "keystones",
441
- rel8nInfos: [
442
- {
443
- rel8nName: "keystone",
444
- ibGibs: [
445
- toDto({ ibGib: evolvedParentKeystoneIbGib }),
446
- toDto({ ibGib: delegateKeystoneIbGib })
447
- ],
448
- },
449
- ],
450
- metaspace,
451
- space,
808
+ // 5. Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
809
+ await this.finalizeIdentityOnboarding({
810
+ primaryKeystone: evolvedParentKeystoneIbGib,
811
+ delegateKeystone: delegateKeystoneIbGib,
812
+ primaryTjpAddr: evolvedParentAddr,
813
+ masterSecret,
814
+ delegateSecret,
815
+ makeActive: this.elements!.checkboxMakeActive?.checked ?? true
452
816
  });
453
817
 
454
- // Expose for dev tools
455
- (window as any).dev_domainI = evolvedParentKeystoneIbGib;
456
- (window as any).dev_domainIMasterSecret = masterSecret;
457
- (window as any).dev_syncDelegate = delegateKeystoneIbGib;
458
- (window as any).dev_syncDelegateSecret = delegateSecret;
459
-
460
- // 6. Request to make the new identity active if the checkbox is checked
461
- const makeActive = this.elements!.checkboxMakeActive?.checked;
462
- if (makeActive) {
463
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
464
- detail: { activeIdentityAddr: evolvedParentAddr },
465
- bubbles: true,
466
- composed: true
467
- }));
468
- }
469
-
470
- // 7. Success
471
- this.elements!.keystoneAddrEl.textContent = evolvedParentAddr;
472
- this.elements!.statusArea.classList.remove('hidden');
473
- this.setStatus("Keystone successfully created and synced!", "success");
474
- await delay(humanEyeDelay); // for human eyes, not needed for functionality
475
- console.log(`✓ Keystone successfully created and synced!`);
476
-
477
818
  } catch (error) {
478
819
  const emsg = extractErrorMsg(error);
479
820
  console.error(`${lc} ${emsg}`, error);
@@ -487,6 +828,164 @@ export class KeystoneCreatorComponentInstance
487
828
  }
488
829
  }
489
830
 
831
+ /**
832
+ * DRY helper to register keystones in the local keystones special index,
833
+ * set the active identity, and navigate to the Identity Manager component.
834
+ */
835
+ private async finalizeIdentityOnboarding({
836
+ primaryKeystone,
837
+ primaryGraph,
838
+ delegateKeystone,
839
+ primaryTjpAddr,
840
+ masterSecret,
841
+ delegateSecret,
842
+ makeActive = true,
843
+ }: {
844
+ primaryKeystone?: IbGib_V1;
845
+ primaryGraph?: FlatIbGibGraph;
846
+ delegateKeystone: IbGib_V1;
847
+ primaryTjpAddr: string;
848
+ masterSecret?: string;
849
+ delegateSecret?: string;
850
+ makeActive?: boolean;
851
+ }): Promise<void> {
852
+ const lc = `${this.lc}[${this.finalizeIdentityOnboarding.name}]`;
853
+ const humanEyeDelay = 500;
854
+
855
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
856
+ if (!metaspace) {
857
+ throw new Error(`(UNEXPECTED) metaspace falsy? Expected active global metaspace instance. (E: 2e37a8b6fe987449c5d8693a7d4a2827)`);
858
+ }
859
+
860
+ const space = await metaspace.getLocalUserSpace({});
861
+ if (!space) {
862
+ throw new Error(`(UNEXPECTED) space falsy? Expected valid default local user space from metaspace. (E: 1e37a8b6fe987449c5d8693a7d4a2826)`);
863
+ }
864
+
865
+ // 0. Persist all frames of primary identity graph into local browser space
866
+ if (primaryGraph) {
867
+ const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
868
+ for (const frame of graphIbGibs) {
869
+ await metaspace.put({ ibGibs: [frame], space });
870
+ await metaspace.registerNewIbGib({ ibGib: frame, space });
871
+ }
872
+ } else if (primaryKeystone) {
873
+ await metaspace.put({ ibGibs: [primaryKeystone], space });
874
+ await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
875
+ }
876
+
877
+ // 0.5 Verify primary keystone exists in local browser space via getLatestKeystone
878
+ const keystoneService = new KeystoneService_V1();
879
+ try {
880
+ await keystoneService.getLatestKeystone({
881
+ addr: primaryTjpAddr,
882
+ metaspace,
883
+ space
884
+ });
885
+ } catch (error) {
886
+ const emsg = `Primary identity keystone ${primaryTjpAddr} not found in local browser space! Error: ${extractErrorMsg(error)}`;
887
+ console.error(`${lc} ${emsg} (E: a123456789abcdef0123456789c0001)`);
888
+ throw new Error(emsg);
889
+ }
890
+
891
+ // 1. Register with the local keystones special index
892
+ this.showBusy({
893
+ isBusy: true,
894
+ title: 'Registering Identity...',
895
+ msg: 'Updating local identity indexes and scope settings...',
896
+ animationEmoji: '💾'
897
+ });
898
+ const ibGibsToRegister: IbGib_V1[] = [toDto({ ibGib: delegateKeystone })];
899
+
900
+ if (!primaryKeystone && !primaryGraph) { throw new Error(`(UNEXPECTED) primaryKeystone and primaryGraph both falsy? We should have gotten this back from the server. (E: e34cd882ac66633c75c53318a3b28826)`); }
901
+ if (!delegateKeystone) { throw new Error(`(UNEXPECTED) delegateKeystone falsy? We should have made this locally and even registered it with the server. (E: 56b1c396269e15f008b7e298c71bc826)`); }
902
+
903
+ if (primaryKeystone) {
904
+ ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone }));
905
+ }
906
+ await updateSpecialIndex({
907
+ type: "keystones",
908
+ rel8nInfos: [
909
+ {
910
+ rel8nName: "keystone",
911
+ ibGibs: ibGibsToRegister,
912
+ },
913
+ ],
914
+ metaspace,
915
+ space,
916
+ });
917
+
918
+ // Expose for dev tools debugging if available
919
+ if (masterSecret) {
920
+ (window as any).dev_domainI = primaryKeystone;
921
+ (window as any).dev_domainIMasterSecret = masterSecret;
922
+ (window as any).dev_syncDelegate = delegateKeystone;
923
+ (window as any).dev_syncDelegateSecret = delegateSecret;
924
+ }
925
+
926
+ // 2. Request to make the new identity active
927
+ if (makeActive) {
928
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
929
+ detail: { activeIdentityAddr: primaryTjpAddr },
930
+ bubbles: true,
931
+ composed: true
932
+ }));
933
+ }
934
+
935
+ // 3. Display success status
936
+ const successMsg = "Identity successfully created and activated!";
937
+ this.showBusy({
938
+ isBusy: true,
939
+ title: 'Success!',
940
+ msg: successMsg,
941
+ animationEmoji: '✅'
942
+ });
943
+ await delay(humanEyeDelay);
944
+ if (this.elements?.keystoneAddrEl) {
945
+ this.elements.keystoneAddrEl.textContent = primaryTjpAddr;
946
+ }
947
+ if (this.elements?.statusArea) {
948
+ this.elements.statusArea.classList.remove('hidden');
949
+ }
950
+ this.setStatus(successMsg, "success");
951
+ console.log(`${lc} ✓ ${successMsg}`);
952
+
953
+ // 4. Dispatch UI navigation message to close creator and open identity manager
954
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
955
+ detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
956
+ bubbles: true,
957
+ composed: true
958
+ }));
959
+
960
+ this.showBusy({ isBusy: false });
961
+ }
962
+
963
+ private setFormDisabled(disabled: boolean): void {
964
+ const root = this.shadowRoot!;
965
+ const inputIds = [
966
+ 'input-username',
967
+ 'input-description',
968
+ 'input-email',
969
+ 'input-secret',
970
+ 'checkbox-make-active',
971
+ 'btn-generate',
972
+ 'btn-switch-sign-in',
973
+ ];
974
+ for (const id of inputIds) {
975
+ const el = root.getElementById(id) as HTMLInputElement | HTMLButtonElement | null;
976
+ if (el) {
977
+ el.disabled = disabled;
978
+ if (disabled) {
979
+ el.style.opacity = '0.5';
980
+ el.style.pointerEvents = 'none';
981
+ } else {
982
+ el.style.opacity = '1';
983
+ el.style.pointerEvents = 'auto';
984
+ }
985
+ }
986
+ }
987
+ }
988
+
490
989
  protected override async renderUI(): Promise<void> {
491
990
  // No complex rendering needed for this simple creator component yet
492
991
  }