@ibgib/web-gib 0.0.59 → 0.0.60
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.
- package/dist/AUTO-GENERATED-version.d.mts +1 -1
- package/dist/AUTO-GENERATED-version.mjs +1 -1
- package/dist/identity/custodian-delegate-helper.d.mts.map +1 -1
- package/dist/identity/custodian-delegate-helper.mjs +20 -3
- package/dist/identity/custodian-delegate-helper.mjs.map +1 -1
- package/dist/identity/identity-constants.mjs +2 -2
- package/dist/identity/identity-constants.mjs.map +1 -1
- package/dist/identity/identity-types.d.mts +4 -4
- package/dist/identity/identity-types.d.mts.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.css +93 -17
- package/dist/ui/component/identity/add-device/add-device.d.mts +24 -7
- package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
- package/dist/ui/component/identity/add-device/add-device.html +97 -23
- package/dist/ui/component/identity/add-device/add-device.mjs +490 -186
- package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +7 -4
- package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.css +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +8 -8
- package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
- package/dist/ui/component/identity/keystone-details/keystone-details.html +29 -20
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs +51 -51
- package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.d.mts +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
- package/dist/ui/component/identity/remove-device/remove-device.html +3 -3
- package/dist/ui/component/identity/remove-device/remove-device.mjs +7 -7
- package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
- package/dist/util/device-helper.d.mts +30 -0
- package/dist/util/device-helper.d.mts.map +1 -0
- package/dist/util/device-helper.mjs +90 -0
- package/dist/util/device-helper.mjs.map +1 -0
- package/dist/util/device-helper.respec.d.mts +2 -0
- package/dist/util/device-helper.respec.d.mts.map +1 -0
- package/dist/util/device-helper.respec.mjs +83 -0
- package/dist/util/device-helper.respec.mjs.map +1 -0
- package/package.json +3 -3
- package/src/AUTO-GENERATED-version.mts +1 -1
- package/src/identity/custodian-delegate-helper.mts +28 -3
- package/src/identity/identity-constants.mts +2 -2
- package/src/identity/identity-types.mts +4 -4
- package/src/ui/component/identity/add-device/add-device.css +93 -17
- package/src/ui/component/identity/add-device/add-device.html +97 -23
- package/src/ui/component/identity/add-device/add-device.mts +577 -194
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +7 -4
- package/src/ui/component/identity/keystone-details/keystone-details.css +1 -1
- package/src/ui/component/identity/keystone-details/keystone-details.html +29 -20
- package/src/ui/component/identity/keystone-details/keystone-details.mts +57 -57
- package/src/ui/component/identity/remove-device/remove-device.html +3 -3
- package/src/ui/component/identity/remove-device/remove-device.mts +8 -8
- package/src/util/device-helper.mts +104 -0
- package/src/util/device-helper.respec.mts +90 -0
|
@@ -13,6 +13,8 @@ import { KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT, KEYSTONE_USERNAME_REGEXP } f
|
|
|
13
13
|
import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/strategy/hash-reveal-v1/hash-reveal-v1.mjs";
|
|
14
14
|
import { getTimelinesGroupedByTjp, getTjpAddr, toDto } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
15
15
|
import { updateSpecialIndex } from "@ibgib/core-gib/dist/timeline/timeline-api.mjs";
|
|
16
|
+
import { validateEmail } from "@ibgib/core-gib/dist/keystone/keystone-helpers.mjs";
|
|
17
|
+
import { FlatIbGibGraph } from "@ibgib/core-gib/dist/common/other/graph-types.mjs";
|
|
16
18
|
|
|
17
19
|
import { getApiBridge } from "../../../../api/ibgib-api-bridge.mjs";
|
|
18
20
|
import { getGlobalMetaspace_waitIfNeeded } from "../../../../helpers.mjs";
|
|
@@ -25,6 +27,9 @@ import { getComponentCtorArg } from "../../../../app-bootstrap/init-orchestratio
|
|
|
25
27
|
import {
|
|
26
28
|
EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_UI_MESSAGE
|
|
27
29
|
} from "../../../ui-constants.mjs";
|
|
30
|
+
import { generateDefaultDeviceTag } from "../../../../util/device-helper.mjs";
|
|
31
|
+
import { executeSsoOAuthPopup } from "../../../../identity/sso-popup-helper.mjs";
|
|
32
|
+
import { deriveLocalCustodianDelegateSecret } from "../../../../identity/custodian-delegate-helper.mjs";
|
|
28
33
|
|
|
29
34
|
export const ADD_DEVICE_COMPONENT_NAME = 'ibgib-add-device';
|
|
30
35
|
|
|
@@ -65,11 +70,20 @@ export class AddDeviceComponentMeta extends IbGibDynamicComponentMetaBase {
|
|
|
65
70
|
interface AddDeviceElements extends ElementsBase {
|
|
66
71
|
containerEl: HTMLDivElement;
|
|
67
72
|
titleEl: HTMLHeadingElement;
|
|
73
|
+
subtitleEl: HTMLParagraphElement;
|
|
74
|
+
surfaceEntryEl: HTMLDivElement;
|
|
75
|
+
btnGoogleSso: HTMLButtonElement;
|
|
76
|
+
btnGithubSso: HTMLButtonElement;
|
|
68
77
|
inputIdentifier: HTMLInputElement;
|
|
78
|
+
btnContinueEmail: HTMLButtonElement;
|
|
79
|
+
sectionCodeVerification: HTMLDivElement;
|
|
80
|
+
inputCode: HTMLInputElement;
|
|
81
|
+
btnVerifyCode: HTMLButtonElement;
|
|
82
|
+
sectionSovereignPassword: HTMLDivElement;
|
|
83
|
+
inputPassword: HTMLInputElement;
|
|
84
|
+
btnAuthorizePassword: HTMLButtonElement;
|
|
69
85
|
inputDeviceTag: HTMLInputElement;
|
|
70
|
-
inputPassphrase: HTMLInputElement;
|
|
71
86
|
checkboxMakeActive: HTMLInputElement;
|
|
72
|
-
btnAddDeviceEl: HTMLButtonElement;
|
|
73
87
|
statusArea: HTMLDivElement;
|
|
74
88
|
statusText: HTMLParagraphElement;
|
|
75
89
|
resultAreaEl: HTMLDivElement;
|
|
@@ -85,6 +99,12 @@ export class AddDeviceComponentInstance
|
|
|
85
99
|
|
|
86
100
|
protected lc: string = `[AddDeviceComponentInstance]`;
|
|
87
101
|
|
|
102
|
+
// Staged state across progressive surfaces
|
|
103
|
+
private _stagedEmail?: string;
|
|
104
|
+
private _stagedPrimaryTjpAddr?: string;
|
|
105
|
+
private _stagedPrimaryKeystone?: KeystoneIbGib_V1;
|
|
106
|
+
private _stagedPrimaryGraph?: FlatIbGibGraph;
|
|
107
|
+
|
|
88
108
|
constructor() {
|
|
89
109
|
super();
|
|
90
110
|
}
|
|
@@ -94,21 +114,37 @@ export class AddDeviceComponentInstance
|
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
override async created(): Promise<void> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
const shadow = this.shadowRoot!;
|
|
118
|
+
const elements: AddDeviceElements = {} as AddDeviceElements;
|
|
119
|
+
|
|
120
|
+
elements.containerEl = shadow.getElementById('container') as HTMLDivElement;
|
|
121
|
+
elements.titleEl = shadow.getElementById('title') as HTMLHeadingElement;
|
|
122
|
+
elements.subtitleEl = shadow.getElementById('subtitle') as HTMLParagraphElement;
|
|
123
|
+
elements.surfaceEntryEl = shadow.getElementById('surface-entry') as HTMLDivElement;
|
|
124
|
+
elements.btnGoogleSso = shadow.getElementById('btn-google-sso') as HTMLButtonElement;
|
|
125
|
+
elements.btnGithubSso = shadow.getElementById('btn-github-sso') as HTMLButtonElement;
|
|
126
|
+
elements.inputIdentifier = shadow.getElementById('input-identifier') as HTMLInputElement;
|
|
127
|
+
elements.btnContinueEmail = shadow.getElementById('btn-continue-email') as HTMLButtonElement;
|
|
128
|
+
elements.sectionCodeVerification = shadow.getElementById('section-code-verification') as HTMLDivElement;
|
|
129
|
+
elements.inputCode = shadow.getElementById('input-code') as HTMLInputElement;
|
|
130
|
+
elements.btnVerifyCode = shadow.getElementById('btn-verify-code') as HTMLButtonElement;
|
|
131
|
+
elements.sectionSovereignPassword = shadow.getElementById('section-sovereign-password') as HTMLDivElement;
|
|
132
|
+
elements.inputPassword = shadow.getElementById('input-password') as HTMLInputElement;
|
|
133
|
+
elements.btnAuthorizePassword = shadow.getElementById('btn-authorize-password') as HTMLButtonElement;
|
|
134
|
+
elements.inputDeviceTag = shadow.getElementById('input-device-tag') as HTMLInputElement;
|
|
135
|
+
elements.checkboxMakeActive = shadow.getElementById('checkbox-make-active') as HTMLInputElement;
|
|
136
|
+
elements.statusArea = shadow.getElementById('status-area') as HTMLDivElement;
|
|
137
|
+
elements.statusText = shadow.getElementById('status-text') as HTMLParagraphElement;
|
|
138
|
+
elements.resultAreaEl = shadow.getElementById('result-area') as HTMLDivElement;
|
|
139
|
+
elements.delegateAddrEl = shadow.getElementById('delegate-addr') as HTMLElement;
|
|
140
|
+
elements.contentEl = elements.containerEl;
|
|
141
|
+
|
|
142
|
+
this.elements = elements;
|
|
143
|
+
|
|
144
|
+
// Auto-generate clean, friendly default device tag
|
|
145
|
+
if (elements.inputDeviceTag && !elements.inputDeviceTag.value) {
|
|
146
|
+
elements.inputDeviceTag.value = generateDefaultDeviceTag();
|
|
147
|
+
}
|
|
112
148
|
|
|
113
149
|
this.initHandlers();
|
|
114
150
|
await this.renderUI();
|
|
@@ -119,7 +155,12 @@ export class AddDeviceComponentInstance
|
|
|
119
155
|
}
|
|
120
156
|
|
|
121
157
|
private initHandlers() {
|
|
122
|
-
|
|
158
|
+
const elements = this.elements!;
|
|
159
|
+
elements.btnGoogleSso?.addEventListener('click', () => this.handleSsoClick('google'));
|
|
160
|
+
elements.btnGithubSso?.addEventListener('click', () => this.handleSsoClick('github'));
|
|
161
|
+
elements.btnContinueEmail?.addEventListener('click', () => this.handleContinueEmail());
|
|
162
|
+
elements.btnVerifyCode?.addEventListener('click', () => this.handleVerifyCode());
|
|
163
|
+
elements.btnAuthorizePassword?.addEventListener('click', () => this.handleAuthorizePassword());
|
|
123
164
|
|
|
124
165
|
this.shadowRoot!.getElementById('btn-switch-create-account')?.addEventListener('click', (e) => {
|
|
125
166
|
e.preventDefault();
|
|
@@ -130,158 +171,432 @@ export class AddDeviceComponentInstance
|
|
|
130
171
|
}));
|
|
131
172
|
});
|
|
132
173
|
|
|
133
|
-
|
|
134
|
-
|
|
174
|
+
elements.inputIdentifier?.addEventListener('input', () => {
|
|
175
|
+
elements.inputIdentifier.classList.remove('invalid');
|
|
176
|
+
});
|
|
177
|
+
elements.inputCode?.addEventListener('input', () => {
|
|
178
|
+
elements.inputCode.classList.remove('invalid');
|
|
135
179
|
});
|
|
136
|
-
|
|
137
|
-
|
|
180
|
+
elements.inputPassword?.addEventListener('input', () => {
|
|
181
|
+
elements.inputPassword.classList.remove('invalid');
|
|
138
182
|
});
|
|
139
|
-
|
|
140
|
-
|
|
183
|
+
elements.inputDeviceTag?.addEventListener('input', () => {
|
|
184
|
+
elements.inputDeviceTag.classList.remove('invalid');
|
|
141
185
|
});
|
|
142
186
|
}
|
|
143
187
|
|
|
144
|
-
private
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
188
|
+
private getDeviceTag(): string {
|
|
189
|
+
const elements = this.elements!;
|
|
190
|
+
const raw = elements.inputDeviceTag?.value?.trim();
|
|
191
|
+
if (raw && KEYSTONE_USERNAME_REGEXP.test(raw)) {
|
|
192
|
+
return raw;
|
|
148
193
|
}
|
|
194
|
+
const generated = generateDefaultDeviceTag();
|
|
195
|
+
if (elements.inputDeviceTag) {
|
|
196
|
+
elements.inputDeviceTag.value = generated;
|
|
197
|
+
}
|
|
198
|
+
return generated;
|
|
149
199
|
}
|
|
150
200
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
201
|
+
// =========================================================================
|
|
202
|
+
// Flow 1: OAuth2 SSO Sign In (Google / GitHub)
|
|
203
|
+
// =========================================================================
|
|
204
|
+
|
|
205
|
+
protected async handleSsoClick(providerId: 'google' | 'github') {
|
|
206
|
+
const lc = `${this.lc}[${this.handleSsoClick.name}]`;
|
|
207
|
+
const providerName = providerId === 'google' ? 'Google' : 'GitHub';
|
|
208
|
+
|
|
209
|
+
this.setStatus(`Opening ${providerName} sign-in window...`, 'info');
|
|
210
|
+
this.showBusy({
|
|
211
|
+
isBusy: true,
|
|
212
|
+
title: 'Sign In via SSO',
|
|
213
|
+
msg: `Opening ${providerName} authorization window...\nPlease complete sign-in in the popup window.`,
|
|
214
|
+
animationEmoji: '🌐'
|
|
215
|
+
});
|
|
216
|
+
|
|
156
217
|
try {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const identifier = this.elements!.inputIdentifier.value.trim();
|
|
161
|
-
this.elements!.inputIdentifier.value = identifier;
|
|
162
|
-
if (!identifier) {
|
|
163
|
-
this.elements!.inputIdentifier.classList.add('invalid');
|
|
164
|
-
this.setStatus("Please enter a registered email.", "error");
|
|
218
|
+
const resPopup = await executeSsoOAuthPopup({ providerId });
|
|
219
|
+
if (!resPopup.success || !resPopup.code) {
|
|
220
|
+
this.setStatus(resPopup.message || `${providerName} sign-in cancelled.`, 'error');
|
|
165
221
|
return;
|
|
166
222
|
}
|
|
167
223
|
|
|
168
|
-
|
|
169
|
-
this.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
224
|
+
this.setStatus(`Validating ${providerName} authorization...`, 'info');
|
|
225
|
+
this.showBusy({
|
|
226
|
+
isBusy: true,
|
|
227
|
+
title: 'Exchanging SSO Code...',
|
|
228
|
+
msg: `Validating ${providerName} authorization with server...`,
|
|
229
|
+
animationEmoji: '🔐'
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const apiBridge = getApiBridge();
|
|
233
|
+
const resLogin = await apiBridge.postSsoLogin({
|
|
234
|
+
code: resPopup.code,
|
|
235
|
+
providerId,
|
|
236
|
+
redirectUri: resPopup.redirectUri!
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (!resLogin.success || !resLogin.primaryTjpAddr || !resLogin.custodianReqs) {
|
|
240
|
+
this.setStatus(resLogin.message || `${providerName} login failed.`, 'error');
|
|
173
241
|
return;
|
|
174
242
|
}
|
|
175
243
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
244
|
+
const { primaryTjpAddr, custodianReqs, primaryKeystone } = resLogin;
|
|
245
|
+
const email = resLogin.userInfo?.email || '';
|
|
246
|
+
const username = email ? email.split('@')[0] : 'user';
|
|
247
|
+
|
|
248
|
+
await this.completeCustodialDelegateOnboarding({
|
|
249
|
+
primaryTjpAddr,
|
|
250
|
+
custodianReqs,
|
|
251
|
+
fallbackPrimaryKeystone: primaryKeystone,
|
|
252
|
+
fallbackPrimaryGraph: resLogin.primaryGraph,
|
|
253
|
+
email,
|
|
254
|
+
username,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const emsg = extractErrorMsg(error);
|
|
259
|
+
console.error(`${lc} SSO login error: ${emsg}`, error);
|
|
260
|
+
this.setStatus(`Error: ${emsg}`, 'error');
|
|
261
|
+
} finally {
|
|
262
|
+
this.showBusy({ isBusy: false });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// =========================================================================
|
|
267
|
+
// Flow 2: Email Sign In (2FA OTP Gate)
|
|
268
|
+
// =========================================================================
|
|
269
|
+
|
|
270
|
+
protected async handleContinueEmail() {
|
|
271
|
+
const lc = `${this.lc}[${this.handleContinueEmail.name}]`;
|
|
272
|
+
const elements = this.elements!;
|
|
273
|
+
const email = elements.inputIdentifier?.value?.trim();
|
|
274
|
+
if (!email || !validateEmail(email)) {
|
|
275
|
+
elements.inputIdentifier.classList.add('invalid');
|
|
276
|
+
this.setStatus('Please enter a valid email address.', 'error');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
elements.btnContinueEmail.disabled = true;
|
|
281
|
+
elements.btnContinueEmail.textContent = 'Checking...';
|
|
282
|
+
this.setStatus('Checking identity on server...', 'info');
|
|
283
|
+
this.showBusy({
|
|
284
|
+
isBusy: true,
|
|
285
|
+
title: 'Locating Identity...',
|
|
286
|
+
msg: 'Locating account on server...',
|
|
287
|
+
animationEmoji: '🔍'
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
// 1. Look up primary identity keystone for this email
|
|
292
|
+
const resLookup = await getApiBridge().lookupPrimaryKeystone({ identifier: email });
|
|
293
|
+
if (!resLookup.success || !resLookup.tjpAddr) {
|
|
294
|
+
this.setStatus('No account found for this email address. Please click "Create Account" below.', 'error');
|
|
181
295
|
return;
|
|
182
296
|
}
|
|
183
297
|
|
|
184
|
-
this.
|
|
298
|
+
this._stagedEmail = email;
|
|
299
|
+
this._stagedPrimaryTjpAddr = resLookup.tjpAddr;
|
|
300
|
+
|
|
301
|
+
// 2. Dispatch 6-digit verification code to email
|
|
302
|
+
this.setStatus('Dispatching 6-digit verification code...', 'info');
|
|
185
303
|
this.showBusy({
|
|
186
304
|
isBusy: true,
|
|
187
|
-
title: '
|
|
188
|
-
msg: '
|
|
189
|
-
animationEmoji: '
|
|
305
|
+
title: 'Sending Code...',
|
|
306
|
+
msg: 'Dispatching 6-digit verification code to your email...',
|
|
307
|
+
animationEmoji: '✉️'
|
|
190
308
|
});
|
|
191
309
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
310
|
+
const resCode = await getApiBridge().postVerificationCode({
|
|
311
|
+
email,
|
|
312
|
+
primaryTjpAddr: this._stagedPrimaryTjpAddr
|
|
313
|
+
});
|
|
314
|
+
if (!resCode.success) {
|
|
315
|
+
this.setStatus(resCode.message || 'Failed to dispatch verification code.', 'error');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
195
318
|
|
|
319
|
+
// 3. Transition UI to 6-digit PIN input prompt
|
|
320
|
+
elements.sectionCodeVerification.classList.remove('hidden');
|
|
321
|
+
elements.sectionCodeVerification.scrollIntoView({ behavior: 'smooth' });
|
|
322
|
+
this.setStatus(`Verification code sent to ${email}! Enter the 6-digit code above to continue.`, 'info');
|
|
323
|
+
elements.inputCode?.focus();
|
|
196
324
|
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const emsg = extractErrorMsg(error);
|
|
327
|
+
console.error(`${lc} ${emsg}`, error);
|
|
328
|
+
this.setStatus(`Error: ${emsg}`, 'error');
|
|
329
|
+
} finally {
|
|
330
|
+
elements.btnContinueEmail.disabled = false;
|
|
331
|
+
elements.btnContinueEmail.textContent = 'Continue with Email';
|
|
332
|
+
this.showBusy({ isBusy: false });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
protected async handleVerifyCode() {
|
|
337
|
+
const lc = `${this.lc}[${this.handleVerifyCode.name}]`;
|
|
338
|
+
const elements = this.elements!;
|
|
339
|
+
const code = elements.inputCode?.value?.trim();
|
|
340
|
+
const email = this._stagedEmail;
|
|
341
|
+
const primaryTjpAddr = this._stagedPrimaryTjpAddr;
|
|
342
|
+
|
|
343
|
+
if (!email || !primaryTjpAddr) {
|
|
344
|
+
this.setStatus('Please enter your email and request a code first.', 'error');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!code || code.length !== 6) {
|
|
349
|
+
elements.inputCode.classList.add('invalid');
|
|
350
|
+
this.setStatus('Please enter a 6-digit verification code.', 'error');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
elements.btnVerifyCode.disabled = true;
|
|
355
|
+
elements.btnVerifyCode.textContent = 'Verifying...';
|
|
356
|
+
this.setStatus('Verifying 6-digit code...', 'info');
|
|
357
|
+
this.showBusy({
|
|
358
|
+
isBusy: true,
|
|
359
|
+
title: 'Verifying Code...',
|
|
360
|
+
msg: 'Validating verification code with server...',
|
|
361
|
+
animationEmoji: '🔐'
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
// First, attempt Custodian Genesis / Sign-In resolution
|
|
366
|
+
const resGenesis = await getApiBridge().postCustodianGenesis({ email, code });
|
|
367
|
+
|
|
368
|
+
if (resGenesis.success && resGenesis.primaryTjpAddr && resGenesis.custodianReqs) {
|
|
369
|
+
// -------------------------------------------------------------
|
|
370
|
+
// Path A: Custodial Account (100% Passwordless)
|
|
371
|
+
// -------------------------------------------------------------
|
|
372
|
+
const { primaryTjpAddr, custodianReqs, primaryKeystone, primaryGraph } = resGenesis;
|
|
373
|
+
const username = email ? email.split('@')[0] : 'user';
|
|
374
|
+
|
|
375
|
+
await this.completeCustodialDelegateOnboarding({
|
|
376
|
+
primaryTjpAddr,
|
|
377
|
+
custodianReqs,
|
|
378
|
+
fallbackPrimaryKeystone: primaryKeystone,
|
|
379
|
+
fallbackPrimaryGraph: primaryGraph,
|
|
380
|
+
email,
|
|
381
|
+
username,
|
|
382
|
+
});
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// -----------------------------------------------------------------
|
|
387
|
+
// Path B: Sovereign Account (Fetch Verified Graph, Reveal Password Prompt)
|
|
388
|
+
// -----------------------------------------------------------------
|
|
389
|
+
const resVerify = await getApiBridge().postVerifyCode({ email, primaryTjpAddr, code });
|
|
390
|
+
if (!resVerify.success) {
|
|
391
|
+
this.setStatus(resVerify.message || 'Verification failed. Please check the code.', 'error');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
this.setStatus('Retrieving identity graph from server...', 'info');
|
|
396
|
+
this.showBusy({
|
|
397
|
+
isBusy: true,
|
|
398
|
+
title: 'Loading Identity Graph...',
|
|
399
|
+
msg: 'Fetching cryptographic identity timeline from server...',
|
|
400
|
+
animationEmoji: '📥'
|
|
202
401
|
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const found = res?.ibGibs?.[0] as KeystoneIbGib_V1 | undefined;
|
|
209
|
-
if (found && found.data) {
|
|
210
|
-
const username = found.data.frameDetails?.username ?? '';
|
|
211
|
-
const emails = found.data.emails ?? found.data.frameDetails?.emails ?? [];
|
|
212
|
-
if (username.toLowerCase() === identifier.toLowerCase() || emails.some((e: string) => e.toLowerCase() === identifier.toLowerCase())) {
|
|
213
|
-
primaryKeystone = found;
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
402
|
+
|
|
403
|
+
const resGraph = await getApiBridge().getKeystoneGraph(primaryTjpAddr);
|
|
404
|
+
if (!resGraph.success || !resGraph.graph) {
|
|
405
|
+
this.setStatus('Failed to retrieve identity graph from server.', 'error');
|
|
406
|
+
return;
|
|
217
407
|
}
|
|
218
408
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (resGraph.success && resGraph.graph) {
|
|
230
|
-
const graphIbGibs = Object.values(resGraph.graph);
|
|
231
|
-
await metaspace.put({ ibGibs: graphIbGibs, space });
|
|
232
|
-
|
|
233
|
-
// Order timelines by TJP and register each frame in ascending order
|
|
234
|
-
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: graphIbGibs });
|
|
235
|
-
for (const tjpKey of Object.keys(timelinesMap)) {
|
|
236
|
-
const timeline = timelinesMap[tjpKey];
|
|
237
|
-
for (const frame of timeline) {
|
|
238
|
-
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Set primaryKeystone to the latest frame in the domain timeline
|
|
243
|
-
const domainTimeline = timelinesMap[domainAddr] || timelinesMap[Object.keys(timelinesMap)[0]];
|
|
244
|
-
if (domainTimeline && domainTimeline.length > 0) {
|
|
245
|
-
primaryKeystone = domainTimeline[domainTimeline.length - 1] as KeystoneIbGib_V1;
|
|
246
|
-
} else {
|
|
247
|
-
primaryKeystone = fetchedKeystone;
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
await metaspace.put({ ibGibs: [fetchedKeystone], space });
|
|
251
|
-
await metaspace.registerNewIbGib({ ibGib: fetchedKeystone, space });
|
|
252
|
-
primaryKeystone = fetchedKeystone;
|
|
409
|
+
this._stagedPrimaryGraph = resGraph.graph;
|
|
410
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
411
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
412
|
+
if (space) {
|
|
413
|
+
const graphIbGibs = Object.values(resGraph.graph);
|
|
414
|
+
await metaspace.put({ ibGibs: graphIbGibs, space });
|
|
415
|
+
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: graphIbGibs });
|
|
416
|
+
for (const tjpKey of Object.keys(timelinesMap)) {
|
|
417
|
+
for (const frame of timelinesMap[tjpKey]) {
|
|
418
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
253
419
|
}
|
|
254
420
|
}
|
|
421
|
+
const domainTimeline = timelinesMap[primaryTjpAddr] || timelinesMap[Object.keys(timelinesMap)[0]];
|
|
422
|
+
if (domainTimeline && domainTimeline.length > 0) {
|
|
423
|
+
this._stagedPrimaryKeystone = domainTimeline[domainTimeline.length - 1] as KeystoneIbGib_V1;
|
|
424
|
+
}
|
|
255
425
|
}
|
|
256
426
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
427
|
+
// Hide OTP section, reveal Sovereign Master Password prompt
|
|
428
|
+
elements.sectionCodeVerification.classList.add('hidden');
|
|
429
|
+
elements.sectionSovereignPassword.classList.remove('hidden');
|
|
430
|
+
elements.sectionSovereignPassword.scrollIntoView({ behavior: 'smooth' });
|
|
431
|
+
this.setStatus('2FA verified! Please enter your master password to authorize this device.', 'info');
|
|
432
|
+
elements.inputPassword?.focus();
|
|
433
|
+
|
|
434
|
+
} catch (error) {
|
|
435
|
+
const emsg = extractErrorMsg(error);
|
|
436
|
+
console.error(`${lc} ${emsg}`, error);
|
|
437
|
+
this.setStatus(`Error: ${emsg}`, 'error');
|
|
438
|
+
} finally {
|
|
439
|
+
elements.btnVerifyCode.disabled = false;
|
|
440
|
+
elements.btnVerifyCode.textContent = 'Verify Code';
|
|
441
|
+
this.showBusy({ isBusy: false });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// =========================================================================
|
|
446
|
+
// DRY Custodial Delegation Helper (Shared by SSO & Email OTP)
|
|
447
|
+
// =========================================================================
|
|
448
|
+
|
|
449
|
+
private async completeCustodialDelegateOnboarding({
|
|
450
|
+
primaryTjpAddr,
|
|
451
|
+
custodianReqs,
|
|
452
|
+
fallbackPrimaryKeystone,
|
|
453
|
+
fallbackPrimaryGraph,
|
|
454
|
+
email,
|
|
455
|
+
username,
|
|
456
|
+
}: {
|
|
457
|
+
primaryTjpAddr: string;
|
|
458
|
+
custodianReqs: any;
|
|
459
|
+
fallbackPrimaryKeystone?: any;
|
|
460
|
+
fallbackPrimaryGraph?: FlatIbGibGraph;
|
|
461
|
+
email: string;
|
|
462
|
+
username?: string;
|
|
463
|
+
}): Promise<void> {
|
|
464
|
+
const lc = `${this.lc}[${this.completeCustodialDelegateOnboarding.name}]`;
|
|
465
|
+
const deviceTag = this.getDeviceTag();
|
|
466
|
+
|
|
467
|
+
this.setStatus('Deriving local device delegate keys...', 'info');
|
|
468
|
+
this.showBusy({
|
|
469
|
+
isBusy: true,
|
|
470
|
+
title: 'Creating Sync Delegate...',
|
|
471
|
+
msg: `Generating cryptographic sync delegate keys for ${deviceTag}...`,
|
|
472
|
+
animationEmoji: '🔑'
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const delegateSecret = await deriveLocalCustodianDelegateSecret({ primaryTjpAddr });
|
|
476
|
+
const userAlias = username || (email ? email.split('@')[0] : 'user');
|
|
477
|
+
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
478
|
+
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
479
|
+
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)');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(custodianSyncProfileName.trim() as any)
|
|
483
|
+
.withUsername(`${userAlias}-sync-${deviceTag}`)
|
|
484
|
+
.withDescription(`Sync Delegate for ${userAlias} on ${deviceTag}`)
|
|
485
|
+
.withDetails({
|
|
486
|
+
client: 'space-gib-web',
|
|
487
|
+
role: 'sync-delegate',
|
|
488
|
+
deviceTag,
|
|
489
|
+
primaryTjpAddr,
|
|
490
|
+
custodianReqs,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const configs = await delegateBuilder.compileConfigs();
|
|
494
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
495
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
496
|
+
if (!space) {
|
|
497
|
+
this.setStatus('Could not access local user space.', 'error');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const keystoneService = new KeystoneService_V1();
|
|
502
|
+
const delegateKeystone = await keystoneService.genesis({
|
|
503
|
+
masterSecret: delegateSecret,
|
|
504
|
+
configs,
|
|
505
|
+
metaspace,
|
|
506
|
+
space,
|
|
507
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
508
|
+
isPrimary: false
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
this.setStatus('Registering device delegate with custodian server...', 'info');
|
|
512
|
+
this.showBusy({
|
|
513
|
+
isBusy: true,
|
|
514
|
+
title: 'Registering Delegate...',
|
|
515
|
+
msg: 'Registering local delegate with custodian server...',
|
|
516
|
+
animationEmoji: '🔄'
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
|
|
520
|
+
if (!resRegister.success) {
|
|
521
|
+
this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Finalize onboarding into local space & special index
|
|
526
|
+
await this.finalizeDeviceOnboarding({
|
|
527
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || fallbackPrimaryKeystone,
|
|
528
|
+
primaryGraph: resRegister.primaryGraph || fallbackPrimaryGraph,
|
|
529
|
+
delegateKeystone,
|
|
530
|
+
primaryTjpAddr,
|
|
531
|
+
delegateSecret,
|
|
532
|
+
makeActive: this.elements?.checkboxMakeActive?.checked ?? true
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// =========================================================================
|
|
537
|
+
// Flow 3: Sovereign Master Password Unlock & Local Evolution
|
|
538
|
+
// =========================================================================
|
|
539
|
+
|
|
540
|
+
protected async handleAuthorizePassword() {
|
|
541
|
+
const lc = `${this.lc}[${this.handleAuthorizePassword.name}]`;
|
|
542
|
+
const elements = this.elements!;
|
|
543
|
+
const masterSecret = elements.inputPassword?.value?.trim();
|
|
544
|
+
const primaryKeystone = this._stagedPrimaryKeystone;
|
|
545
|
+
const primaryTjpAddr = this._stagedPrimaryTjpAddr;
|
|
546
|
+
|
|
547
|
+
if (!masterSecret) {
|
|
548
|
+
elements.inputPassword.classList.add('invalid');
|
|
549
|
+
this.setStatus('Please enter your master password.', 'error');
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!primaryKeystone || !primaryTjpAddr) {
|
|
554
|
+
this.setStatus('Missing staged primary identity context. Please restart sign-in.', 'error');
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
elements.btnAuthorizePassword.disabled = true;
|
|
559
|
+
elements.btnAuthorizePassword.textContent = 'Authorizing...';
|
|
560
|
+
this.setStatus('Verifying master password locally...', 'info');
|
|
561
|
+
this.showBusy({
|
|
562
|
+
isBusy: true,
|
|
563
|
+
title: 'Verifying Password...',
|
|
564
|
+
msg: 'Verifying cryptographic password locally in browser...',
|
|
565
|
+
animationEmoji: '🔐'
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
const keystoneService = new KeystoneService_V1();
|
|
570
|
+
const isValid = await keystoneService.verifySigningSecret({
|
|
571
|
+
keystoneIbGib: primaryKeystone,
|
|
572
|
+
signingSecret: masterSecret,
|
|
573
|
+
poolId: 'manage'
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
if (!isValid) {
|
|
577
|
+
elements.inputPassword.classList.add('invalid');
|
|
578
|
+
this.setStatus('Incorrect master password. Please try again.', 'error');
|
|
263
579
|
return;
|
|
264
580
|
}
|
|
265
581
|
|
|
266
|
-
const
|
|
582
|
+
const deviceTag = this.getDeviceTag();
|
|
583
|
+
const primaryUsername = primaryKeystone.data?.frameDetails?.username || 'user';
|
|
267
584
|
|
|
268
|
-
|
|
269
|
-
this.setStatus("Generating local device delegate...", "info");
|
|
585
|
+
this.setStatus(`Generating local sync delegate for ${deviceTag}...`, 'info');
|
|
270
586
|
this.showBusy({
|
|
271
587
|
isBusy: true,
|
|
272
588
|
title: 'Generating Keys...',
|
|
273
|
-
msg:
|
|
589
|
+
msg: `Generating cryptographic delegate keys for ${deviceTag}...`,
|
|
274
590
|
animationEmoji: '🔑'
|
|
275
591
|
});
|
|
276
592
|
|
|
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
593
|
const delegateSecret = await deriveDelegateSecret({ masterSecret });
|
|
280
|
-
const
|
|
281
|
-
if (!
|
|
594
|
+
const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
|
|
595
|
+
if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
|
|
282
596
|
throw new Error('KEYSTONE_PROFILE_SOVEREIGN_SYNC environment variable is missing. Expected valid profile name like "sync-profile.dev" or "sync-profile.prod". (E: 8f9e0a1b2c3d4e5f67890123456789ab)');
|
|
283
597
|
}
|
|
284
|
-
|
|
598
|
+
|
|
599
|
+
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(sovereignSyncProfileName.trim() as any)
|
|
285
600
|
.withUsername(`${primaryUsername}-sync-${deviceTag}`)
|
|
286
601
|
.withDescription(`Sync Delegate for ${primaryUsername} on ${deviceTag}`)
|
|
287
602
|
.withDetails({
|
|
@@ -290,113 +605,181 @@ export class AddDeviceComponentInstance
|
|
|
290
605
|
deviceTag,
|
|
291
606
|
primaryTjpAddr,
|
|
292
607
|
});
|
|
608
|
+
|
|
293
609
|
const delegateConfigs = await delegateBuilder.compileConfigs();
|
|
610
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
611
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
612
|
+
if (!space) { throw new Error('No default space found in metaspace.'); }
|
|
294
613
|
|
|
295
|
-
const keystoneService = new KeystoneService_V1();
|
|
296
614
|
const delegateKeystone = await keystoneService.genesis({
|
|
297
615
|
masterSecret: delegateSecret,
|
|
298
616
|
configs: delegateConfigs,
|
|
299
617
|
metaspace,
|
|
300
618
|
space,
|
|
301
|
-
frameDetails: delegateBuilder.getFrameDetails()
|
|
619
|
+
frameDetails: delegateBuilder.getFrameDetails(),
|
|
620
|
+
isPrimary: false
|
|
302
621
|
});
|
|
303
622
|
|
|
304
|
-
|
|
305
|
-
console.log(`${lc} Local device delegate generated: ${delegateAddr}`);
|
|
306
|
-
await delay(humanEyeDelay);
|
|
307
|
-
|
|
308
|
-
// 3. Evolve parent primary keystone to register delegate
|
|
309
|
-
this.setStatus("Registering device on primary identity...", "info");
|
|
623
|
+
this.setStatus('Evolving primary identity to register delegate...', 'info');
|
|
310
624
|
this.showBusy({
|
|
311
625
|
isBusy: true,
|
|
312
626
|
title: 'Registering Device...',
|
|
313
|
-
msg: '
|
|
314
|
-
animationEmoji: '
|
|
627
|
+
msg: 'Evolving primary identity to register new device delegate...',
|
|
628
|
+
animationEmoji: '⚙️'
|
|
315
629
|
});
|
|
316
630
|
|
|
317
|
-
const
|
|
631
|
+
const evolvedParentKeystone = await keystoneService.registerDelegate({
|
|
318
632
|
parentKeystone: primaryKeystone,
|
|
319
633
|
delegateKeystone,
|
|
320
634
|
masterSecret,
|
|
635
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
321
636
|
metaspace,
|
|
322
637
|
space,
|
|
323
|
-
|
|
324
|
-
deviceName: deviceTag,
|
|
638
|
+
deviceName: deviceTag
|
|
325
639
|
});
|
|
326
640
|
|
|
327
|
-
|
|
328
|
-
console.log(`${lc} Primary identity evolved with delegate: ${evolvedPrimaryAddr}`);
|
|
329
|
-
await delay(humanEyeDelay);
|
|
330
|
-
|
|
331
|
-
// 4. Post evolved primary keystone + delegate to server
|
|
332
|
-
this.setStatus("Syncing device pairing to server...", "info");
|
|
641
|
+
this.setStatus('Publishing evolved identity to server...', 'info');
|
|
333
642
|
this.showBusy({
|
|
334
643
|
isBusy: true,
|
|
335
644
|
title: 'Syncing to Server...',
|
|
336
|
-
msg: '
|
|
337
|
-
animationEmoji: '
|
|
645
|
+
msg: 'Publishing evolved identity and delegate to server...',
|
|
646
|
+
animationEmoji: '⚡'
|
|
338
647
|
});
|
|
339
648
|
|
|
340
|
-
// const domainAddr = primaryTjpAddr ? getIbGibAddr({ ibGib: primaryKeystone }) : getIbGibAddr({ ibGib: primaryKeystone });
|
|
341
|
-
|
|
342
649
|
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
343
650
|
domainAddr: primaryTjpAddr,
|
|
344
|
-
keystoneIbGib:
|
|
651
|
+
keystoneIbGib: evolvedParentKeystone,
|
|
345
652
|
relatedKeystoneIbGibs: [delegateKeystone]
|
|
346
653
|
});
|
|
347
|
-
|
|
348
654
|
if (!resEvolve.success) {
|
|
349
|
-
|
|
350
|
-
console.error(`${lc} ${msg}`);
|
|
351
|
-
this.setStatus(msg, "error");
|
|
352
|
-
this.showBusy({ isBusy: false });
|
|
353
|
-
this.setButtonLoading(false);
|
|
354
|
-
return;
|
|
655
|
+
console.warn(`${lc} Warning syncing delegate evolution to server: ${resEvolve.message}`);
|
|
355
656
|
}
|
|
356
657
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
658
|
+
await this.finalizeDeviceOnboarding({
|
|
659
|
+
primaryKeystone: evolvedParentKeystone,
|
|
660
|
+
primaryGraph: this._stagedPrimaryGraph,
|
|
661
|
+
delegateKeystone,
|
|
662
|
+
primaryTjpAddr,
|
|
663
|
+
masterSecret,
|
|
664
|
+
delegateSecret,
|
|
665
|
+
makeActive: elements.checkboxMakeActive?.checked ?? true
|
|
363
666
|
});
|
|
364
667
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
],
|
|
376
|
-
metaspace,
|
|
377
|
-
space,
|
|
378
|
-
});
|
|
668
|
+
} catch (error) {
|
|
669
|
+
const emsg = extractErrorMsg(error);
|
|
670
|
+
console.error(`${lc} ${emsg}`, error);
|
|
671
|
+
this.setStatus(`Error: ${emsg}`, 'error');
|
|
672
|
+
} finally {
|
|
673
|
+
elements.btnAuthorizePassword.disabled = false;
|
|
674
|
+
elements.btnAuthorizePassword.textContent = 'Authorize & Pair Device';
|
|
675
|
+
this.showBusy({ isBusy: false });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
379
678
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
679
|
+
// =========================================================================
|
|
680
|
+
// Finalize Device Onboarding & Navigation
|
|
681
|
+
// =========================================================================
|
|
682
|
+
|
|
683
|
+
private async finalizeDeviceOnboarding({
|
|
684
|
+
primaryKeystone,
|
|
685
|
+
primaryGraph,
|
|
686
|
+
delegateKeystone,
|
|
687
|
+
primaryTjpAddr,
|
|
688
|
+
masterSecret,
|
|
689
|
+
delegateSecret,
|
|
690
|
+
makeActive = true,
|
|
691
|
+
}: {
|
|
692
|
+
primaryKeystone?: any;
|
|
693
|
+
primaryGraph?: FlatIbGibGraph;
|
|
694
|
+
delegateKeystone: any;
|
|
695
|
+
primaryTjpAddr: string;
|
|
696
|
+
masterSecret?: string;
|
|
697
|
+
delegateSecret?: string;
|
|
698
|
+
makeActive?: boolean;
|
|
699
|
+
}): Promise<void> {
|
|
700
|
+
const lc = `${this.lc}[${this.finalizeDeviceOnboarding.name}]`;
|
|
701
|
+
const humanEyeDelay = 500;
|
|
702
|
+
|
|
703
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
704
|
+
if (!metaspace) { throw new Error('Metaspace not found'); }
|
|
705
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
706
|
+
if (!space) { throw new Error('Local user space not found'); }
|
|
707
|
+
|
|
708
|
+
// 1. Put primary frames and delegate into local space
|
|
709
|
+
if (primaryGraph) {
|
|
710
|
+
const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
|
|
711
|
+
await metaspace.put({ ibGibs: graphIbGibs, space });
|
|
712
|
+
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: graphIbGibs });
|
|
713
|
+
for (const tjpKey of Object.keys(timelinesMap)) {
|
|
714
|
+
for (const frame of timelinesMap[tjpKey]) {
|
|
715
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
716
|
+
}
|
|
387
717
|
}
|
|
718
|
+
} else if (primaryKeystone) {
|
|
719
|
+
await metaspace.put({ ibGibs: [primaryKeystone], space });
|
|
720
|
+
await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
|
|
721
|
+
}
|
|
388
722
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.elements!.delegateAddrEl.innerText = delegateAddr;
|
|
392
|
-
this.elements!.resultAreaEl.classList.remove('collapsed');
|
|
393
|
-
this.setStatus("Device successfully paired!", "success");
|
|
723
|
+
await metaspace.put({ ibGibs: [delegateKeystone], space });
|
|
724
|
+
await metaspace.registerNewIbGib({ ibGib: delegateKeystone, space });
|
|
394
725
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
this.setButtonLoading(false);
|
|
726
|
+
// 2. Register in keystones special index
|
|
727
|
+
const ibGibsToRegister: IbGib_V1[] = [toDto({ ibGib: delegateKeystone })!];
|
|
728
|
+
if (primaryKeystone) {
|
|
729
|
+
ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone })!);
|
|
400
730
|
}
|
|
731
|
+
|
|
732
|
+
await updateSpecialIndex({
|
|
733
|
+
type: 'keystones',
|
|
734
|
+
rel8nInfos: [{ rel8nName: 'keystone', ibGibs: ibGibsToRegister }],
|
|
735
|
+
metaspace,
|
|
736
|
+
space
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// 3. Make active if checked
|
|
740
|
+
if (makeActive) {
|
|
741
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
742
|
+
detail: { activeIdentityAddr: primaryTjpAddr },
|
|
743
|
+
bubbles: true,
|
|
744
|
+
composed: true
|
|
745
|
+
}));
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// 4. Update UI results
|
|
749
|
+
const delegateAddr = getIbGibAddr({ ibGib: delegateKeystone });
|
|
750
|
+
if (this.elements?.delegateAddrEl) {
|
|
751
|
+
this.elements.delegateAddrEl.textContent = delegateAddr;
|
|
752
|
+
}
|
|
753
|
+
if (this.elements?.resultAreaEl) {
|
|
754
|
+
this.elements.resultAreaEl.classList.remove('collapsed');
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const successMsg = 'Device paired and identity activated successfully!';
|
|
758
|
+
this.setStatus(successMsg, 'success');
|
|
759
|
+
this.showBusy({
|
|
760
|
+
isBusy: true,
|
|
761
|
+
title: 'Success!',
|
|
762
|
+
msg: successMsg,
|
|
763
|
+
animationEmoji: '✅'
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
await delay(humanEyeDelay);
|
|
767
|
+
|
|
768
|
+
// 5. Navigate to identity manager
|
|
769
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
770
|
+
detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
|
|
771
|
+
bubbles: true,
|
|
772
|
+
composed: true
|
|
773
|
+
}));
|
|
774
|
+
|
|
775
|
+
this.showBusy({ isBusy: false });
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
protected override async renderUI(): Promise<void> {
|
|
779
|
+
// No complex rendering needed
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
override async handleContextUpdated(): Promise<void> {
|
|
783
|
+
await this.renderUI();
|
|
401
784
|
}
|
|
402
785
|
}
|