@ibgib/web-gib 0.0.57 → 0.0.59

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 (46) 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/ibgib-api-bridge.d.mts +63 -1
  4. package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
  5. package/dist/api/ibgib-api-bridge.mjs +135 -0
  6. package/dist/api/ibgib-api-bridge.mjs.map +1 -1
  7. package/dist/identity/custodian-delegate-helper.d.mts +22 -0
  8. package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
  9. package/dist/identity/custodian-delegate-helper.mjs +50 -0
  10. package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
  11. package/dist/identity/sso-popup-helper.d.mts +33 -0
  12. package/dist/identity/sso-popup-helper.d.mts.map +1 -0
  13. package/dist/identity/sso-popup-helper.mjs +124 -0
  14. package/dist/identity/sso-popup-helper.mjs.map +1 -0
  15. package/dist/index.d.mts +2 -0
  16. package/dist/index.d.mts.map +1 -1
  17. package/dist/index.mjs +3 -0
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  20. package/dist/ui/component/identity/add-device/add-device.mjs +5 -1
  21. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  22. package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
  23. package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
  24. package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
  25. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
  26. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +22 -0
  27. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  28. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
  29. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +445 -73
  30. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  31. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  32. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +10 -53
  33. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  34. package/package.json +4 -4
  35. package/src/AUTO-GENERATED-version.mts +1 -1
  36. package/src/api/ibgib-api-bridge.mts +195 -1
  37. package/src/identity/custodian-delegate-helper.mts +61 -0
  38. package/src/identity/sso-popup-helper.mts +155 -0
  39. package/src/index.mts +3 -0
  40. package/src/ui/component/identity/add-device/add-device.mts +5 -1
  41. package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
  42. package/src/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
  43. package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
  44. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +507 -77
  45. package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
  46. package/tools/auto-generated-agent-skills/skills/ibgib-implementation-plan/SKILL.md +3 -0
@@ -14,8 +14,10 @@ import { getGlobalMetaspace_waitIfNeeded, } from "../../../../helpers.mjs";
14
14
  import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
15
15
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
16
16
  import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
17
+ import { deriveLocalCustodianDelegateSecret } from "../../../../identity/custodian-delegate-helper.mjs";
18
+ import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
17
19
  import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
18
- import { shadowRoot_getElementById } from "../../../../helpers.web.mjs";
20
+ import { alertUser, shadowRoot_getElementById } from "../../../../helpers.web.mjs";
19
21
  export const KEYSTONE_CREATOR_COMPONENT_NAME = 'ibgib-keystone-creator';
