@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
@@ -1,10 +1,11 @@
1
1
  import { generateKeyPairSync, createSign } from 'node:crypto';
2
- import {
3
- respecfully,
4
- ifWeMight,
5
- iReckon,
6
- firstOfAll,
7
- lastOfAll
2
+ import {
3
+ respecfully,
4
+ ifWeMight,
5
+ iReckon,
6
+ firstOfAll,
7
+ lastOfAll,
8
+ ifWe
8
9
  } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
9
10
  import { SsoCustodianService } from './sso-custodian-service.mjs';
10
11
  import { SsoServerConfig } from './sso-types.mjs';
@@ -24,7 +25,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
24
25
  modulusLength: 2048
25
26
  });
26
27
  mockPrivateKey = privateKey;
27
-
28
+
28
29
  // Export public key as JWK
29
30
  mockPublicKeyJwk = publicKey.export({ format: 'jwk' });
30
31
  mockPublicKeyJwk.kid = 'mock-kid-123';
@@ -58,7 +59,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
58
59
  }
59
60
  });
60
61
 
61
- await ifWeMight(sir, 'verify dynamic RSA RS256 JWT signature successfully', async () => {
62
+ await ifWe(sir, 'verify dynamic RSA RS256 JWT signature successfully', async () => {
62
63
  const config: SsoServerConfig = {
63
64
  ssoServerKdfSecret: 'mock-kdf-secret',
64
65
  sessionSecret: 'mock-session-secret',
@@ -106,7 +107,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
106
107
  iReckon(sir, decodedPayload.name).isGonnaBe('John Doe');
107
108
  });
108
109
 
109
- await ifWeMight(sir, 'fail validation if JWT has invalid signature', async () => {
110
+ await ifWe(sir, 'fail validation if JWT has invalid signature', async () => {
110
111
  const config: SsoServerConfig = {
111
112
  ssoServerKdfSecret: 'mock-kdf-secret',
112
113
  sessionSecret: 'mock-session-secret',
@@ -154,7 +155,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
154
155
  iReckon(sir, verifyError.message).includes('Cryptographic signature verification failed');
155
156
  });
156
157
 
157
- await ifWeMight(sir, 'derive server-delegate secret deterministically', async () => {
158
+ await ifWe(sir, 'derive server-delegate secret deterministically', async () => {
158
159
  const config: SsoServerConfig = {
159
160
  ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
160
161
  sessionSecret: 'session-secret-123',
@@ -179,7 +180,7 @@ await respecfully(sir, 'SsoCustodianService', async () => {
179
180
  iReckon(sir, secret1_a.length).isGonnaBe(128); // sha512 output in hex/base64 wrap
180
181
  });
181
182
 
182
- await ifWeMight(sir, 'create custodian challenge pool successfully', async () => {
183
+ await ifWe(sir, 'create custodian challenge pool successfully', async () => {
183
184
  const config: SsoServerConfig = {
184
185
  ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
185
186
  sessionSecret: 'session-secret-123',
@@ -234,16 +235,16 @@ await respecfully(sir, 'SsoCustodianService', async () => {
234
235
  iReckon(sir, custodianPool.id).willEqual('custodian-manage-google');
235
236
  iReckon(sir, custodianPool.isForeign).isGonnaBeTrue();
236
237
  iReckon(sir, custodianPool.metadata?.providerKey).willEqual(key);
237
-
238
+
238
239
  // Replicated behavior size from parent manage pool config
239
240
  iReckon(sir, custodianPool.config.behavior.size).willEqual(userManageConfig.behavior.size);
240
-
241
+
241
242
  // Structural validation of challenges
242
243
  const challengesCount = Object.keys(custodianPool.challenges).length;
243
244
  iReckon(sir, challengesCount).willEqual(custodianPool.config.behavior.size);
244
245
  });
245
246
 
246
- await ifWeMight(sir, 'revoke tokens successfully, making the correct http fetch calls', async () => {
247
+ await ifWe(sir, 'revoke tokens successfully, making the correct http fetch calls', async () => {
247
248
  const config: SsoServerConfig = {
248
249
  ssoServerKdfSecret: 'high-entropy-server-kdf-secret',
249
250
  sessionSecret: 'session-secret-123',
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @module identity/sso-popup-helper
3
+ *
4
+ * DRY client helper for initiating OAuth2 SSO authentication via a popup window
5
+ * and capturing the returned authorization code.
6
+ */
7
+
8
+ import { extractErrorMsg, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
9
+ import { getApiBridge } from "../api/ibgib-api-bridge.mjs";
10
+
11
+ export interface SsoPopupOpts {
12
+ providerId: 'google' | 'github';
13
+ parentTjpAddr?: string;
14
+ }
15
+
16
+ export interface SsoPopupResult {
17
+ success: boolean;
18
+ code?: string;
19
+ stateObj?: any;
20
+ redirectUri?: string;
21
+ message?: string;
22
+ }
23
+
24
+ /**
25
+ * Executes an OAuth2 SSO login/link authorization flow using a popup window.
26
+ *
27
+ * 1. Queries `GET /api/identity/sso/config` for safe public client configs.
28
+ * 2. Constructs the provider authorization URL with encoded state payload.
29
+ * 3. Opens a 600x700 popup window and listens for `sso-oauth-callback` postMessage.
30
+ * 4. Returns `{ success: true, code, stateObj, redirectUri }` upon authorization.
31
+ */
32
+ export async function executeSsoOAuthPopup({
33
+ providerId,
34
+ parentTjpAddr,
35
+ }: SsoPopupOpts): Promise<SsoPopupResult> {
36
+ const lc = `[${executeSsoOAuthPopup.name}]`;
37
+ try {
38
+ // 1. Fetch public SSO configs from server
39
+ const apiBridge = getApiBridge();
40
+ const resConfig = await apiBridge.getSsoConfig();
41
+ if (!resConfig.success || !resConfig.providers) {
42
+ return { success: false, message: resConfig.message || 'SSO configuration retrieval failed.' };
43
+ }
44
+
45
+ const providers = resConfig.providers as any;
46
+ const providerConfig = Array.isArray(providers)
47
+ ? providers.find((p: any) => p.providerId === providerId)
48
+ : providers[providerId];
49
+
50
+ if (!providerConfig) {
51
+ return {
52
+ success: false,
53
+ message: `SSO provider '${providerId}' is not configured on this server environment.`
54
+ };
55
+ }
56
+
57
+ // 2. Generate high-entropy user nonce & state
58
+ /**
59
+ * {@link getUUID} uses {@link globalThis.crypto.getRandomValues}
60
+ */
61
+ const userNonce = await getUUID();
62
+ const stateObj = {
63
+ providerId,
64
+ parentTjpAddr,
65
+ userNonce
66
+ };
67
+ const state = btoa(JSON.stringify(stateObj));
68
+
69
+ // 3. Construct Auth URL if not pre-constructed
70
+ let authUrl = providerConfig.authUrl;
71
+ if (!authUrl.includes('client_id=')) {
72
+ authUrl = `${authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
73
+ } else if (!authUrl.includes('state=')) {
74
+ authUrl = `${authUrl}&state=${encodeURIComponent(state)}`;
75
+ }
76
+
77
+ // 4. Open popup window
78
+ const popup = window.open(
79
+ authUrl,
80
+ 'sso-oauth-popup',
81
+ 'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
82
+ );
83
+
84
+ if (!popup) {
85
+ return { success: false, message: 'Popup blocked by browser. Please enable popups for this site.' };
86
+ }
87
+
88
+ // 5. Promise wrapper listening for postMessage or window closure
89
+ return await new Promise<SsoPopupResult>((resolve) => {
90
+ let checkClosedInterval: any;
91
+
92
+ const messageListener = (event: MessageEvent) => {
93
+ if (event.origin !== window.location.origin) return;
94
+ const callbackData = event.data;
95
+ if (callbackData && callbackData.type === 'sso-oauth-callback') {
96
+ window.removeEventListener('message', messageListener);
97
+ clearInterval(checkClosedInterval);
98
+ resolve({
99
+ success: true,
100
+ code: callbackData.code,
101
+ stateObj: callbackData.stateObj || stateObj,
102
+ redirectUri: providerConfig.redirectUri
103
+ });
104
+ }
105
+ };
106
+
107
+ window.addEventListener('message', messageListener);
108
+
109
+ checkClosedInterval = setInterval(() => {
110
+ if (popup.closed) {
111
+ clearInterval(checkClosedInterval);
112
+ window.removeEventListener('message', messageListener);
113
+ resolve({ success: false, message: 'Authentication cancelled (window closed).' });
114
+ }
115
+ }, 500);
116
+ });
117
+
118
+ } catch (error) {
119
+ const emsg = extractErrorMsg(error);
120
+ console.error(`${lc} ${emsg}`);
121
+ return { success: false, message: emsg };
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Checks if the current window was opened as an OAuth popup (`window.opener` exists)
127
+ * and has received an authorization code in the URL query string (`?code=...`).
128
+ * If so, posts `sso-oauth-callback` to the parent window and closes itself.
129
+ */
130
+ export function checkAndNotifyPopupOpener(): boolean {
131
+ if (typeof window === 'undefined' || !window.opener) {
132
+ return false;
133
+ }
134
+
135
+ const url = new URL(window.location.href);
136
+ const code = url.searchParams.get('code');
137
+ if (!code) {
138
+ return false;
139
+ }
140
+
141
+ const rawState = url.searchParams.get('state');
142
+ let stateObj: any;
143
+ if (rawState) {
144
+ try { stateObj = JSON.parse(atob(rawState)); } catch { }
145
+ }
146
+
147
+ window.opener.postMessage({
148
+ type: 'sso-oauth-callback',
149
+ code,
150
+ stateObj
151
+ }, window.location.origin);
152
+
153
+ window.close();
154
+ return true;
155
+ }
package/src/index.mts CHANGED
@@ -18,6 +18,9 @@ export * from "./api/ibgib-api-bridge.mjs";
18
18
 
19
19
  // Identity & Sync services
20
20
  export * from "./identity/keystone-sync-service.mjs";
21
+ export * from "./identity/custodian-delegate-helper.mjs";
22
+ // export * from "./identity/sso-callback-helper.mjs";
23
+ export * from "./identity/sso-popup-helper.mjs";
21
24
 
22
25
  // Identity UI components
23
26
  export * from "./ui/component/identity/index.mjs";
@@ -1,16 +1,15 @@
1
1
  <div id="container" class="component-container">
2
- <h2 id="title">Add New Device (Pairing)</h2>
2
+ <h2 id="title">Sign In New Device</h2>
3
3
  <div class="content">
4
- <p>Pair this device to your existing primary identity timeline.</p>
4
+ <p>Sign in on this device to add it to an existing account.</p>
5
5
 
6
6
  <div class="input-group">
7
- <label for="input-identifier">Username or Email</label>
8
- <input type="text" id="input-identifier" placeholder="Enter registered username or email..."
9
- class="text-input">
7
+ <label for="input-identifier">Email</label>
8
+ <input type="text" id="input-identifier" placeholder="Enter registered email..." class="text-input">
10
9
  </div>
11
10
 
12
11
  <div class="input-group">
13
- <label for="input-device-tag">Device Tag / Name *</label>
12
+ <label for="input-device-tag">Device Name</label>
14
13
  <input type="text" id="input-device-tag" placeholder="e.g. laptop, phone, living-room..."
15
14
  class="text-input">
16
15
  </div>
@@ -29,7 +28,7 @@
29
28
  </div>
30
29
 
31
30
  <div class="action-row">
32
- <button id="btn-add-device" class="action-btn">Pair & Add Device</button>
31
+ <button id="btn-add-device" class="action-btn">Sign In</button>
33
32
  </div>
34
33
 
35
34
  <div class="switch-workflow" style="margin-top: 1.2rem; text-align: center; font-size: 0.9rem; color: #a6adc8;">
@@ -2,30 +2,29 @@ import thisCss from "./add-device.css";
2
2
  import thisHtml from "./add-device.html";
3
3
  import commonComponentStylesCss from "../../common-component-styles.css";
4
4
 
5
- import { delay, extractErrorMsg, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
5
+ import { delay, extractErrorMsg, } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
6
6
  import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
7
7
  import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
8
8
  import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
9
- import { getGibInfo } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
10
9
  import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
11
10
  import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
12
11
  import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
13
12
  import { KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT, KEYSTONE_USERNAME_REGEXP } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
14
13
  import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
15
- import { getTimelinesGroupedByTjp, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
14
+ import { getTimelinesGroupedByTjp, getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
16
15
  import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
17
- import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
18
16
 
17
+ import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
19
18
  import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
20
- import {
21
- IbGibDynamicComponentMetaBase, IbGibFormInstanceBase,
22
- } from "../../ibgib-dynamic-component-bases.mjs";
19
+ import { IbGibDynamicComponentMetaBase, IbGibFormInstanceBase, } from "../../ibgib-dynamic-component-bases.mjs";
23
20
  import {
24
21
  ElementsBase, IbGibDynamicComponentInstance,
25
22
  IbGibDynamicComponentInstanceInitOpts,
26
23
  } from "../../component-types.mjs";
27
24
  import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestration.mjs";
28
- import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE } from "../../../ui-constants.mjs";
25
+ import {
26
+ EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE
27
+ } from "../../../ui-constants.mjs";
29
28
 
30
29
  export const ADD_DEVICE_COMPONENT_NAME = 'ibgib-add-device';
31
30
 
@@ -162,7 +161,7 @@ export class AddDeviceComponentInstance
162
161
  this.elements!.inputIdentifier.value = identifier;
163
162
  if (!identifier) {
164
163
  this.elements!.inputIdentifier.classList.add('invalid');
165
- this.setStatus("Please enter a registered username or email.", "error");
164
+ this.setStatus("Please enter a registered email.", "error");
166
165
  return;
167
166
  }
168
167
 
@@ -170,7 +169,7 @@ export class AddDeviceComponentInstance
170
169
  this.elements!.inputDeviceTag.value = deviceTag;
171
170
  if (!deviceTag || !KEYSTONE_USERNAME_REGEXP.test(deviceTag)) {
172
171
  this.elements!.inputDeviceTag.classList.add('invalid');
173
- this.setStatus("Please enter a valid Device Tag / Name (e.g. 'work-laptop').", "error");
172
+ this.setStatus("Please enter a valid Device Name (e.g. 'work-laptop').", "error");
174
173
  return;
175
174
  }
176
175
 
@@ -194,7 +193,6 @@ export class AddDeviceComponentInstance
194
193
  const space = await metaspace.getLocalUserSpace({});
195
194
  if (!space) { throw new Error("No default space found in metaspace."); }
196
195
 
197
- const keystoneService = new KeystoneService_V1();
198
196
 
199
197
  // 1a. First attempt to locate primary identity keystone locally in space
200
198
  const keystonesIndex = await metaspace.getSpecialIbGib({
@@ -225,7 +223,7 @@ export class AddDeviceComponentInstance
225
223
  if (resLookup.success && resLookup.primaryKeystoneIbGib) {
226
224
  const fetchedKeystone = resLookup.primaryKeystoneIbGib as KeystoneIbGib_V1;
227
225
  const domainAddr = resLookup.tjpAddr || getIbGibAddr({ ibGib: fetchedKeystone });
228
-
226
+
229
227
  // Fetch full domain graph from server so local space has all historical keystone frames
230
228
  const resGraph = await getApiBridge().getKeystoneGraph(domainAddr);
231
229
  if (resGraph.success && resGraph.graph) {
@@ -276,6 +274,8 @@ export class AddDeviceComponentInstance
276
274
  animationEmoji: '🔑'
277
275
  });
278
276
 
277
+ const primaryTjpAddr = getTjpAddr({ ibGib: primaryKeystone, defaultIfNone: 'incomingAddr' });
278
+ if (!primaryTjpAddr) { throw new Error(`(UNEXPECTED) primaryTjpAddr falsy? couldn't get tjpAddr from primaryKeystone? (E: bc8cc8678e1ce4194ab02646160fde26)`); }
279
279
  const delegateSecret = await deriveDelegateSecret({ masterSecret });
280
280
  const delegateBuilder = KeystoneProfileBuilder.buildKeystone('sync')
281
281
  .withUsername(`${primaryUsername}-sync-${deviceTag}`)
@@ -283,10 +283,12 @@ export class AddDeviceComponentInstance
283
283
  .withDetails({
284
284
  client: 'space-gib-web',
285
285
  role: 'sync-delegate',
286
- deviceTag
286
+ deviceTag,
287
+ primaryTjpAddr,
287
288
  });
288
289
  const delegateConfigs = await delegateBuilder.compileConfigs();
289
290
 
291
+ const keystoneService = new KeystoneService_V1();
290
292
  const delegateKeystone = await keystoneService.genesis({
291
293
  masterSecret: delegateSecret,
292
294
  configs: delegateConfigs,
@@ -331,11 +333,10 @@ export class AddDeviceComponentInstance
331
333
  animationEmoji: '🔄'
332
334
  });
333
335
 
334
- const primaryTjpAddr = getGibInfo({ ibGibAddr: getIbGibAddr({ ibGib: primaryKeystone }) }).tjpGib;
335
- const domainAddr = primaryTjpAddr ? getIbGibAddr({ ibGib: primaryKeystone }) : getIbGibAddr({ ibGib: primaryKeystone });
336
+ // const domainAddr = primaryTjpAddr ? getIbGibAddr({ ibGib: primaryKeystone }) : getIbGibAddr({ ibGib: primaryKeystone });
336
337
 
337
338
  const resEvolve = await getApiBridge().putEvolveKeystone({
338
- domainAddr,
339
+ domainAddr: primaryTjpAddr,
339
340
  keystoneIbGib: evolvedPrimaryKeystone,
340
341
  relatedKeystoneIbGibs: [delegateKeystone]
341
342
  });
@@ -146,7 +146,8 @@ export class IdentityHeaderComponentInstance
146
146
  };
147
147
  this.keystoneCache.set(addr, details);
148
148
  return details;
149
- } catch {
149
+ } catch (error) {
150
+ console.error(`${this.lc}[getKeystoneDetails] Registered keystone address ${addr} not found in local browser space: ${extractErrorMsg(error)} (E: a123456789abcdef0123456789c0002)`);
150
151
  return { username: '', description: '', isPrimary: false };
151
152
  }
152
153
  }
@@ -10,17 +10,105 @@ h2 {
10
10
  color: var(--clr-accent, #50fa7b);
11
11
  }
12
12
 
13
+ .mode-tabs {
14
+ display: flex;
15
+ gap: 0.5rem;
16
+ background: rgba(0, 0, 0, 0.25);
17
+ padding: 0.25rem;
18
+ border-radius: 10px;
19
+ border: 1px solid var(--clr-border, rgba(255, 255, 255, 0.1));
20
+ margin-bottom: 1rem;
21
+ }
22
+
23
+ .mode-tab {
24
+ flex: 1;
25
+ padding: 0.6rem 0.75rem;
26
+ border: none;
27
+ border-radius: 8px;
28
+ background: transparent;
29
+ color: var(--clr-text-secondary, #a6adc8);
30
+ font-weight: 600;
31
+ font-size: 0.9rem;
32
+ cursor: pointer;
33
+ transition: all 0.2s ease;
34
+ }
35
+
36
+ .mode-tab:hover {
37
+ color: var(--clr-text, #cdd6f4);
38
+ background: rgba(255, 255, 255, 0.05);
39
+ }
40
+
41
+ .mode-tab.active {
42
+ background: #45475a;
43
+ color: #89b4fa;
44
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
45
+ }
46
+
13
47
  .subtitle {
14
48
  color: var(--clr-text-secondary, #a6adc8);
15
- font-size: 0.95rem;
49
+ font-size: 0.92rem;
16
50
  margin: 0;
51
+ line-height: 1.45;
17
52
  }
18
53
 
19
54
  .content {
20
- margin-top: 1.5rem;
55
+ margin-top: 1.25rem;
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 1.25rem;
59
+ }
60
+
61
+ .tab-panel.hidden, .hidden {
62
+ display: none !important;
63
+ }
64
+
65
+ .sso-buttons {
21
66
  display: flex;
22
67
  flex-direction: column;
23
- gap: 1.5rem;
68
+ gap: 0.75rem;
69
+ }
70
+
71
+ .sso-btn {
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ gap: 0.75rem;
76
+ padding: 0.8rem 1.25rem;
77
+ border: 1px solid rgba(255, 255, 255, 0.15);
78
+ border-radius: 8px;
79
+ background: #1e1e2e;
80
+ color: #cdd6f4;
81
+ font-size: 0.95rem;
82
+ font-weight: 600;
83
+ cursor: pointer;
84
+ transition: transform 0.15s ease, background 0.15s ease;
85
+ }
86
+
87
+ .sso-btn:hover {
88
+ transform: translateY(-1px);
89
+ background: #313244;
90
+ border-color: #89b4fa;
91
+ }
92
+
93
+ .divider {
94
+ display: flex;
95
+ align-items: center;
96
+ text-align: center;
97
+ margin: 0.5rem 0;
98
+ color: #a6adc8;
99
+ font-size: 0.75rem;
100
+ font-weight: 700;
101
+ letter-spacing: 1px;
102
+ }
103
+
104
+ .divider::before, .divider::after {
105
+ content: '';
106
+ flex: 1;
107
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
108
+ }
109
+
110
+ .divider span {
111
+ padding: 0 0.75rem;
24
112
  }
25
113
 
26
114
  .input-group {
@@ -34,8 +122,25 @@ h2 {
34
122
  color: var(--clr-text-secondary, #a6adc8);
35
123
  }
36
124
 
125
+ .text-input {
126
+ width: 100%;
127
+ padding: 0.75rem 1rem;
128
+ border-radius: 8px;
129
+ border: 1px solid rgba(255, 255, 255, 0.15);
130
+ background: #11111b;
131
+ color: #cdd6f4;
132
+ font-size: 0.95rem;
133
+ box-sizing: border-box;
134
+ }
135
+
136
+ .text-input:focus {
137
+ outline: none;
138
+ border-color: #89b4fa;
139
+ box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.2);
140
+ }
141
+
37
142
  .action-row {
38
- margin-top: 1rem;
143
+ margin-top: 0.5rem;
39
144
  width: 100%;
40
145
  }
41
146
 
@@ -65,10 +170,6 @@ h2 {
65
170
  margin-top: 0.5rem;
66
171
  }
67
172
 
68
- .status-area.hidden {
69
- display: none;
70
- }
71
-
72
173
  .result-area {
73
174
  padding: 1.5rem;
74
175
  background: rgba(255, 255, 255, 0.03);
@@ -77,7 +178,6 @@ h2 {
77
178
  display: flex;
78
179
  flex-direction: column;
79
180
  gap: 0.75rem;
80
- transition: opacity var(--t-med, 0.2s ease);
81
181
  }
82
182
 
83
183
  .result-area.collapsed {
@@ -94,4 +194,9 @@ h2 {
94
194
  font-size: 0.8rem;
95
195
  color: var(--clr-warn, #f38ba8);
96
196
  margin: 0.5rem 0 0;
197
+ }
198
+
199
+ input::placeholder {
200
+ color: rgba(255, 255, 255, 0.35);
201
+ opacity: 1;
97
202
  }