@sanity/workbench 0.1.0-alpha.12 → 0.1.0-alpha.14
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/_chunks-es/index.js +6 -1
- package/dist/_chunks-es/index.js.map +1 -1
- package/dist/_chunks-es/studio.js +798 -0
- package/dist/_chunks-es/studio.js.map +1 -0
- package/dist/core.d.ts +132 -34
- package/dist/core.js +3 -729
- package/dist/core.js.map +1 -1
- package/dist/system.d.ts +681 -0
- package/dist/system.js +528 -0
- package/dist/system.js.map +1 -0
- package/package.json +12 -1
- package/src/_exports/system.ts +1 -0
- package/src/core/log/index.ts +33 -0
- package/src/core/user-applications/core-app.ts +19 -6
- package/src/core/user-applications/studios/schemas.ts +12 -1
- package/src/core/user-applications/studios/studio.ts +92 -77
- package/src/core/user-applications/studios/workspace.ts +5 -0
- package/src/core/user-applications/user-application.ts +17 -1
- package/src/system/auth.machine.ts +223 -0
- package/src/system/index.ts +11 -0
- package/src/system/inspect.ts +40 -0
- package/src/system/root.machine.ts +190 -0
- package/src/system/system-preferences.machine.ts +215 -0
- package/src/system/telemetry.machine.ts +179 -0
package/src/core/log/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface Logger {
|
|
|
26
26
|
warn: (message: string, context?: LogContext) => void;
|
|
27
27
|
info: (message: string, context?: LogContext) => void;
|
|
28
28
|
debug: (message: string, context?: LogContext) => void;
|
|
29
|
+
child: (domain: string, context?: LogContext) => Logger;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const LEVELS: readonly LogLevel[] = ["none", "error", "warn", "info", "debug"];
|
|
@@ -37,6 +38,31 @@ interface LoggerOptions {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
41
|
+
* Creates a leveled logger with an optional namespace prefix and bound
|
|
42
|
+
* context. Calls below the configured `logLevel` are suppressed; `"none"`
|
|
43
|
+
* silences the logger entirely.
|
|
44
|
+
*
|
|
45
|
+
* Use {@link Logger.child} to derive a sub-logger with an extended namespace
|
|
46
|
+
* (e.g. `parent:domain`) that inherits the parent's level and merges its
|
|
47
|
+
* bound context.
|
|
48
|
+
*
|
|
49
|
+
* @param options - Logger configuration.
|
|
50
|
+
* @param options.namespace - Prepended to every message in `[brackets]`.
|
|
51
|
+
* @param options.context - Bound context merged with per-call context.
|
|
52
|
+
* Per-call keys win on conflict.
|
|
53
|
+
* @param options.logLevel - Maximum verbosity to emit. Default `"info"`.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const log = createLogger({ namespace: "checkout", logLevel: "debug" });
|
|
58
|
+
* log.info("placed", { orderId: "abc" });
|
|
59
|
+
* // → [checkout] placed { orderId: "abc" }
|
|
60
|
+
*
|
|
61
|
+
* const auth = log.child("auth", { tenant: "acme" });
|
|
62
|
+
* auth.warn("token expiring");
|
|
63
|
+
* // → [checkout:auth] token expiring { tenant: "acme" }
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
40
66
|
* @public
|
|
41
67
|
*/
|
|
42
68
|
export function createLogger({
|
|
@@ -76,6 +102,13 @@ export function createLogger({
|
|
|
76
102
|
warn: (message, context) => logAtLevel("warn", message, context),
|
|
77
103
|
info: (message, context) => logAtLevel("info", message, context),
|
|
78
104
|
debug: (message, context) => logAtLevel("debug", message, context),
|
|
105
|
+
child: (domain, context) =>
|
|
106
|
+
createLogger({
|
|
107
|
+
logLevel,
|
|
108
|
+
namespace: namespace ? `${namespace}:${domain}` : domain,
|
|
109
|
+
context:
|
|
110
|
+
baseContext || context ? { ...baseContext, ...context } : undefined,
|
|
111
|
+
}),
|
|
79
112
|
};
|
|
80
113
|
}
|
|
81
114
|
|
|
@@ -29,19 +29,22 @@ const CoreAppUserApplicationBase = UserApplicationBase.extend({
|
|
|
29
29
|
title: z.string(),
|
|
30
30
|
organizationId: OrganizationId,
|
|
31
31
|
type: z.literal("coreApp"),
|
|
32
|
-
manifest: CoreAppUserApplicationManifest.nullable().optional(),
|
|
33
32
|
});
|
|
34
33
|
|
|
34
|
+
// Core apps surface their manifest exclusively via `activeDeployment.manifest`
|
|
35
|
+
// — they have no top-level `manifest` field and no `manifestData`.
|
|
36
|
+
const CoreAppActiveDeployment = ActiveDeployment.extend({
|
|
37
|
+
manifest: CoreAppUserApplicationManifest.nullable(),
|
|
38
|
+
}).nullable();
|
|
39
|
+
|
|
35
40
|
const InternalCoreAppUserApplication = CoreAppUserApplicationBase.extend({
|
|
36
41
|
urlType: z.literal("internal"),
|
|
37
|
-
activeDeployment:
|
|
38
|
-
manifest: CoreAppUserApplicationManifest.nullable().optional(),
|
|
39
|
-
}).nullable(),
|
|
42
|
+
activeDeployment: CoreAppActiveDeployment,
|
|
40
43
|
});
|
|
41
44
|
|
|
42
45
|
const ExternalCoreAppUserApplication = CoreAppUserApplicationBase.extend({
|
|
43
46
|
urlType: z.literal("external"),
|
|
44
|
-
activeDeployment:
|
|
47
|
+
activeDeployment: CoreAppActiveDeployment,
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
/**
|
|
@@ -98,7 +101,17 @@ export class CoreAppApplication extends UserApplication<
|
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
get title() {
|
|
101
|
-
return this.
|
|
104
|
+
return this.manifest?.title ?? this.application.title;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolves the core app's manifest from `activeDeployment.manifest`. This
|
|
109
|
+
* is the canonical (and only) slot for core app manifests — both for
|
|
110
|
+
* Sanity-deployed apps and for local CLI dev-server apps, which synthesise
|
|
111
|
+
* a deployment to surface the live manifest.
|
|
112
|
+
*/
|
|
113
|
+
get manifest(): CoreAppUserApplicationManifest | null {
|
|
114
|
+
return this.activeDeployment?.manifest ?? null;
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
get subtitle() {
|
|
@@ -18,7 +18,10 @@ const Workspace = z.object({
|
|
|
18
18
|
*/
|
|
19
19
|
export type Workspace = z.output<typeof Workspace>;
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
export const ServerManifest = z.object({
|
|
22
25
|
buildId: z.string().optional(),
|
|
23
26
|
bundleVersion: z.string().optional(),
|
|
24
27
|
version: z.string().optional(),
|
|
@@ -32,6 +35,11 @@ const ServerManifest = z.object({
|
|
|
32
35
|
.optional(),
|
|
33
36
|
});
|
|
34
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export type ServerManifest = z.output<typeof ServerManifest>;
|
|
42
|
+
|
|
35
43
|
/**
|
|
36
44
|
* @public
|
|
37
45
|
*/
|
|
@@ -56,6 +64,9 @@ const StudioUserApplicationBase = UserApplicationBase.extend({
|
|
|
56
64
|
title: z.string().nullable(),
|
|
57
65
|
projectId: ProjectId,
|
|
58
66
|
type: z.literal("studio"),
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated Use `manifestData` instead.
|
|
69
|
+
*/
|
|
59
70
|
manifest: ClientManifest.nullable(),
|
|
60
71
|
manifestData: z.object({ value: ClientManifest }).nullable(),
|
|
61
72
|
autoUpdatingVersion: z.string().nullable(),
|
|
@@ -1,40 +1,52 @@
|
|
|
1
1
|
import type { SemVer } from "semver";
|
|
2
2
|
import { coerce, gt, gte, lt, rsort, valid } from "semver";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
|
|
4
5
|
import type { Project } from "../../projects";
|
|
5
6
|
import { UserApplication } from "../user-application";
|
|
6
7
|
import {
|
|
8
|
+
type ClientManifest as ClientManifestType,
|
|
9
|
+
type ServerManifest as ServerManifestType,
|
|
7
10
|
type Workspace,
|
|
8
11
|
type StudioUserApplication,
|
|
9
12
|
ClientManifest as ClientManifestSchema,
|
|
13
|
+
ServerManifest as ServerManifestSchema,
|
|
10
14
|
} from "./schemas";
|
|
11
15
|
import { StudioWorkspace } from "./workspace";
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Validated manifest — accepts either a refined client manifest (with
|
|
19
|
+
* version ≥ 2 and `studioVersion` set for v3+) or a server/deployment
|
|
20
|
+
* manifest. Parsing fails when the input matches neither shape.
|
|
21
|
+
*/
|
|
22
|
+
const Manifest = z.union([
|
|
23
|
+
ClientManifestSchema.superRefine((data, ctx) => {
|
|
24
|
+
if (!data.version) {
|
|
25
|
+
ctx.addIssue({
|
|
26
|
+
code: "invalid_type",
|
|
27
|
+
message: "Manifest version is too old",
|
|
28
|
+
expected: "number",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
if (data.version < 2) {
|
|
33
|
+
ctx.addIssue({
|
|
34
|
+
code: "invalid_value",
|
|
35
|
+
message: "Manifest version is too old",
|
|
36
|
+
values: [2, 3],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
})
|
|
40
|
+
if (data.version >= 3 && !data.studioVersion) {
|
|
41
|
+
ctx.addIssue({
|
|
42
|
+
code: "invalid_type",
|
|
43
|
+
message: "Manifest version 3 or higher requires a `studioVersion`",
|
|
44
|
+
expected: "string",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
48
|
+
ServerManifestSchema,
|
|
49
|
+
]);
|
|
38
50
|
|
|
39
51
|
const DEFAULT_WORKSPACE_DATA = {
|
|
40
52
|
name: "default",
|
|
@@ -83,21 +95,18 @@ export class StudioApplication extends UserApplication<
|
|
|
83
95
|
super(application, "studio", options);
|
|
84
96
|
|
|
85
97
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
98
|
+
* Derive workspaces from the application's most authoritative manifest.
|
|
99
|
+
* `this.manifest` already prefers the deployment manifest over the
|
|
100
|
+
* client manifest; the `Manifest` schema accepts either shape and
|
|
101
|
+
* rejects malformed ones (e.g. a v3 client manifest missing
|
|
102
|
+
* `studioVersion`). A parse failure logs a warning and falls through
|
|
103
|
+
* to the default-workspace path rather than crashing.
|
|
90
104
|
*/
|
|
91
105
|
let workspaces: Workspace[] = [];
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* If the workspaces fail to parse we dont want to throw an error because
|
|
95
|
-
* this could potentially crash the app. Instead we log a warning and return an empty array.
|
|
96
|
-
*/
|
|
106
|
+
const manifest = this.manifest;
|
|
107
|
+
if (manifest) {
|
|
97
108
|
try {
|
|
98
|
-
workspaces =
|
|
99
|
-
application.manifestData?.value,
|
|
100
|
-
).workspaces;
|
|
109
|
+
workspaces = Manifest.parse(manifest).workspaces ?? [];
|
|
101
110
|
} catch (error) {
|
|
102
111
|
console.warn(
|
|
103
112
|
`Failed to parse manifest for application ${application.id}`,
|
|
@@ -106,34 +115,6 @@ export class StudioApplication extends UserApplication<
|
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
if (workspaces.length === 0) {
|
|
110
|
-
/**
|
|
111
|
-
* If the user application does not have a valid manifest or no workspaces,
|
|
112
|
-
* we attempt to get the manifest from the server-side maninfest first and then
|
|
113
|
-
* fall back to the live-manifest.
|
|
114
|
-
*
|
|
115
|
-
* In the future this should happen even before checking the traditional manifest,
|
|
116
|
-
* but for now we keep the current order to not introduce breaking changes.
|
|
117
|
-
*
|
|
118
|
-
* The live manifest is inherintly less reliable as it depends on which user has
|
|
119
|
-
* uploaded it, which is why this is wrapped in a feature flag for now.
|
|
120
|
-
*/
|
|
121
|
-
const deploymentManifest = application.activeDeployment?.manifest;
|
|
122
|
-
// const liveManifest = growthbook.isOn('dashboard-use-live-manifest')
|
|
123
|
-
// ? application.config['live-manifest']?.value
|
|
124
|
-
// : null
|
|
125
|
-
|
|
126
|
-
const serverManifest = deploymentManifest; /*?? liveManifest*/
|
|
127
|
-
const serverManifestWorkspaces = serverManifest?.workspaces;
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
Array.isArray(serverManifestWorkspaces) &&
|
|
131
|
-
serverManifestWorkspaces.length
|
|
132
|
-
) {
|
|
133
|
-
workspaces = serverManifestWorkspaces;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
118
|
/**
|
|
138
119
|
* Filter all the workspaces that have a project the user does not have access to.
|
|
139
120
|
*/
|
|
@@ -236,24 +217,57 @@ export class StudioApplication extends UserApplication<
|
|
|
236
217
|
return this.application[attr];
|
|
237
218
|
}
|
|
238
219
|
|
|
239
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Resolves the studio's most authoritative manifest. The lookup order is:
|
|
222
|
+
*
|
|
223
|
+
* 1. `activeDeployment.manifest` — deployment manifests are the new
|
|
224
|
+
* primitive within Sanity and are preferred whenever available.
|
|
225
|
+
* 2. `manifestData.value` — fallback to support older studios that
|
|
226
|
+
* haven't produced a deployment manifest yet.
|
|
227
|
+
* 3. `application.manifest` — last-resort legacy field.
|
|
228
|
+
*
|
|
229
|
+
* Returns `null` when no manifest is available. The return shape is a
|
|
230
|
+
* union because the deployment and client manifests are not
|
|
231
|
+
* interchangeable — consumers that depend on client-only fields (e.g.
|
|
232
|
+
* `studioVersion`, workspace `schema`) must narrow or read the raw
|
|
233
|
+
* field they need.
|
|
234
|
+
*/
|
|
235
|
+
get manifest(): ClientManifestType | ServerManifestType | null {
|
|
240
236
|
return (
|
|
241
|
-
|
|
242
|
-
this.application.manifestData?.value
|
|
237
|
+
this.application.activeDeployment?.manifest ??
|
|
238
|
+
this.application.manifestData?.value ??
|
|
239
|
+
this.application.manifest ??
|
|
240
|
+
null
|
|
243
241
|
);
|
|
244
242
|
}
|
|
245
243
|
|
|
244
|
+
get hasManifest(): boolean {
|
|
245
|
+
const manifest = this.manifest;
|
|
246
|
+
if (!manifest) return false;
|
|
247
|
+
|
|
248
|
+
// `manifest` is a union: server manifests encode `version` as an optional
|
|
249
|
+
// string, client manifests as a required number. The typeof check doubles
|
|
250
|
+
// as a discriminator — if it's not a number, we're looking at a server
|
|
251
|
+
// manifest whose presence alone is authoritative.
|
|
252
|
+
return typeof manifest.version !== "number" || manifest.version >= 2;
|
|
253
|
+
}
|
|
254
|
+
|
|
246
255
|
get hasSchema(): boolean {
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
256
|
+
// A deployment manifest tracks a schema descriptor for every workspace,
|
|
257
|
+
// so its presence is sufficient to short-circuit.
|
|
258
|
+
if (this.application.activeDeployment?.manifest) return true;
|
|
259
|
+
|
|
260
|
+
// Otherwise inspect the client manifest. We cannot look at
|
|
261
|
+
// `this.workspaces` because it won't contain workspaces the user does
|
|
262
|
+
// not have access to. The application has a schema even if the user
|
|
263
|
+
// can't access any of the workspaces, otherwise it would be displayed
|
|
264
|
+
// as not having a manifest in the setup guide and therefore considered
|
|
250
265
|
// partially compatible.
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
266
|
+
const clientManifest =
|
|
267
|
+
this.application.manifestData?.value ?? this.application.manifest;
|
|
268
|
+
const workspaces = clientManifest?.workspaces ?? [];
|
|
256
269
|
|
|
270
|
+
if (workspaces.length === 0) return false;
|
|
257
271
|
return workspaces.every((w) => Boolean(w.schema));
|
|
258
272
|
}
|
|
259
273
|
|
|
@@ -265,12 +279,13 @@ export class StudioApplication extends UserApplication<
|
|
|
265
279
|
if (this.get("urlType") === "internal") {
|
|
266
280
|
version = this.get("activeDeployment")?.version;
|
|
267
281
|
} else {
|
|
268
|
-
const
|
|
282
|
+
const clientManifest =
|
|
283
|
+
this.get("manifestData")?.value ?? this.get("manifest");
|
|
269
284
|
// The property 'studioVersion' exists only on external studios,
|
|
270
285
|
// starting from manifest.version 3
|
|
271
286
|
version =
|
|
272
|
-
|
|
273
|
-
?
|
|
287
|
+
clientManifest && "studioVersion" in clientManifest
|
|
288
|
+
? clientManifest.studioVersion
|
|
274
289
|
: undefined;
|
|
275
290
|
}
|
|
276
291
|
|
|
@@ -108,7 +108,12 @@ export class StudioWorkspace extends AbstractApplication<
|
|
|
108
108
|
id: this.id,
|
|
109
109
|
hasManifest: true,
|
|
110
110
|
hasSchema: Boolean("schema" in this.workspace && this.workspace.schema),
|
|
111
|
+
// The protocol resource requires the client-shaped manifest (workspaces
|
|
112
|
+
// with `schema`). Read those sources directly rather than the unified
|
|
113
|
+
// `manifest` getter, which may return a `ServerManifest` for studios
|
|
114
|
+
// with a deployment manifest.
|
|
111
115
|
manifest: (this.studio.get("manifestData")?.value ??
|
|
116
|
+
this.studio.get("manifest") ??
|
|
112
117
|
null) as ProtocolStudioResource["manifest"],
|
|
113
118
|
updatedAt: this.studio.get("updatedAt"),
|
|
114
119
|
version: this.studio.get("activeDeployment")?.version,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "../applications/application";
|
|
9
9
|
import { getSanityDomain, getSanityEnv } from "../env";
|
|
10
10
|
import type { CoreAppUserApplicationManifest } from "./core-app";
|
|
11
|
-
import type { ClientManifest } from "./studios/schemas";
|
|
11
|
+
import type { ClientManifest, ServerManifest } from "./studios/schemas";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* User application ID schema, branded for type safety.
|
|
@@ -186,4 +186,20 @@ export abstract class UserApplication<
|
|
|
186
186
|
|
|
187
187
|
return new URL(this.application.appHost);
|
|
188
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Unified manifest accessor for user applications. Resolves the most
|
|
192
|
+
* authoritative manifest available for the application, preferring the
|
|
193
|
+
* deployment manifest (`activeDeployment.manifest`) before falling back to
|
|
194
|
+
* any client-side manifest. The returned shape depends on the application
|
|
195
|
+
* type and source: studios may yield either a `ServerManifest` (deployment)
|
|
196
|
+
* or a `ClientManifest` (fallback); core apps yield a
|
|
197
|
+
* `CoreAppUserApplicationManifest`. Returns `null` when no manifest is
|
|
198
|
+
* available.
|
|
199
|
+
*/
|
|
200
|
+
abstract get manifest():
|
|
201
|
+
| ClientManifest
|
|
202
|
+
| ServerManifest
|
|
203
|
+
| CoreAppUserApplicationManifest
|
|
204
|
+
| null;
|
|
189
205
|
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AuthState,
|
|
3
|
+
AuthStateType,
|
|
4
|
+
type CurrentUser,
|
|
5
|
+
getAuthState,
|
|
6
|
+
type LoggedInAuthState,
|
|
7
|
+
logout,
|
|
8
|
+
type SanityInstance,
|
|
9
|
+
} from "@sanity/sdk";
|
|
10
|
+
import { assign, fromObservable, fromPromise, setup } from "xstate";
|
|
11
|
+
|
|
12
|
+
import type { OSBaseInput } from "./root.machine";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export interface AuthInput extends OSBaseInput {}
|
|
18
|
+
|
|
19
|
+
const authStateLogic = fromObservable<AuthState, AuthInput>(
|
|
20
|
+
({ input }) => getAuthState(input.instance).observable,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export interface LogoutInput extends OSBaseInput {}
|
|
27
|
+
|
|
28
|
+
const logoutActorLogic = fromPromise<void, LogoutInput>(async ({ input }) => {
|
|
29
|
+
await logout(input.instance);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
type AuthContext = {
|
|
33
|
+
instance: SanityInstance;
|
|
34
|
+
token: string | null;
|
|
35
|
+
currentUser: CurrentUser | null;
|
|
36
|
+
error: unknown;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type AuthEvent = { type: "auth.logout" };
|
|
40
|
+
|
|
41
|
+
export const authLogic = setup({
|
|
42
|
+
types: {
|
|
43
|
+
input: {} as AuthInput,
|
|
44
|
+
context: {} as AuthContext,
|
|
45
|
+
events: {} as AuthEvent,
|
|
46
|
+
tags: {} as "authenticating" | "authenticated" | "error",
|
|
47
|
+
},
|
|
48
|
+
actors: {
|
|
49
|
+
authState: authStateLogic,
|
|
50
|
+
logoutActor: logoutActorLogic,
|
|
51
|
+
},
|
|
52
|
+
delays: {
|
|
53
|
+
authTimeout: 30_000,
|
|
54
|
+
},
|
|
55
|
+
guards: {
|
|
56
|
+
isLoggedInComplete: (_, params: { state: AuthState | undefined }) =>
|
|
57
|
+
params.state?.type === AuthStateType.LOGGED_IN &&
|
|
58
|
+
Boolean((params.state as LoggedInAuthState).token) &&
|
|
59
|
+
(params.state as LoggedInAuthState).currentUser !== null,
|
|
60
|
+
isAuthState: (
|
|
61
|
+
_,
|
|
62
|
+
params: { state: AuthState | undefined; type: AuthStateType },
|
|
63
|
+
) => params.state?.type === params.type,
|
|
64
|
+
},
|
|
65
|
+
actions: {
|
|
66
|
+
setLoggedIn: assign({
|
|
67
|
+
token: (_, params: { token: string; currentUser: CurrentUser }) =>
|
|
68
|
+
params.token,
|
|
69
|
+
currentUser: (_, params: { token: string; currentUser: CurrentUser }) =>
|
|
70
|
+
params.currentUser,
|
|
71
|
+
error: () => null,
|
|
72
|
+
}),
|
|
73
|
+
clearAuth: assign({
|
|
74
|
+
token: () => null,
|
|
75
|
+
currentUser: () => null,
|
|
76
|
+
error: () => null,
|
|
77
|
+
}),
|
|
78
|
+
setError: assign({
|
|
79
|
+
token: () => null,
|
|
80
|
+
currentUser: () => null,
|
|
81
|
+
error: (_, params: { error: unknown }) => params.error,
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
}).createMachine({
|
|
85
|
+
id: "auth",
|
|
86
|
+
initial: "init",
|
|
87
|
+
context: ({ input }) => ({
|
|
88
|
+
instance: input.instance,
|
|
89
|
+
token: null,
|
|
90
|
+
currentUser: null,
|
|
91
|
+
error: null,
|
|
92
|
+
}),
|
|
93
|
+
invoke: {
|
|
94
|
+
src: "authState",
|
|
95
|
+
input: ({ context }) => ({ instance: context.instance }),
|
|
96
|
+
onSnapshot: [
|
|
97
|
+
{
|
|
98
|
+
guard: {
|
|
99
|
+
type: "isLoggedInComplete",
|
|
100
|
+
params: ({ event }) => ({
|
|
101
|
+
state: event.snapshot.context,
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
actions: [
|
|
105
|
+
{
|
|
106
|
+
type: "setLoggedIn",
|
|
107
|
+
params: ({ event }) => {
|
|
108
|
+
const state = event.snapshot.context as LoggedInAuthState;
|
|
109
|
+
return {
|
|
110
|
+
token: state.token,
|
|
111
|
+
currentUser: state.currentUser!,
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
target: `.${AuthStateType.LOGGED_IN}`,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
guard: {
|
|
120
|
+
type: "isAuthState",
|
|
121
|
+
params: ({ event }) => ({
|
|
122
|
+
state: event.snapshot.context,
|
|
123
|
+
type: AuthStateType.LOGGING_IN,
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
actions: [{ type: "clearAuth" }],
|
|
127
|
+
target: `.${AuthStateType.LOGGING_IN}`,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
guard: {
|
|
131
|
+
type: "isAuthState",
|
|
132
|
+
params: ({ event }) => ({
|
|
133
|
+
state: event.snapshot.context,
|
|
134
|
+
type: AuthStateType.ERROR,
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
actions: [
|
|
138
|
+
{
|
|
139
|
+
type: "setError",
|
|
140
|
+
params: ({ event }) => ({
|
|
141
|
+
error:
|
|
142
|
+
event.snapshot.context?.type === AuthStateType.ERROR
|
|
143
|
+
? event.snapshot.context.error
|
|
144
|
+
: null,
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
target: `.${AuthStateType.ERROR}`,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
guard: {
|
|
152
|
+
type: "isAuthState",
|
|
153
|
+
params: ({ event }) => ({
|
|
154
|
+
state: event.snapshot.context,
|
|
155
|
+
type: AuthStateType.LOGGED_OUT,
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
actions: [{ type: "clearAuth" }],
|
|
159
|
+
target: `.${AuthStateType.LOGGED_OUT}`,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
states: {
|
|
164
|
+
init: {
|
|
165
|
+
tags: ["authenticating"],
|
|
166
|
+
after: {
|
|
167
|
+
authTimeout: {
|
|
168
|
+
actions: [
|
|
169
|
+
{
|
|
170
|
+
type: "setError",
|
|
171
|
+
params: () => ({
|
|
172
|
+
error: new Error("Authentication timed out"),
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
target: AuthStateType.ERROR,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
[AuthStateType.LOGGING_IN]: {
|
|
181
|
+
tags: ["authenticating"],
|
|
182
|
+
after: {
|
|
183
|
+
authTimeout: {
|
|
184
|
+
actions: [
|
|
185
|
+
{
|
|
186
|
+
type: "setError",
|
|
187
|
+
params: () => ({
|
|
188
|
+
error: new Error("Authentication timed out"),
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
target: AuthStateType.ERROR,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
[AuthStateType.LOGGED_IN]: {
|
|
197
|
+
tags: ["authenticated"],
|
|
198
|
+
on: {
|
|
199
|
+
"auth.logout": { target: "logging-out" },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
["logging-out"]: {
|
|
203
|
+
invoke: {
|
|
204
|
+
src: "logoutActor",
|
|
205
|
+
input: ({ context }) => ({ instance: context.instance }),
|
|
206
|
+
onDone: {
|
|
207
|
+
target: AuthStateType.LOGGED_OUT,
|
|
208
|
+
},
|
|
209
|
+
onError: {
|
|
210
|
+
actions: [
|
|
211
|
+
{
|
|
212
|
+
type: "setError",
|
|
213
|
+
params: ({ event }) => ({ error: event.error }),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
target: AuthStateType.ERROR,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
[AuthStateType.LOGGED_OUT]: {},
|
|
221
|
+
[AuthStateType.ERROR]: { tags: ["error"] },
|
|
222
|
+
},
|
|
223
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { AuthInput, LogoutInput } from "./auth.machine";
|
|
2
|
+
export type {
|
|
3
|
+
SystemPreferencesAdapter,
|
|
4
|
+
SystemPreferencesContext,
|
|
5
|
+
SystemPreferencesEvent,
|
|
6
|
+
} from "./system-preferences.machine";
|
|
7
|
+
export { os, createOSOptions } from "./root.machine";
|
|
8
|
+
export type {
|
|
9
|
+
TelemetryInput,
|
|
10
|
+
WorkbenchUserProperties,
|
|
11
|
+
} from "./telemetry.machine";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ActorRefLike, type InspectionEvent } from "xstate";
|
|
2
|
+
|
|
3
|
+
import { logger } from "../core";
|
|
4
|
+
|
|
5
|
+
// Mirrors @statelyai/inspect's ActorRefLikeWithData — the inspect
|
|
6
|
+
// callback receives full Actor instances at runtime, but the type is
|
|
7
|
+
// narrowed for @xstate/store compat.
|
|
8
|
+
type ActorRefWithAncestry = ActorRefLike & {
|
|
9
|
+
id?: string;
|
|
10
|
+
_parent?: ActorRefWithAncestry;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const actorPath = (ref: ActorRefWithAncestry): string => {
|
|
14
|
+
const segments: string[] = [];
|
|
15
|
+
let current: ActorRefWithAncestry | undefined = ref;
|
|
16
|
+
while (current) {
|
|
17
|
+
segments.unshift(current.id ?? current.sessionId);
|
|
18
|
+
current = current._parent;
|
|
19
|
+
}
|
|
20
|
+
return segments.join(":");
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** @internal exported for testing */
|
|
24
|
+
export const inspect = (event: InspectionEvent): void => {
|
|
25
|
+
const ref = event.actorRef as ActorRefWithAncestry;
|
|
26
|
+
const log = logger.child(actorPath(ref));
|
|
27
|
+
|
|
28
|
+
switch (event.type) {
|
|
29
|
+
case "@xstate.snapshot": {
|
|
30
|
+
log.debug("snapshot", event.snapshot);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "@xstate.event":
|
|
34
|
+
log.debug("event", event.event);
|
|
35
|
+
break;
|
|
36
|
+
case "@xstate.action":
|
|
37
|
+
log.debug("action", event.action);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
};
|