20
22
  /**
21
23
  * Metadata for the Keystone Creator component.
@@ -113,6 +115,50 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
113
115
  // cleanup if needed
114
116
  }
115
117
  initHandlers() {
118
+ // Mode Tab Switching (Quick vs Password-based)
119
+ const tabQuick = this.shadowRoot?.getElementById('tab-quick');
120
+ const tabPassword = this.shadowRoot?.getElementById('tab-password');
121
+ const panelQuick = this.shadowRoot?.getElementById('panel-quick');
122
+ const panelPassword = this.shadowRoot?.getElementById('panel-password');
123
+ const subtitleQuick = this.shadowRoot?.getElementById('subtitle-quick');
124
+ const subtitlePassword = this.shadowRoot?.getElementById('subtitle-password');
125
+ tabQuick?.addEventListener('click', () => {
126
+ tabQuick.classList.add('active');
127
+ tabPassword?.classList.remove('active');
128
+ panelQuick?.classList.remove('hidden');
129
+ panelPassword?.classList.add('hidden');
130
+ subtitleQuick?.classList.remove('hidden');
131
+ subtitlePassword?.classList.add('hidden');
132
+ });
133
+ tabPassword?.addEventListener('click', () => {
134
+ tabPassword.classList.add('active');
135
+ tabQuick?.classList.remove('active');
136
+ panelPassword?.classList.remove('hidden');
137
+ panelQuick?.classList.add('hidden');
138
+ subtitlePassword?.classList.remove('hidden');
139
+ subtitleQuick?.classList.add('hidden');
140
+ });
141
+ const btnGoogleSso = this.shadowRoot?.getElementById('btn-google-sso');
142
+ btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
143
+ const btnGithubSso = this.shadowRoot?.getElementById('btn-github-sso');
144
+ btnGithubSso?.addEventListener('click', () => this.handleSsoClick('github'));
145
+ // Email Sign-In Info Button
146
+ const btnEmailInfo = this.shadowRoot?.getElementById('btn-email-info');
147
+ btnEmailInfo?.addEventListener('click', (e) => {
148
+ e.preventDefault();
149
+ alertUser({
150
+ title: 'Custom Email Sign-In',
151
+ 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!'
152
+ });
153
+ });
154
+ // Quick Email Verification Code Handlers
155
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code');
156
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
157
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
158
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
159
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
160
+ btnQuickSendCode?.addEventListener('click', () => this.handleQuickSendCode());
161
+ btnQuickVerifyCode?.addEventListener('click', () => this.handleQuickVerifyCode());
116
162
  this.elements.btnGenerateEl.addEventListener('click', () => this.handleGenerate());
117
163
  this.shadowRoot.getElementById('btn-switch-sign-in')?.addEventListener('click', (e) => {
118
164
  e.preventDefault();
@@ -123,16 +169,16 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
123
169
  }));
124
170
  });
125
171
  // Clear invalid styling when user inputs text
126
- this.elements.inputUsername.addEventListener('input', () => {
172
+ this.elements.inputUsername?.addEventListener('input', () => {
127
173
  this.elements.inputUsername.classList.remove('invalid');
128
174
  });
129
- this.elements.inputDescription.addEventListener('input', () => {
175
+ this.elements.inputDescription?.addEventListener('input', () => {
130
176
  this.elements.inputDescription.classList.remove('invalid');
131
177
  });
132
- this.elements.inputEmail.addEventListener('input', () => {
178
+ this.elements.inputEmail?.addEventListener('input', () => {
133
179
  this.elements.inputEmail.classList.remove('invalid');
134
180
  });
135
- this.elements.inputSecret.addEventListener('input', () => {
181
+ this.elements.inputSecret?.addEventListener('input', () => {
136
182
  this.elements.inputSecret.classList.remove('invalid');
137
183
  });
138
184
  }
@@ -143,6 +189,266 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
143
189
  btn.textContent = isLoading ? 'Generating...' : 'Generate Identity Keystone';
144
190
  }
145
191
  }
192
+ /**
193
+ * Triggered by Google / GitHub SSO buttons.
194
+ * Opens OAuth popup, exchanges authorization code, builds local delegate keystone,
195
+ * registers delegate on server, and finalizes onboarding.
196
+ */
197
+ async handleSsoClick(providerId) {
198
+ const lc = `${this.lc}[${this.handleSsoClick.name}]`;
199
+ const providerName = providerId === 'google' ? 'Google' : providerId === 'github' ? 'GitHub' : providerId;
200
+ this.setStatus(`Opening ${providerName} SSO authorization window...`, 'info');
201
+ this.showBusy({
202
+ isBusy: true,
203
+ title: `Connecting to ${providerName}...`,
204
+ msg: `Opening ${providerName} SSO authorization window...\nPlease complete authorization in the popup window.`,
205
+ animationEmoji: '🌐'
206
+ });
207
+ try {
208
+ const resPopup = await executeSsoOAuthPopup({ providerId });
209
+ if (!resPopup.success || !resPopup.code) {
210
+ this.setStatus(resPopup.message || `${providerName} sign-in cancelled.`, 'error');
211
+ return;
212
+ }
213
+ this.setStatus(`Exchanging ${providerName} authorization code...`, 'info');
214
+ this.showBusy({
215
+ isBusy: true,
216
+ title: 'Exchanging SSO Code...',
217
+ msg: `Validating ${providerName} authorization code with custodian server...`,
218
+ animationEmoji: '🔐'
219
+ });
220
+ const apiBridge = getApiBridge();
221
+ const resLogin = await apiBridge.postSsoLogin({
222
+ code: resPopup.code,
223
+ providerId,
224
+ redirectUri: resPopup.redirectUri
225
+ });
226
+ if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
227
+ this.setStatus(resLogin.message || `${providerName} login failed.`, 'error');
228
+ return;
229
+ }
230
+ const { primaryTjpAddr, custodianReqs } = resLogin;
231
+ this.setStatus('Deriving local device delegate keys...', 'info');
232
+ this.showBusy({
233
+ isBusy: true,
234
+ title: 'Creating Sync Delegate...',
235
+ msg: 'Generating cryptographic sync delegate keys...',
236
+ animationEmoji: '🔑'
237
+ });
238
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
239
+ const email = resLogin.userInfo?.email || '';
240
+ const username = email ? email.split('@')[0] : 'user';
241
+ const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
242
+ if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
243
+ throw new Error('KEYSTONE_PROFILE_CUSTODIAN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 4d5e6f7890123456789abcdef4)');
244
+ }
245
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim())
246
+ .withUsername(`${username}-sync`)
247
+ .withDescription(`Sync Delegate for ${username}`)
248
+ .withDetails({
249
+ client: 'space-gib-web',
250
+ role: 'sync-delegate',
251
+ primaryTjpAddr,
252
+ custodianReqs,
253
+ });
254
+ const configs = await delegateBuilder.compileConfigs();
255
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
256
+ const space = await metaspace.getLocalUserSpace({ lock: false });
257
+ if (!space) {
258
+ this.setStatus('Could not access local user space.', 'error');
259
+ return;
260
+ }
261
+ const keystoneService = new KeystoneService_V1();
262
+ const delegateKeystone = await keystoneService.genesis({
263
+ masterSecret: delegateSecret,
264
+ configs,
265
+ metaspace,
266
+ space,
267
+ frameDetails: delegateBuilder.getFrameDetails(),
268
+ isPrimary: false
269
+ });
270
+ this.setStatus('Registering device delegate with custodian server...', 'info');
271
+ this.showBusy({
272
+ isBusy: true,
273
+ title: 'Registering Delegate...',
274
+ msg: 'Registering local delegate with custodian server...',
275
+ animationEmoji: '📝'
276
+ });
277
+ const resRegister = await apiBridge.postCustodianRegisterDelegate({ delegateKeystone });
278
+ if (!resRegister.success) {
279
+ this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
280
+ return;
281
+ }
282
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
283
+ await this.finalizeIdentityOnboarding({
284
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resLogin.primaryKeystone,
285
+ primaryGraph: resRegister.primaryGraph || resLogin.primaryGraph,
286
+ delegateKeystone,
287
+ primaryTjpAddr,
288
+ delegateSecret,
289
+ makeActive: true
290
+ });
291
+ }
292
+ catch (error) {
293
+ const emsg = extractErrorMsg(error);
294
+ console.error(`${lc} ${emsg}`, error);
295
+ this.setStatus(`SSO sign-in error: ${emsg}`, 'error');
296
+ }
297
+ finally {
298
+ this.showBusy({ isBusy: false });
299
+ }
300
+ }
301
+ /**
302
+ * Triggered by the "Send 6-Digit Verification Code" button.
303
+ * Validates quick email input and dispatches a 6-digit OTP code to the user's inbox.
304
+ */
305
+ async handleQuickSendCode() {
306
+ const lc = `${this.lc}[${this.handleQuickSendCode.name}]`;
307
+ const btnQuickSendCode = this.shadowRoot?.getElementById('btn-quick-send-code');
308
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
309
+ const secQuickOtp = this.shadowRoot?.getElementById('section-quick-otp');
310
+ const email = (inputQuickEmail?.value || '').trim();
311
+ if (!email || !validateEmail(email)) {
312
+ this.setStatus("Please enter a valid email address.", "error");
313
+ return;
314
+ }
315
+ if (btnQuickSendCode) {
316
+ btnQuickSendCode.disabled = true;
317
+ btnQuickSendCode.textContent = "Sending Verification Code...";
318
+ }
319
+ this.setStatus("Requesting verification code...", "info");
320
+ try {
321
+ const resCode = await getApiBridge().postVerificationCode({ email });
322
+ if (resCode.success) {
323
+ secQuickOtp?.classList.remove('hidden');
324
+ this.setStatus("✉️ 6-digit verification code sent to your email!", "success");
325
+ }
326
+ else {
327
+ this.setStatus(resCode.message || "Failed to dispatch verification code.", "error");
328
+ }
329
+ }
330
+ catch (error) {
331
+ const emsg = extractErrorMsg(error);
332
+ console.error(`${lc} ${emsg}`);
333
+ this.setStatus(`Failed to dispatch verification code: ${emsg}`, "error");
334
+ }
335
+ finally {
336
+ if (btnQuickSendCode) {
337
+ btnQuickSendCode.disabled = false;
338
+ btnQuickSendCode.textContent = "Send 6-Digit Verification Code";
339
+ }
340
+ }
341
+ }
342
+ /**
343
+ * Triggered by the "Verify Code" button.
344
+ * Submits 6-digit OTP code to custodian server, builds local sync delegate keystone,
345
+ * registers delegate on server, and finalizes onboarding.
346
+ */
347
+ async handleQuickVerifyCode() {
348
+ const lc = `${this.lc}[${this.handleQuickVerifyCode.name}]`;
349
+ const inputQuickEmail = this.shadowRoot?.getElementById('input-quick-email');
350
+ const inputQuickOtp = this.shadowRoot?.getElementById('input-quick-otp');
351
+ const btnQuickVerifyCode = this.shadowRoot?.getElementById('btn-quick-verify-code');
352
+ const email = (inputQuickEmail?.value || '').trim();
353
+ const code = (inputQuickOtp?.value || '').trim();
354
+ if (!code || code.length !== 6 || !/^\d{6}$/.test(code)) {
355
+ this.setStatus("Please enter a valid 6-digit verification code.", "error");
356
+ return;
357
+ }
358
+ if (btnQuickVerifyCode) {
359
+ btnQuickVerifyCode.disabled = true;
360
+ btnQuickVerifyCode.textContent = "Verifying...";
361
+ }
362
+ this.setStatus("Verifying code & completing onboarding...", "info");
363
+ this.showBusy({
364
+ isBusy: true,
365
+ title: 'Verifying Code...',
366
+ msg: 'Great! Juust a moment...\n(Validating code, promoting server domain space, other security stuff...)',
367
+ animationEmoji: '🔐'
368
+ });
369
+ try {
370
+ const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
371
+ if (!resGenesis.success || !resGenesis.primaryTjpAddr || !resGenesis.custodianReqs) {
372
+ this.setStatus(resGenesis.message || "Custodian verification failed.", "error");
373
+ return;
374
+ }
375
+ const primaryTjpAddr = resGenesis.primaryTjpAddr;
376
+ const custodianReqs = resGenesis.custodianReqs;
377
+ // Construct local delegate keystone
378
+ this.setStatus("Constructing local sync delegate...", "info");
379
+ this.showBusy({
380
+ isBusy: true,
381
+ title: 'Creating Sync Delegate...',
382
+ msg: 'Generating cryptographic sync delegate keys...',
383
+ animationEmoji: '🔑'
384
+ });
385
+ const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
386
+ const username = email ? email.split('@')[0] : 'user';
387
+ const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
388
+ if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
389
+ throw new Error('KEYSTONE_PROFILE_CUSTODIAN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 4d5e6f7890123456789abcdef4)');
390
+ }
391
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim())
392
+ .withUsername(`${username}-sync`)
393
+ .withDescription(`Sync Delegate for ${username}`)
394
+ .withDetails({
395
+ client: 'space-gib-web',
396
+ role: 'sync-delegate',
397
+ primaryTjpAddr,
398
+ custodianReqs,
399
+ });
400
+ const configs = await delegateBuilder.compileConfigs();
401
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
402
+ const space = await metaspace.getLocalUserSpace({ lock: false });
403
+ if (!space) {
404
+ this.setStatus("Failed to access local user space.", "error");
405
+ return;
406
+ }
407
+ const keystoneService = new KeystoneService_V1();
408
+ const delegateKeystone = await keystoneService.genesis({
409
+ masterSecret: delegateSecret,
410
+ configs,
411
+ metaspace,
412
+ space,
413
+ frameDetails: delegateBuilder.getFrameDetails(),
414
+ isPrimary: false
415
+ });
416
+ // Register delegate with server
417
+ this.setStatus("Registering device delegate with custodian server...", "info");
418
+ this.showBusy({
419
+ isBusy: true,
420
+ title: 'Registering Delegate...',
421
+ msg: 'Registering local delegate with custodian server...',
422
+ animationEmoji: '📝'
423
+ });
424
+ const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
425
+ if (!resRegister.success) {
426
+ this.setStatus(resRegister.message || "Failed to register delegate with custodian server.", "error");
427
+ return;
428
+ }
429
+ // Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
430
+ await this.finalizeIdentityOnboarding({
431
+ primaryKeystone: resRegister.evolvedPrimaryKeystone || resGenesis.primaryKeystone,
432
+ primaryGraph: resRegister.primaryGraph || resGenesis.primaryGraph,
433
+ delegateKeystone,
434
+ primaryTjpAddr,
435
+ delegateSecret,
436
+ makeActive: true
437
+ });
438
+ }
439
+ catch (error) {
440
+ const emsg = extractErrorMsg(error);
441
+ console.error(`${lc} ${emsg}`, error);
442
+ this.setStatus(`Error: ${emsg}`, "error");
443
+ }
444
+ finally {
445
+ this.showBusy({ isBusy: false });
446
+ if (btnQuickVerifyCode) {
447
+ btnQuickVerifyCode.disabled = false;
448
+ btnQuickVerifyCode.textContent = "Verify Code";
449
+ }
450
+ }
451
+ }
146
452
  /**
147
453
  * Triggered by the "Generate" button.
148
454
  * Orchestrates: Secret -> Local Genesis -> Graph Collection -> Server Sync.
@@ -218,12 +524,13 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
218
524
  msg: 'Preparing configuration and policy options...',
219
525
  animationEmoji: '⚙️'
220
526
  });
221
- // 1. Prepare Primary/Domain Keystone Configs
222
- const isProd = process.env.NODE_ENV === 'production';
223
- const requestedProfile = (process.env.KEYSTONE_PROFILE || 'domain').toLowerCase();
224
- const profileName = (isProd && requestedProfile === 'test') ? 'domain' : requestedProfile;
225
- console.log(`${lc} Using keystone policy profile '${profileName}' (I: 90123456789abcdef0123456789abcde)`);
226
- const builder = KeystoneProfileBuilder.buildKeystone(profileName)
527
+ // 1. Prepare Primary Keystone Configs
528
+ const sovereignPrimaryProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_PRIMARY;
529
+ if (!sovereignPrimaryProfileName || !sovereignPrimaryProfileName.trim()) {
530
+ throw new Error('KEYSTONE_PROFILE_SOVEREIGN_PRIMARY environment variable is missing. Expected valid profile name like "primary-profile.dev" or "primary-profile.prod". (E: 3c4d5e6f7890123456789abcdef3)');
531
+ }
532
+ console.log(`${lc} Using sovereign primary keystone policy profile '${sovereignPrimaryProfileName}' (I: 90123456789abcdef0123456789abcde)`);
533
+ const builder = KeystoneProfileBuilder.buildKeystone(sovereignPrimaryProfileName.trim())
227
534
  .withUsername(username)
228
535
  .withDescription(description);
229
536
  if (email) {
@@ -234,11 +541,12 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
234
541
  role: 'domain-identity'
235
542
  });
236
543
  const configs = await builder.compileConfigs();
237
- // 2. Get local services
544
+ // 2. Prepare Metaspace/Space
238
545
  const metaspace = await getGlobalMetaspace_waitIfNeeded();
239
- const space = await metaspace.getLocalUserSpace({});
546
+ const space = await metaspace.getLocalUserSpace({ lock: false });
240
547
  if (!space) {
241
- throw new Error("No default space found in metaspace.");
548
+ this.setStatus("Failed to access local user space.", "error");
549
+ return;
242
550
  }
243
551
  // 3. Perform Genesis (Local)
244
552
  this.showBusy({
@@ -270,7 +578,11 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
270
578
  animationEmoji: '🔑'
271
579
  });
272
580
  const delegateSecret = await deriveDelegateSecret({ masterSecret });
273
- const delegateBuilder = KeystoneProfileBuilder.buildKeystone(profileName)
581
+ const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
582
+ if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
583
+ throw new Error('KEYSTONE_PROFILE_SOVEREIGN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 5e6f7890123456789abcdef5)');
584
+ }
585
+ const delegateBuilder = KeystoneProfileBuilder.buildKeystone(sovereignSyncProfileName.trim())
274
586
  .withUsername(`${username}-sync`)
275
587
  .withDescription(`Sync Delegate for ${username}`)
276
588
  .withDetails({
@@ -313,7 +625,7 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
313
625
  this.showBusy({
314
626
  isBusy: true,
315
627
  title: 'Syncing to Server...',
316
- msg: 'Quarantining staging genesis keystone on server...',
628
+ msg: 'Staging pending genesis keystone on server...',
317
629
  animationEmoji: '🔄'
318
630
  });
319
631
  const resSync = await getApiBridge().postGenesisKeystone(keystoneIbGib);
@@ -425,64 +737,15 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
425
737
  return;
426
738
  }
427
739
  }
428
- // 5. Register with the keystones special index
429
- this.showBusy({
430
- isBusy: true,
431
- title: 'Registering Identity...',
432
- msg: 'Server sync succeeded!\nUpdating local identity indexes and scope settings...',
433
- animationEmoji: '💾'
434
- });
435
- await delay(humanEyeDelay); // for human eyes, not needed for functionality
436
- await updateSpecialIndex({
437
- type: "keystones",
438
- rel8nInfos: [
439
- {
440
- rel8nName: "keystone",
441
- ibGibs: [
442
- toDto({ ibGib: evolvedParentKeystoneIbGib }),
443
- toDto({ ibGib: delegateKeystoneIbGib })
444
- ],
445
- },
446
- ],
447
- metaspace,
448
- space,
449
- });
450
- // Expose for dev tools
451
- window.dev_domainI = evolvedParentKeystoneIbGib;
452
- window.dev_domainIMasterSecret = masterSecret;
453
- window.dev_syncDelegate = delegateKeystoneIbGib;
454
- window.dev_syncDelegateSecret = delegateSecret;
455
- // 6. Request to make the new identity active if the checkbox is checked
456
- const makeActive = this.elements.checkboxMakeActive?.checked;
457
- if (makeActive) {
458
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
459
- detail: { activeIdentityAddr: evolvedParentAddr },
460
- bubbles: true,
461
- composed: true
462
- }));
463
- }
464
- // 7. Success
465
- const successMsg = "Identity successfully created and activated! Just a sec...";
466
- this.showBusy({
467
- isBusy: true,
468
- title: 'Success!',
469
- msg: successMsg,
470
- animationEmoji: '✅'
740
+ // 5. Finalize onboarding: update keystones index, activate identity, and navigate to identity manager
741
+ await this.finalizeIdentityOnboarding({
742
+ primaryKeystone: evolvedParentKeystoneIbGib,
743
+ delegateKeystone: delegateKeystoneIbGib,
744
+ primaryTjpAddr: evolvedParentAddr,
745
+ masterSecret,
746
+ delegateSecret,
747
+ makeActive: this.elements.checkboxMakeActive?.checked ?? true
471
748
  });
472
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
473
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
474
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
475
- await delay(humanEyeDelay); // for human eyes, not needed for functionality, because we'll redirect
476
- this.elements.keystoneAddrEl.textContent = evolvedParentAddr;
477
- this.elements.statusArea.classList.remove('hidden');
478
- this.setStatus(successMsg, "success");
479
- console.log(`${lc} ✓ ${successMsg} (I: 3396d8261978d7027895b71e27e9ab26)`);
480
- // Dispatch UI message to close keystone-creator and show identity-manager
481
- window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
482
- detail: { action: 'show-identity-manager', addr: evolvedParentAddr },
483
- bubbles: true,
484
- composed: true
485
- }));
486
749
  }
487
750
  catch (error) {
488
751
  const emsg = extractErrorMsg(error);
@@ -497,6 +760,115 @@ export class KeystoneCreatorComponentInstance extends IbGibFormInstanceBase {
497
760
  this.setButtonLoading(false);
498
761
  }
499
762
  }
763
+ /**
764
+ * DRY helper to register keystones in the local keystones special index,
765
+ * set the active identity, and navigate to the Identity Manager component.
766
+ */
767
+ async finalizeIdentityOnboarding({ primaryKeystone, primaryGraph, delegateKeystone, primaryTjpAddr, masterSecret, delegateSecret, makeActive = true, }) {
768
+ const lc = `${this.lc}[${this.finalizeIdentityOnboarding.name}]`;
769
+ const humanEyeDelay = 500;
770
+ const metaspace = await getGlobalMetaspace_waitIfNeeded();
771
+ if (!metaspace) {
772
+ throw new Error(`(UNEXPECTED) metaspace falsy? Expected active global metaspace instance. (E: 2e37a8b6fe987449c5d8693a7d4a2827)`);
773
+ }
774
+ const space = await metaspace.getLocalUserSpace({});
775
+ if (!space) {
776
+ throw new Error(`(UNEXPECTED) space falsy? Expected valid default local user space from metaspace. (E: 1e37a8b6fe987449c5d8693a7d4a2826)`);
777
+ }
778
+ // 0. Persist all frames of primary identity graph into local browser space
779
+ if (primaryGraph) {
780
+ const graphIbGibs = Object.values(primaryGraph);
781
+ for (const frame of graphIbGibs) {
782
+ await metaspace.put({ ibGibs: [frame], space });
783
+ await metaspace.registerNewIbGib({ ibGib: frame, space });
784
+ }
785
+ }
786
+ else if (primaryKeystone) {
787
+ await metaspace.put({ ibGibs: [primaryKeystone], space });
788
+ await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
789
+ }
790
+ // 0.5 Verify primary keystone exists in local browser space via getLatestKeystone
791
+ const keystoneService = new KeystoneService_V1();
792
+ try {
793
+ await keystoneService.getLatestKeystone({
794
+ addr: primaryTjpAddr,
795
+ metaspace,
796
+ space
797
+ });
798
+ }
799
+ catch (error) {
800
+ const emsg = `Primary identity keystone ${primaryTjpAddr} not found in local browser space! Error: ${extractErrorMsg(error)}`;
801
+ console.error(`${lc} ${emsg} (E: a123456789abcdef0123456789c0001)`);
802
+ throw new Error(emsg);
803
+ }
804
+ // 1. Register with the local keystones special index
805
+ this.showBusy({
806
+ isBusy: true,
807
+ title: 'Registering Identity...',
808
+ msg: 'Updating local identity indexes and scope settings...',
809
+ animationEmoji: '💾'
810
+ });
811
+ const ibGibsToRegister = [toDto({ ibGib: delegateKeystone })];
812
+ if (!primaryKeystone && !primaryGraph) {
813
+ throw new Error(`(UNEXPECTED) primaryKeystone and primaryGraph both falsy? We should have gotten this back from the server. (E: e34cd882ac66633c75c53318a3b28826)`);
814
+ }
815
+ if (!delegateKeystone) {
816
+ throw new Error(`(UNEXPECTED) delegateKeystone falsy? We should have made this locally and even registered it with the server. (E: 56b1c396269e15f008b7e298c71bc826)`);
817
+ }
818
+ if (primaryKeystone) {
819
+ ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone }));
820
+ }
821
+ await updateSpecialIndex({
822
+ type: "keystones",
823
+ rel8nInfos: [
824
+ {
825
+ rel8nName: "keystone",
826
+ ibGibs: ibGibsToRegister,
827
+ },
828
+ ],
829
+ metaspace,
830
+ space,
831
+ });
832
+ // Expose for dev tools debugging if available
833
+ if (masterSecret) {
834
+ window.dev_domainI = primaryKeystone;
835
+ window.dev_domainIMasterSecret = masterSecret;
836
+ window.dev_syncDelegate = delegateKeystone;
837
+ window.dev_syncDelegateSecret = delegateSecret;
838
+ }
839
+ // 2. Request to make the new identity active
840
+ if (makeActive) {
841
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
842
+ detail: { activeIdentityAddr: primaryTjpAddr },
843
+ bubbles: true,
844
+ composed: true
845
+ }));
846
+ }
847
+ // 3. Display success status
848
+ const successMsg = "Identity successfully created and activated!";
849
+ this.showBusy({
850
+ isBusy: true,
851
+ title: 'Success!',
852
+ msg: successMsg,
853
+ animationEmoji: '✅'
854
+ });
855
+ await delay(humanEyeDelay);
856
+ if (this.elements?.keystoneAddrEl) {
857
+ this.elements.keystoneAddrEl.textContent = primaryTjpAddr;
858
+ }
859
+ if (this.elements?.statusArea) {
860
+ this.elements.statusArea.classList.remove('hidden');
861
+ }
862
+ this.setStatus(successMsg, "success");
863
+ console.log(`${lc} ✓ ${successMsg}`);
864
+ // 4. Dispatch UI navigation message to close creator and open identity manager
865
+ window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
866
+ detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
867
+ bubbles: true,
868
+ composed: true
869
+ }));
870
+ this.showBusy({ isBusy: false });
871
+ }
500
872
  setFormDisabled(disabled) {
501
873
  const root = this.shadowRoot;
502
874
  const inputIds = [