@oatscenter/trellis-svelte 0.100.0-rc.1
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/README.md +16 -0
- package/dist/authorization_context.d.ts +18 -0
- package/dist/authorization_context.js +150 -0
- package/dist/components/TrellisContextProvider.svelte +24 -0
- package/dist/components/TrellisContextProvider.svelte.d.ts +11 -0
- package/dist/components/TrellisProvider.svelte +192 -0
- package/dist/components/TrellisProvider.svelte.d.ts +6 -0
- package/dist/components/TrellisProvider.types.d.ts +15 -0
- package/dist/components/TrellisProvider.types.js +1 -0
- package/dist/context.d.ts +72 -0
- package/dist/context.js +116 -0
- package/dist/device_activation.d.ts +6 -0
- package/dist/device_activation.js +19 -0
- package/dist/device_activation_controller.d.ts +91 -0
- package/dist/device_activation_controller.js +318 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/internal/activation_view.d.ts +44 -0
- package/dist/internal/activation_view.js +95 -0
- package/dist/internal/callback_state.d.ts +9 -0
- package/dist/internal/callback_state.js +24 -0
- package/dist/internal/portal_url.d.ts +13 -0
- package/dist/internal/portal_url.js +57 -0
- package/dist/portal_flow.d.ts +24 -0
- package/dist/portal_flow.js +173 -0
- package/package.json +42 -0
- package/src/authorization_context.svelte.ts +153 -0
- package/src/components/TrellisContextProvider.svelte +24 -0
- package/src/components/TrellisProvider.svelte +192 -0
- package/src/components/TrellisProvider.types.ts +28 -0
- package/src/context.svelte.ts +205 -0
- package/src/device_activation.svelte.ts +28 -0
- package/src/device_activation_controller.ts +528 -0
- package/src/index.ts +31 -0
- package/src/internal/activation_view.ts +179 -0
- package/src/internal/callback_state.ts +48 -0
- package/src/internal/portal_url.ts +94 -0
- package/src/portal_flow.svelte.ts +143 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import type { AsyncResult, BaseError } from "@oatscenter/result";
|
|
2
|
+
import type {
|
|
3
|
+
OperationEvent,
|
|
4
|
+
OperationSnapshot,
|
|
5
|
+
TerminalOperation,
|
|
6
|
+
} from "@oatscenter/trellis";
|
|
7
|
+
import type {
|
|
8
|
+
DeviceUserAuthoritiesResolveOutput,
|
|
9
|
+
DeviceUserAuthoritiesResolveProgress,
|
|
10
|
+
} from "@oatscenter/trellis/auth";
|
|
11
|
+
import { ulid } from "ulid";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createDeviceActivationReadyView,
|
|
15
|
+
createDeviceActivationSignInRequiredView,
|
|
16
|
+
createInvalidDeviceActivationView,
|
|
17
|
+
type DeviceActivationView,
|
|
18
|
+
isDeviceActivationReviewerRequiredFailure,
|
|
19
|
+
mapDeviceActivationFailure,
|
|
20
|
+
mapDeviceActivationProgress,
|
|
21
|
+
mapDeviceActivationTerminal,
|
|
22
|
+
} from "./internal/activation_view.ts";
|
|
23
|
+
import {
|
|
24
|
+
clearPreservedDeviceActivationCallbackState,
|
|
25
|
+
getPreservedDeviceActivationCallbackState,
|
|
26
|
+
preserveDeviceActivationCallbackState,
|
|
27
|
+
type StorageLike,
|
|
28
|
+
} from "./internal/callback_state.ts";
|
|
29
|
+
import {
|
|
30
|
+
buildDeviceActivationCallbackPath,
|
|
31
|
+
buildDeviceActivationConnectAuthUrlState,
|
|
32
|
+
cleanupDeviceActivationCallbackUrl,
|
|
33
|
+
type DeviceActivationConnectAuthUrlState,
|
|
34
|
+
resolveDeviceActivationUrlState,
|
|
35
|
+
} from "./internal/portal_url.ts";
|
|
36
|
+
|
|
37
|
+
export type DeviceActivationSignInOptions = {
|
|
38
|
+
redirectTo?: string;
|
|
39
|
+
landingPath?: string;
|
|
40
|
+
context?: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type DeviceActivationBindResult =
|
|
44
|
+
| { status: "bound" }
|
|
45
|
+
| { status: "approval_denied" }
|
|
46
|
+
| { status: "approval_required" }
|
|
47
|
+
| { status: "insufficient_capabilities"; missingCapabilities: string[] }
|
|
48
|
+
| { status: "error"; message: string };
|
|
49
|
+
|
|
50
|
+
export type DeviceActivationAuth = {
|
|
51
|
+
init(): Promise<unknown>;
|
|
52
|
+
handleCallback(
|
|
53
|
+
callbackUrl: string,
|
|
54
|
+
): Promise<DeviceActivationBindResult | null>;
|
|
55
|
+
signIn(options?: DeviceActivationSignInOptions): Promise<void>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type DeviceActivationControllerConfig = {
|
|
59
|
+
authState: DeviceActivationAuth;
|
|
60
|
+
createClient(
|
|
61
|
+
authUrlState: DeviceActivationConnectAuthUrlState,
|
|
62
|
+
): Promise<DeviceActivationClient>;
|
|
63
|
+
getUrl?: () => URL;
|
|
64
|
+
replaceUrl?: (url: string) => void;
|
|
65
|
+
sessionStorage?: StorageLike;
|
|
66
|
+
createCallbackToken?: () => string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type DeviceActivationOperationRef = {
|
|
70
|
+
watch(): AsyncResult<
|
|
71
|
+
AsyncIterable<
|
|
72
|
+
OperationEvent<
|
|
73
|
+
DeviceUserAuthoritiesResolveProgress,
|
|
74
|
+
DeviceUserAuthoritiesResolveOutput
|
|
75
|
+
>
|
|
76
|
+
>,
|
|
77
|
+
BaseError
|
|
78
|
+
>;
|
|
79
|
+
wait(): AsyncResult<
|
|
80
|
+
TerminalOperation<
|
|
81
|
+
DeviceUserAuthoritiesResolveProgress,
|
|
82
|
+
DeviceUserAuthoritiesResolveOutput
|
|
83
|
+
>,
|
|
84
|
+
BaseError
|
|
85
|
+
>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type SnapshotCapableDeviceActivationOperationRef =
|
|
89
|
+
& DeviceActivationOperationRef
|
|
90
|
+
& {
|
|
91
|
+
get(): AsyncResult<
|
|
92
|
+
OperationSnapshot<
|
|
93
|
+
DeviceUserAuthoritiesResolveProgress,
|
|
94
|
+
DeviceUserAuthoritiesResolveOutput
|
|
95
|
+
>,
|
|
96
|
+
BaseError
|
|
97
|
+
>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
type CompanionConsent = NonNullable<
|
|
101
|
+
DeviceUserAuthoritiesResolveProgress["companionConsent"]
|
|
102
|
+
>;
|
|
103
|
+
|
|
104
|
+
export type DeviceCompanionApproval = {
|
|
105
|
+
mode: "capabilities";
|
|
106
|
+
installedRevision: CompanionConsent["installedRevision"];
|
|
107
|
+
expectedGrantRevision: CompanionConsent["expectedGrantRevision"];
|
|
108
|
+
decisionDigest: CompanionConsent["decisionDigest"];
|
|
109
|
+
approvedCapabilities: Array<{
|
|
110
|
+
id: CompanionConsent["capabilities"][number]["id"];
|
|
111
|
+
consentDigest: CompanionConsent["capabilities"][number]["consentDigest"];
|
|
112
|
+
}>;
|
|
113
|
+
approvedResources: Array<{
|
|
114
|
+
kind: CompanionConsent["resources"][number]["kind"];
|
|
115
|
+
name: CompanionConsent["resources"][number]["name"];
|
|
116
|
+
commitment: CompanionConsent["resources"][number]["requestedCommitment"];
|
|
117
|
+
}>;
|
|
118
|
+
companionApproved: boolean;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type DeviceActivationClient = {
|
|
122
|
+
activateDevice(
|
|
123
|
+
input: {
|
|
124
|
+
flowId: string;
|
|
125
|
+
confirmationCode: string;
|
|
126
|
+
companionApproval?: DeviceCompanionApproval;
|
|
127
|
+
},
|
|
128
|
+
): Promise<DeviceActivationOperationRef>;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type DeviceActivationState = {
|
|
132
|
+
loading: boolean;
|
|
133
|
+
requestPending: boolean;
|
|
134
|
+
authError: string | null;
|
|
135
|
+
view: DeviceActivationView | null;
|
|
136
|
+
flowId: string | null;
|
|
137
|
+
confirmationCode: string;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function errorMessage(error: unknown): string {
|
|
141
|
+
return error instanceof Error ? error.message : String(error);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildCompanionApproval(
|
|
145
|
+
consent: NonNullable<
|
|
146
|
+
DeviceUserAuthoritiesResolveProgress["companionConsent"]
|
|
147
|
+
>,
|
|
148
|
+
): DeviceCompanionApproval {
|
|
149
|
+
return {
|
|
150
|
+
mode: "capabilities",
|
|
151
|
+
installedRevision: consent.installedRevision,
|
|
152
|
+
expectedGrantRevision: consent.expectedGrantRevision,
|
|
153
|
+
decisionDigest: consent.decisionDigest,
|
|
154
|
+
approvedCapabilities: consent.capabilities
|
|
155
|
+
.filter((item) => item.required && item.eligible)
|
|
156
|
+
.map((item) => ({ id: item.id, consentDigest: item.consentDigest })),
|
|
157
|
+
approvedResources: consent.resources
|
|
158
|
+
.filter((item) => item.required && item.eligible)
|
|
159
|
+
.map((item) => ({
|
|
160
|
+
kind: item.kind,
|
|
161
|
+
name: item.name,
|
|
162
|
+
commitment: item.requestedCommitment,
|
|
163
|
+
})),
|
|
164
|
+
companionApproved: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function redirectErrorMessage(error: unknown): string | null {
|
|
169
|
+
const message = errorMessage(error);
|
|
170
|
+
return message.startsWith("Redirecting to auth for provider selection")
|
|
171
|
+
? null
|
|
172
|
+
: message;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function bindErrorMessage(result: DeviceActivationBindResult): string | null {
|
|
176
|
+
if (result.status === "bound") return null;
|
|
177
|
+
if (result.status === "approval_denied") return "Portal access was denied.";
|
|
178
|
+
if (result.status === "approval_required") {
|
|
179
|
+
return "Approval is still pending.";
|
|
180
|
+
}
|
|
181
|
+
if (result.status === "insufficient_capabilities") {
|
|
182
|
+
return `Missing capabilities: ${result.missingCapabilities.join(", ")}`;
|
|
183
|
+
}
|
|
184
|
+
return result.message;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function defaultGetUrl(): URL {
|
|
188
|
+
return new URL(globalThis.location.href);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function defaultReplaceUrl(url: string): void {
|
|
192
|
+
globalThis.history.replaceState(globalThis.history.state, "", url);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function defaultCreateCallbackToken(): string {
|
|
196
|
+
return ulid();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function createInitialDeviceActivationState(): DeviceActivationState {
|
|
200
|
+
return {
|
|
201
|
+
loading: true,
|
|
202
|
+
requestPending: false,
|
|
203
|
+
authError: null,
|
|
204
|
+
view: null,
|
|
205
|
+
flowId: null,
|
|
206
|
+
confirmationCode: "",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class DeviceActivationControllerCore {
|
|
211
|
+
protected readonly state: DeviceActivationState;
|
|
212
|
+
|
|
213
|
+
#authState: DeviceActivationAuth;
|
|
214
|
+
#createClient: DeviceActivationControllerConfig["createClient"];
|
|
215
|
+
#getUrl: () => URL;
|
|
216
|
+
#replaceUrl: (url: string) => void;
|
|
217
|
+
#sessionStorage: StorageLike | null;
|
|
218
|
+
#createCallbackToken: () => string;
|
|
219
|
+
#client: DeviceActivationClient | null = null;
|
|
220
|
+
#observationRunId = 0;
|
|
221
|
+
|
|
222
|
+
constructor(
|
|
223
|
+
config: DeviceActivationControllerConfig,
|
|
224
|
+
state: DeviceActivationState = createInitialDeviceActivationState(),
|
|
225
|
+
) {
|
|
226
|
+
this.state = state;
|
|
227
|
+
this.#authState = config.authState;
|
|
228
|
+
this.#createClient = config.createClient;
|
|
229
|
+
this.#getUrl = config.getUrl ?? defaultGetUrl;
|
|
230
|
+
this.#replaceUrl = config.replaceUrl ?? defaultReplaceUrl;
|
|
231
|
+
this.#sessionStorage = config.sessionStorage ?? null;
|
|
232
|
+
this.#createCallbackToken = config.createCallbackToken ??
|
|
233
|
+
defaultCreateCallbackToken;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
get loading(): boolean {
|
|
237
|
+
return this.state.loading;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
get requestPending(): boolean {
|
|
241
|
+
return this.state.requestPending;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
get authError(): string | null {
|
|
245
|
+
return this.state.authError;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
get view(): DeviceActivationView | null {
|
|
249
|
+
return this.state.view;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
get flowId(): string | null {
|
|
253
|
+
return this.state.flowId;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
get confirmationCode(): string {
|
|
257
|
+
return this.state.confirmationCode;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
set confirmationCode(value: string) {
|
|
261
|
+
this.state.confirmationCode = value;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async load(): Promise<void> {
|
|
265
|
+
this.stop();
|
|
266
|
+
this.state.loading = true;
|
|
267
|
+
this.state.authError = null;
|
|
268
|
+
this.state.view = null;
|
|
269
|
+
this.#client = null;
|
|
270
|
+
|
|
271
|
+
const currentUrl = this.#getUrl();
|
|
272
|
+
let clientUrl = currentUrl;
|
|
273
|
+
const preservedState = this.#sessionStorage
|
|
274
|
+
? getPreservedDeviceActivationCallbackState(this.#sessionStorage)
|
|
275
|
+
: null;
|
|
276
|
+
const { flowId, isAuthCallback } = resolveDeviceActivationUrlState(
|
|
277
|
+
currentUrl,
|
|
278
|
+
preservedState,
|
|
279
|
+
);
|
|
280
|
+
this.state.flowId = flowId;
|
|
281
|
+
|
|
282
|
+
if (!flowId) {
|
|
283
|
+
this.state.view = createInvalidDeviceActivationView("Missing flow id.");
|
|
284
|
+
this.state.loading = false;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
await this.#authState.init();
|
|
290
|
+
|
|
291
|
+
if (isAuthCallback) {
|
|
292
|
+
const bindResult = await this.#authState.handleCallback(
|
|
293
|
+
currentUrl.toString(),
|
|
294
|
+
);
|
|
295
|
+
const cleanedUrl = cleanupDeviceActivationCallbackUrl(
|
|
296
|
+
currentUrl,
|
|
297
|
+
flowId,
|
|
298
|
+
);
|
|
299
|
+
if (cleanedUrl) {
|
|
300
|
+
this.#replaceUrl(cleanedUrl);
|
|
301
|
+
clientUrl = new URL(cleanedUrl, currentUrl.origin);
|
|
302
|
+
}
|
|
303
|
+
if (this.#sessionStorage) {
|
|
304
|
+
clearPreservedDeviceActivationCallbackState(this.#sessionStorage);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const callbackAuthError = currentUrl.searchParams.get("authError");
|
|
308
|
+
if (callbackAuthError && !bindResult) {
|
|
309
|
+
this.state.authError = callbackAuthError;
|
|
310
|
+
this.state.view = createDeviceActivationSignInRequiredView(flowId);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (bindResult) {
|
|
315
|
+
const bindError = bindErrorMessage(bindResult);
|
|
316
|
+
if (bindError) {
|
|
317
|
+
this.state.authError = bindError;
|
|
318
|
+
this.state.view = createDeviceActivationSignInRequiredView(flowId);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
this.#client = await this.#createClient(
|
|
326
|
+
buildDeviceActivationConnectAuthUrlState(clientUrl),
|
|
327
|
+
);
|
|
328
|
+
this.state.view = createDeviceActivationReadyView(flowId);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
this.#client = null;
|
|
331
|
+
if (isAuthCallback) {
|
|
332
|
+
this.state.authError = errorMessage(error);
|
|
333
|
+
}
|
|
334
|
+
this.state.view = createDeviceActivationSignInRequiredView(flowId);
|
|
335
|
+
if (!isAuthCallback) {
|
|
336
|
+
await this.signIn();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
this.state.authError = errorMessage(error);
|
|
341
|
+
this.state.view = createDeviceActivationSignInRequiredView(flowId);
|
|
342
|
+
} finally {
|
|
343
|
+
this.state.loading = false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
stop(): void {
|
|
348
|
+
this.#observationRunId += 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async signIn(): Promise<void> {
|
|
352
|
+
this.state.authError = null;
|
|
353
|
+
if (!this.state.flowId || !this.#sessionStorage) return;
|
|
354
|
+
|
|
355
|
+
const callbackToken = this.#createCallbackToken();
|
|
356
|
+
preserveDeviceActivationCallbackState(this.#sessionStorage, {
|
|
357
|
+
flowId: this.state.flowId,
|
|
358
|
+
callbackToken,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
await this.#authState.signIn({
|
|
363
|
+
redirectTo: buildDeviceActivationCallbackPath(
|
|
364
|
+
this.#getUrl(),
|
|
365
|
+
callbackToken,
|
|
366
|
+
),
|
|
367
|
+
});
|
|
368
|
+
} catch (error) {
|
|
369
|
+
const message = redirectErrorMessage(error);
|
|
370
|
+
if (message) {
|
|
371
|
+
this.state.authError = message;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async requestActivation(): Promise<void> {
|
|
377
|
+
const flowId = this.state.flowId;
|
|
378
|
+
const confirmationCode = this.state.confirmationCode.trim();
|
|
379
|
+
if (!flowId || !confirmationCode || !this.#client) return;
|
|
380
|
+
|
|
381
|
+
this.stop();
|
|
382
|
+
const runId = this.#observationRunId;
|
|
383
|
+
this.state.requestPending = true;
|
|
384
|
+
this.state.authError = null;
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
let operation = await this.#client.activateDevice({
|
|
388
|
+
flowId,
|
|
389
|
+
confirmationCode,
|
|
390
|
+
});
|
|
391
|
+
if (hasOperationSnapshot(operation)) {
|
|
392
|
+
let snapshot = await operation.get().match({
|
|
393
|
+
ok: (value) => value,
|
|
394
|
+
err: () => null,
|
|
395
|
+
});
|
|
396
|
+
for (
|
|
397
|
+
let attempt = 0;
|
|
398
|
+
attempt < 40 &&
|
|
399
|
+
!snapshot?.progress?.companionConsent &&
|
|
400
|
+
snapshot?.state === "running" &&
|
|
401
|
+
this.#isRunActive(runId);
|
|
402
|
+
attempt++
|
|
403
|
+
) {
|
|
404
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
405
|
+
snapshot = await operation.get().match({
|
|
406
|
+
ok: (value) => value,
|
|
407
|
+
err: () => null,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (snapshot?.progress && this.#isRunActive(runId)) {
|
|
411
|
+
this.state.view = mapDeviceActivationProgress(
|
|
412
|
+
flowId,
|
|
413
|
+
snapshot.progress,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
const companionConsent = snapshot?.progress?.companionConsent;
|
|
417
|
+
if (companionConsent && this.#isRunActive(runId)) {
|
|
418
|
+
try {
|
|
419
|
+
operation = await this.#client.activateDevice({
|
|
420
|
+
flowId,
|
|
421
|
+
confirmationCode,
|
|
422
|
+
companionApproval: buildCompanionApproval(companionConsent),
|
|
423
|
+
});
|
|
424
|
+
} catch (error) {
|
|
425
|
+
// Only a permission outcome means a reviewer must decide later;
|
|
426
|
+
// malformed, stale, or transport failures stay visible/retryable.
|
|
427
|
+
if (!isDeviceActivationReviewerRequiredFailure(error)) {
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const watch = await operation.watch().match({
|
|
434
|
+
ok: (value) => value,
|
|
435
|
+
err: () => null,
|
|
436
|
+
});
|
|
437
|
+
const watchPromise = watch
|
|
438
|
+
? this.#observeWatch(flowId, watch, runId)
|
|
439
|
+
: Promise.resolve(false);
|
|
440
|
+
const terminal = await operation.wait().orThrow();
|
|
441
|
+
const handledByWatch = await watchPromise;
|
|
442
|
+
if (!handledByWatch && this.#isRunActive(runId)) {
|
|
443
|
+
this.#applyTerminal(flowId, terminal);
|
|
444
|
+
}
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (!this.#isRunActive(runId)) return;
|
|
447
|
+
|
|
448
|
+
const nextView = mapDeviceActivationFailure(flowId, error);
|
|
449
|
+
if (nextView) {
|
|
450
|
+
this.state.view = nextView;
|
|
451
|
+
} else {
|
|
452
|
+
this.state.view = createDeviceActivationReadyView(flowId);
|
|
453
|
+
this.state.authError = errorMessage(error);
|
|
454
|
+
}
|
|
455
|
+
} finally {
|
|
456
|
+
if (this.#isRunActive(runId)) {
|
|
457
|
+
this.state.requestPending = false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
#isRunActive(runId: number): boolean {
|
|
463
|
+
return this.#observationRunId === runId;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
#applyTerminal(
|
|
467
|
+
flowId: string,
|
|
468
|
+
terminal: TerminalOperation<
|
|
469
|
+
DeviceUserAuthoritiesResolveProgress,
|
|
470
|
+
DeviceUserAuthoritiesResolveOutput
|
|
471
|
+
>,
|
|
472
|
+
): void {
|
|
473
|
+
const view = mapDeviceActivationTerminal(flowId, terminal);
|
|
474
|
+
if (view) {
|
|
475
|
+
this.state.view = view;
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
this.state.view = createDeviceActivationReadyView(flowId);
|
|
480
|
+
if (terminal.error?.message) {
|
|
481
|
+
this.state.authError = terminal.error.message;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async #observeWatch(
|
|
486
|
+
flowId: string,
|
|
487
|
+
watch: AsyncIterable<
|
|
488
|
+
OperationEvent<
|
|
489
|
+
DeviceUserAuthoritiesResolveProgress,
|
|
490
|
+
DeviceUserAuthoritiesResolveOutput
|
|
491
|
+
>
|
|
492
|
+
>,
|
|
493
|
+
runId: number,
|
|
494
|
+
): Promise<boolean> {
|
|
495
|
+
const iterator = watch[Symbol.asyncIterator]();
|
|
496
|
+
|
|
497
|
+
while (true) {
|
|
498
|
+
const next = await iterator.next();
|
|
499
|
+
if (next.done) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const event = next.value;
|
|
504
|
+
if (!this.#isRunActive(runId)) {
|
|
505
|
+
await iterator.return?.();
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (
|
|
510
|
+
event.type === "completed" || event.type === "failed" ||
|
|
511
|
+
event.type === "cancelled"
|
|
512
|
+
) {
|
|
513
|
+
this.#applyTerminal(flowId, event.snapshot);
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (event.type === "progress") {
|
|
518
|
+
this.state.view = mapDeviceActivationProgress(flowId, event.progress);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function hasOperationSnapshot(
|
|
525
|
+
operation: DeviceActivationOperationRef,
|
|
526
|
+
): operation is SnapshotCapableDeviceActivationOperationRef {
|
|
527
|
+
return "get" in operation && typeof operation.get === "function";
|
|
528
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export { default as TrellisProvider } from "./components/TrellisProvider.svelte";
|
|
2
|
+
export type { TrellisProviderProps } from "./components/TrellisProvider.types.ts";
|
|
3
|
+
export type {
|
|
4
|
+
CreateTrellisAppOptions,
|
|
5
|
+
SvelteTrellisConnection,
|
|
6
|
+
TrellisApp,
|
|
7
|
+
TrellisAppOwner,
|
|
8
|
+
TrellisAppUrl,
|
|
9
|
+
TrellisAppUrlResolver,
|
|
10
|
+
TrellisClientFor,
|
|
11
|
+
TrellisContextClient,
|
|
12
|
+
TrellisParticipantLike,
|
|
13
|
+
} from "./context.svelte.ts";
|
|
14
|
+
export { createTrellisApp, resolveTrellisAppUrl } from "./context.svelte.ts";
|
|
15
|
+
export {
|
|
16
|
+
AuthorizationContextController,
|
|
17
|
+
type AuthorizationContextStatus,
|
|
18
|
+
} from "./authorization_context.svelte.ts";
|
|
19
|
+
export {
|
|
20
|
+
createDeviceActivationController,
|
|
21
|
+
type DeviceActivationAuth,
|
|
22
|
+
type DeviceActivationClient,
|
|
23
|
+
DeviceActivationController,
|
|
24
|
+
type DeviceActivationControllerConfig,
|
|
25
|
+
type DeviceActivationOperationRef,
|
|
26
|
+
} from "./device_activation.svelte.ts";
|
|
27
|
+
export {
|
|
28
|
+
createPortalFlow,
|
|
29
|
+
type CreatePortalFlowConfig,
|
|
30
|
+
PortalFlowController,
|
|
31
|
+
} from "./portal_flow.svelte.ts";
|