@ibgib/web-gib 0.0.59 → 0.0.61
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/helpers.d.mts.map +1 -1
- package/dist/helpers.mjs +1 -0
- package/dist/helpers.mjs.map +1 -1
- package/dist/identity/device-delegate-helper.d.mts +43 -0
- package/dist/identity/device-delegate-helper.d.mts.map +1 -0
- package/dist/identity/device-delegate-helper.mjs +106 -0
- package/dist/identity/device-delegate-helper.mjs.map +1 -0
- package/dist/identity/device-delegate.respec.d.mts +2 -0
- package/dist/identity/device-delegate.respec.d.mts.map +1 -0
- package/dist/identity/device-delegate.respec.mjs +106 -0
- package/dist/identity/device-delegate.respec.mjs.map +1 -0
- 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/identity/keystone-sync-service.d.mts +18 -0
- package/dist/identity/keystone-sync-service.d.mts.map +1 -1
- package/dist/identity/keystone-sync-service.mjs +77 -1
- package/dist/identity/keystone-sync-service.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -2
- package/dist/index.mjs.map +1 -1
- package/dist/storage/storage-helpers.web.d.mts +9 -0
- package/dist/storage/storage-helpers.web.d.mts.map +1 -1
- package/dist/storage/storage-helpers.web.mjs +42 -0
- package/dist/storage/storage-helpers.web.mjs.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 +489 -198
- 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 +48 -61
- 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 +11 -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 +358 -97
- 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/ui/ui-constants.d.mts +1 -0
- package/dist/ui/ui-constants.d.mts.map +1 -1
- package/dist/ui/ui-constants.mjs +1 -0
- package/dist/ui/ui-constants.mjs.map +1 -1
- package/dist/ui/ui-types.d.mts +5 -0
- package/dist/ui/ui-types.d.mts.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/helpers.mts +1 -0
- package/src/identity/custodian-delegate-helper.mts.bak +7 -0
- package/src/identity/device-delegate-helper.mts +135 -0
- package/src/identity/device-delegate.respec.mts +125 -0
- package/src/identity/identity-constants.mts +2 -2
- package/src/identity/identity-types.mts +4 -4
- package/src/identity/keystone-sync-service.mts +95 -2
- package/src/index.mts +1 -2
- package/src/storage/storage-helpers.web.mts +47 -0
- 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 +579 -208
- package/src/ui/component/identity/keystone-creator/keystone-creator.mts +55 -69
- 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 +374 -106
- 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/ui/ui-constants.mts +1 -0
- package/src/ui/ui-types.mts +6 -0
- package/src/util/device-helper.mts +104 -0
- package/src/util/device-helper.respec.mts +90 -0
- package/dist/identity/custodian-delegate-helper.d.mts +0 -22
- package/dist/identity/custodian-delegate-helper.d.mts.map +0 -1
- package/dist/identity/custodian-delegate-helper.mjs +0 -50
- package/dist/identity/custodian-delegate-helper.mjs.map +0 -1
- package/src/identity/custodian-delegate-helper.mts +0 -61
|
@@ -9,10 +9,12 @@ import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
|
9
9
|
import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
|
|
10
10
|
import { KeystoneProfileBuilder } from "@ibgib/core-gib/dist/keystone/policy/keystone-profile-builder.mjs";
|
|
11
11
|
import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
|
|
12
|
-
import { KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT
|
|
13
|
-
import { deriveDelegateSecret } from "@ibgib/core-gib/dist/keystone/
|
|
12
|
+
import { KEYSTONE_USERNAME_REGEXP, KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs";
|
|
13
|
+
import { deriveDelegateSecret, createSyncDelegateKeystone } from "@ibgib/core-gib/dist/keystone/delegate/delegate-helpers.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 { getOrCreateDeviceDelegateSecret, setDeviceDelegateSecret } from "../../../../identity/device-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,273 +171,603 @@ 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 metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
476
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
477
|
+
if (!space) {
|
|
478
|
+
this.setStatus('Could not access local user space.', 'error');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const delegateSecret = await getOrCreateDeviceDelegateSecret({ primaryTjpAddr, deviceTag });
|
|
483
|
+
const userAlias = username || (email ? email.split('@')[0] : 'user');
|
|
484
|
+
const custodianSyncProfileName = process.env.KEYSTONE_PROFILE_CUSTODIAN_SYNC;
|
|
485
|
+
if (!custodianSyncProfileName || !custodianSyncProfileName.trim()) {
|
|
486
|
+
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)');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const delegateKeystone = await createSyncDelegateKeystone({
|
|
490
|
+
profileName: custodianSyncProfileName.trim() as any,
|
|
491
|
+
username: userAlias,
|
|
492
|
+
deviceTag,
|
|
493
|
+
primaryTjpAddr,
|
|
494
|
+
delegateSecret,
|
|
495
|
+
custodianReqs,
|
|
496
|
+
additionalFrameDetails: { client: 'space-gib-web' },
|
|
497
|
+
metaspace,
|
|
498
|
+
space,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
this.setStatus('Registering device delegate with custodian server...', 'info');
|
|
502
|
+
this.showBusy({
|
|
503
|
+
isBusy: true,
|
|
504
|
+
title: 'Registering Delegate...',
|
|
505
|
+
msg: 'Registering local delegate with custodian server...',
|
|
506
|
+
animationEmoji: '🔄'
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const resRegister = await getApiBridge().postCustodianRegisterDelegate({ delegateKeystone });
|
|
510
|
+
if (!resRegister.success) {
|
|
511
|
+
this.setStatus(resRegister.message || 'Custodian delegate registration failed.', 'error');
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Finalize onboarding into local space & special index
|
|
516
|
+
await this.finalizeDeviceOnboarding({
|
|
517
|
+
primaryKeystone: resRegister.evolvedPrimaryKeystone || fallbackPrimaryKeystone,
|
|
518
|
+
primaryGraph: resRegister.primaryGraph || fallbackPrimaryGraph,
|
|
519
|
+
delegateKeystone,
|
|
520
|
+
primaryTjpAddr,
|
|
521
|
+
delegateSecret,
|
|
522
|
+
makeActive: this.elements?.checkboxMakeActive?.checked ?? true
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// =========================================================================
|
|
527
|
+
// Flow 3: Sovereign Master Password Unlock & Local Evolution
|
|
528
|
+
// =========================================================================
|
|
529
|
+
|
|
530
|
+
protected async handleAuthorizePassword() {
|
|
531
|
+
const lc = `${this.lc}[${this.handleAuthorizePassword.name}]`;
|
|
532
|
+
const elements = this.elements!;
|
|
533
|
+
const masterSecret = elements.inputPassword?.value?.trim();
|
|
534
|
+
const primaryKeystone = this._stagedPrimaryKeystone;
|
|
535
|
+
const primaryTjpAddr = this._stagedPrimaryTjpAddr;
|
|
536
|
+
|
|
537
|
+
if (!masterSecret) {
|
|
538
|
+
elements.inputPassword.classList.add('invalid');
|
|
539
|
+
this.setStatus('Please enter your master password.', 'error');
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (!primaryKeystone || !primaryTjpAddr) {
|
|
544
|
+
this.setStatus('Missing staged primary identity context. Please restart sign-in.', 'error');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
elements.btnAuthorizePassword.disabled = true;
|
|
549
|
+
elements.btnAuthorizePassword.textContent = 'Authorizing...';
|
|
550
|
+
this.setStatus('Verifying master password locally...', 'info');
|
|
551
|
+
this.showBusy({
|
|
552
|
+
isBusy: true,
|
|
553
|
+
title: 'Verifying Password...',
|
|
554
|
+
msg: 'Verifying cryptographic password locally in browser...',
|
|
555
|
+
animationEmoji: '🔐'
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
const keystoneService = new KeystoneService_V1();
|
|
560
|
+
const isValid = await keystoneService.verifySigningSecret({
|
|
561
|
+
keystoneIbGib: primaryKeystone,
|
|
562
|
+
signingSecret: masterSecret,
|
|
563
|
+
poolId: 'manage'
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
if (!isValid) {
|
|
567
|
+
elements.inputPassword.classList.add('invalid');
|
|
568
|
+
this.setStatus('Incorrect master password. Please try again.', 'error');
|
|
263
569
|
return;
|
|
264
570
|
}
|
|
265
571
|
|
|
266
|
-
const
|
|
572
|
+
const deviceTag = this.getDeviceTag();
|
|
573
|
+
const primaryUsername = primaryKeystone.data?.frameDetails?.username || 'user';
|
|
267
574
|
|
|
268
|
-
|
|
269
|
-
this.setStatus("Generating local device delegate...", "info");
|
|
575
|
+
this.setStatus(`Generating local sync delegate for ${deviceTag}...`, 'info');
|
|
270
576
|
this.showBusy({
|
|
271
577
|
isBusy: true,
|
|
272
578
|
title: 'Generating Keys...',
|
|
273
|
-
msg:
|
|
579
|
+
msg: `Generating cryptographic delegate keys for ${deviceTag}...`,
|
|
274
580
|
animationEmoji: '🔑'
|
|
275
581
|
});
|
|
276
582
|
|
|
277
|
-
const
|
|
278
|
-
|
|
583
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
584
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
585
|
+
if (!space) { throw new Error('No default space found in metaspace.'); }
|
|
586
|
+
|
|
279
587
|
const delegateSecret = await deriveDelegateSecret({ masterSecret });
|
|
280
|
-
|
|
281
|
-
|
|
588
|
+
await setDeviceDelegateSecret({ primaryTjpAddr, delegateSecret });
|
|
589
|
+
|
|
590
|
+
const sovereignSyncProfileName = process.env.KEYSTONE_PROFILE_SOVEREIGN_SYNC;
|
|
591
|
+
if (!sovereignSyncProfileName || !sovereignSyncProfileName.trim()) {
|
|
282
592
|
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
593
|
}
|
|
284
|
-
const delegateBuilder = KeystoneProfileBuilder.buildKeystone(syncProfileName.trim() as any)
|
|
285
|
-
.withUsername(`${primaryUsername}-sync-${deviceTag}`)
|
|
286
|
-
.withDescription(`Sync Delegate for ${primaryUsername} on ${deviceTag}`)
|
|
287
|
-
.withDetails({
|
|
288
|
-
client: 'space-gib-web',
|
|
289
|
-
role: 'sync-delegate',
|
|
290
|
-
deviceTag,
|
|
291
|
-
primaryTjpAddr,
|
|
292
|
-
});
|
|
293
|
-
const delegateConfigs = await delegateBuilder.compileConfigs();
|
|
294
594
|
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
595
|
+
const delegateKeystone = await createSyncDelegateKeystone({
|
|
596
|
+
profileName: sovereignSyncProfileName.trim() as any,
|
|
597
|
+
username: primaryUsername,
|
|
598
|
+
deviceTag,
|
|
599
|
+
primaryTjpAddr,
|
|
600
|
+
delegateSecret,
|
|
601
|
+
additionalFrameDetails: { client: 'space-gib-web' },
|
|
299
602
|
metaspace,
|
|
300
603
|
space,
|
|
301
|
-
frameDetails: delegateBuilder.getFrameDetails()
|
|
302
604
|
});
|
|
303
605
|
|
|
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");
|
|
606
|
+
this.setStatus('Evolving primary identity to register delegate...', 'info');
|
|
310
607
|
this.showBusy({
|
|
311
608
|
isBusy: true,
|
|
312
609
|
title: 'Registering Device...',
|
|
313
|
-
msg: '
|
|
314
|
-
animationEmoji: '
|
|
610
|
+
msg: 'Evolving primary identity to register new device delegate...',
|
|
611
|
+
animationEmoji: '⚙️'
|
|
315
612
|
});
|
|
316
613
|
|
|
317
|
-
const
|
|
614
|
+
const evolvedParentKeystone = await keystoneService.registerDelegate({
|
|
318
615
|
parentKeystone: primaryKeystone,
|
|
319
616
|
delegateKeystone,
|
|
320
617
|
masterSecret,
|
|
618
|
+
allowedVerbs: [KEYSTONE_VERB_SYNC, KEYSTONE_VERB_CONNECT],
|
|
321
619
|
metaspace,
|
|
322
620
|
space,
|
|
323
|
-
|
|
324
|
-
deviceName: deviceTag,
|
|
621
|
+
deviceName: deviceTag
|
|
325
622
|
});
|
|
326
623
|
|
|
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");
|
|
624
|
+
this.setStatus('Publishing evolved identity to server...', 'info');
|
|
333
625
|
this.showBusy({
|
|
334
626
|
isBusy: true,
|
|
335
627
|
title: 'Syncing to Server...',
|
|
336
|
-
msg: '
|
|
337
|
-
animationEmoji: '
|
|
628
|
+
msg: 'Publishing evolved identity and delegate to server...',
|
|
629
|
+
animationEmoji: '⚡'
|
|
338
630
|
});
|
|
339
631
|
|
|
340
|
-
// const domainAddr = primaryTjpAddr ? getIbGibAddr({ ibGib: primaryKeystone }) : getIbGibAddr({ ibGib: primaryKeystone });
|
|
341
|
-
|
|
342
632
|
const resEvolve = await getApiBridge().putEvolveKeystone({
|
|
343
633
|
domainAddr: primaryTjpAddr,
|
|
344
|
-
keystoneIbGib:
|
|
634
|
+
keystoneIbGib: evolvedParentKeystone,
|
|
345
635
|
relatedKeystoneIbGibs: [delegateKeystone]
|
|
346
636
|
});
|
|
347
|
-
|
|
348
637
|
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;
|
|
638
|
+
console.warn(`${lc} Warning syncing delegate evolution to server: ${resEvolve.message}`);
|
|
355
639
|
}
|
|
356
640
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
641
|
+
await this.finalizeDeviceOnboarding({
|
|
642
|
+
primaryKeystone: evolvedParentKeystone,
|
|
643
|
+
primaryGraph: this._stagedPrimaryGraph,
|
|
644
|
+
delegateKeystone,
|
|
645
|
+
primaryTjpAddr,
|
|
646
|
+
masterSecret,
|
|
647
|
+
delegateSecret,
|
|
648
|
+
makeActive: elements.checkboxMakeActive?.checked ?? true
|
|
363
649
|
});
|
|
364
650
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
],
|
|
376
|
-
metaspace,
|
|
377
|
-
space,
|
|
378
|
-
});
|
|
651
|
+
} catch (error) {
|
|
652
|
+
const emsg = extractErrorMsg(error);
|
|
653
|
+
console.error(`${lc} ${emsg}`, error);
|
|
654
|
+
this.setStatus(`Error: ${emsg}`, 'error');
|
|
655
|
+
} finally {
|
|
656
|
+
elements.btnAuthorizePassword.disabled = false;
|
|
657
|
+
elements.btnAuthorizePassword.textContent = 'Authorize & Pair Device';
|
|
658
|
+
this.showBusy({ isBusy: false });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
379
661
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
662
|
+
// =========================================================================
|
|
663
|
+
// Finalize Device Onboarding & Navigation
|
|
664
|
+
// =========================================================================
|
|
665
|
+
|
|
666
|
+
private async finalizeDeviceOnboarding({
|
|
667
|
+
primaryKeystone,
|
|
668
|
+
primaryGraph,
|
|
669
|
+
delegateKeystone,
|
|
670
|
+
primaryTjpAddr,
|
|
671
|
+
masterSecret,
|
|
672
|
+
delegateSecret,
|
|
673
|
+
makeActive = true,
|
|
674
|
+
}: {
|
|
675
|
+
primaryKeystone?: any;
|
|
676
|
+
primaryGraph?: FlatIbGibGraph;
|
|
677
|
+
delegateKeystone: any;
|
|
678
|
+
primaryTjpAddr: string;
|
|
679
|
+
masterSecret?: string;
|
|
680
|
+
delegateSecret?: string;
|
|
681
|
+
makeActive?: boolean;
|
|
682
|
+
}): Promise<void> {
|
|
683
|
+
const lc = `${this.lc}[${this.finalizeDeviceOnboarding.name}]`;
|
|
684
|
+
const humanEyeDelay = 500;
|
|
685
|
+
|
|
686
|
+
const metaspace = await getGlobalMetaspace_waitIfNeeded();
|
|
687
|
+
if (!metaspace) { throw new Error('Metaspace not found'); }
|
|
688
|
+
const space = await metaspace.getLocalUserSpace({});
|
|
689
|
+
if (!space) { throw new Error('Local user space not found'); }
|
|
690
|
+
|
|
691
|
+
// 1. Put primary frames and delegate into local space
|
|
692
|
+
if (primaryGraph) {
|
|
693
|
+
const graphIbGibs = Object.values(primaryGraph) as IbGib_V1[];
|
|
694
|
+
await metaspace.put({ ibGibs: graphIbGibs, space });
|
|
695
|
+
const timelinesMap = getTimelinesGroupedByTjp({ ibGibs: graphIbGibs });
|
|
696
|
+
for (const tjpKey of Object.keys(timelinesMap)) {
|
|
697
|
+
for (const frame of timelinesMap[tjpKey]) {
|
|
698
|
+
await metaspace.registerNewIbGib({ ibGib: frame, space });
|
|
699
|
+
}
|
|
387
700
|
}
|
|
701
|
+
} else if (primaryKeystone) {
|
|
702
|
+
await metaspace.put({ ibGibs: [primaryKeystone], space });
|
|
703
|
+
await metaspace.registerNewIbGib({ ibGib: primaryKeystone, space });
|
|
704
|
+
}
|
|
388
705
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.elements!.delegateAddrEl.innerText = delegateAddr;
|
|
392
|
-
this.elements!.resultAreaEl.classList.remove('collapsed');
|
|
393
|
-
this.setStatus("Device successfully paired!", "success");
|
|
706
|
+
await metaspace.put({ ibGibs: [delegateKeystone], space });
|
|
707
|
+
await metaspace.registerNewIbGib({ ibGib: delegateKeystone, space });
|
|
394
708
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
this.setButtonLoading(false);
|
|
709
|
+
// 2. Register in keystones special index
|
|
710
|
+
const ibGibsToRegister: IbGib_V1[] = [toDto({ ibGib: delegateKeystone })!];
|
|
711
|
+
if (primaryKeystone) {
|
|
712
|
+
ibGibsToRegister.unshift(toDto({ ibGib: primaryKeystone })!);
|
|
400
713
|
}
|
|
714
|
+
|
|
715
|
+
await updateSpecialIndex({
|
|
716
|
+
type: 'keystones',
|
|
717
|
+
rel8nInfos: [{ rel8nName: 'keystone', ibGibs: ibGibsToRegister }],
|
|
718
|
+
metaspace,
|
|
719
|
+
space
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// 3. Make active if checked
|
|
723
|
+
if (makeActive) {
|
|
724
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
725
|
+
detail: { activeIdentityAddr: primaryTjpAddr },
|
|
726
|
+
bubbles: true,
|
|
727
|
+
composed: true
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// 4. Update UI results
|
|
732
|
+
const delegateAddr = getIbGibAddr({ ibGib: delegateKeystone });
|
|
733
|
+
if (this.elements?.delegateAddrEl) {
|
|
734
|
+
this.elements.delegateAddrEl.textContent = delegateAddr;
|
|
735
|
+
}
|
|
736
|
+
if (this.elements?.resultAreaEl) {
|
|
737
|
+
this.elements.resultAreaEl.classList.remove('collapsed');
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const successMsg = 'Device paired and identity activated successfully!';
|
|
741
|
+
this.setStatus(successMsg, 'success');
|
|
742
|
+
this.showBusy({
|
|
743
|
+
isBusy: true,
|
|
744
|
+
title: 'Success!',
|
|
745
|
+
msg: successMsg,
|
|
746
|
+
animationEmoji: '✅'
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
await delay(humanEyeDelay);
|
|
750
|
+
|
|
751
|
+
// 4.5 Sanitize password input fields from DOM
|
|
752
|
+
if (this.elements?.inputPassword) {
|
|
753
|
+
this.elements.inputPassword.value = '';
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// 5. Navigate to identity manager
|
|
757
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_UI_MESSAGE, {
|
|
758
|
+
detail: { action: 'show-identity-manager', addr: primaryTjpAddr },
|
|
759
|
+
bubbles: true,
|
|
760
|
+
composed: true
|
|
761
|
+
}));
|
|
762
|
+
|
|
763
|
+
this.showBusy({ isBusy: false });
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
protected override async renderUI(): Promise<void> {
|
|
767
|
+
// No complex rendering needed
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
override async handleContextUpdated(): Promise<void> {
|
|
771
|
+
await this.renderUI();
|
|
401
772
|
}
|
|
402
773
|
}
|