@jskit-ai/kernel 0.1.245 → 0.1.247
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/client/appConfig.js +2 -1
- package/client/asyncModuleRecovery.js +2 -4
- package/client/index.d.ts +13 -75
- package/client/index.js +1 -1
- package/client/logging.js +8 -2
- package/client/moduleBootstrap.js +41 -33
- package/client/moduleBootstrap.test.js +35 -0
- package/client/shellBootstrap.js +1 -8
- package/client/shellBootstrap.test.js +32 -5
- package/client/shellRouting.js +16 -61
- package/client/shellRouting.test.js +30 -1
- package/client/vite/clientBootstrapPlugin.js +0 -6
- package/client/vite/clientBootstrapPlugin.test.js +2 -3
- package/internal/node/installedPackages.js +19 -62
- package/internal/node/installedPackages.test.js +33 -0
- package/package.json +2 -4
- package/server/actions/actionCatalogue.js +13 -34
- package/{shared → server}/actions/actionContributorHelpers.js +0 -5
- package/{shared → server}/actions/actionDefinitions.js +24 -131
- package/{shared → server}/actions/actionDefinitions.test.js +75 -21
- package/server/actions/actionEvents.js +97 -2
- package/server/actions/actionEvents.test.js +79 -0
- package/{shared → server}/actions/executionContext.js +3 -13
- package/server/actions/idempotency.js +24 -0
- package/server/actions/pipeline.js +87 -0
- package/{shared → server}/actions/policies.js +20 -33
- package/{shared → server}/actions/policies.test.js +6 -6
- package/{shared → server}/actions/registry.js +3 -22
- package/{shared → server}/actions/registry.test.js +70 -32
- package/server/actions/requestMeta.js +17 -0
- package/server/http/capabilityHttpRuntime.js +1 -1
- package/server/http/capabilityHttpRuntime.test.js +56 -0
- package/server/http/errorHandling.js +291 -0
- package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
- package/server/http/lib/routeRegistration.js +36 -150
- package/server/http/lib/routeTransport.js +2 -0
- package/server/http/lib/routeValidator.js +5 -47
- package/server/http/lib/routeValidator.test.js +15 -17
- package/server/http/lib/router.js +10 -94
- package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
- package/server/platform/providerRuntime/helpers.js +1 -34
- package/server/runtime/integers.js +1 -5
- package/server/runtime/serviceAuthorization.js +4 -9
- package/server/support/appConfig.js +2 -1
- package/server/support/appConfigFiles.js +1 -38
- package/server/support/appConfigFiles.test.js +3 -39
- package/server/support/index.js +1 -20
- package/shared/capabilities/defineProvider.js +1 -1
- package/shared/capabilities/index.js +1 -1
- package/shared/index.js +1 -1
- package/shared/support/index.js +1 -4
- package/shared/support/mobileConfig.js +99 -0
- package/shared/support/mobileConfig.test.js +113 -0
- package/shared/support/normalize.js +0 -108
- package/shared/support/normalize.test.js +0 -134
- package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
- package/shared/support/returnToPath.js +1 -20
- package/shared/support/returnToPath.test.js +1 -36
- package/shared/support/shellLayoutTargets.js +2 -140
- package/shared/support/shellLayoutTargets.test.js +1 -110
- package/shared/support/sorting.js +1 -9
- package/shared/surface/apiPaths.js +1 -82
- package/shared/surface/paths.js +38 -171
- package/shared/surface/registry.js +2 -1
- package/shared/surface/runtime.js +18 -109
- package/shared/surface/runtime.test.js +57 -0
- package/shared/validators/index.js +1 -4
- package/shared/validators/jsonRestSchemaSupport.js +0 -9
- package/shared/validators/recordIdParamsValidator.js +0 -6
- package/shared/validators/schemaDefinitions.js +4 -30
- package/shared/validators/schemaPayloadValidation.js +2 -6
- package/test/barrelExposure.test.js +3 -7
- package/test/exportsContract.test.js +1 -3
- package/client/mobileLaunchRouting.js +0 -231
- package/client/mobileLaunchRouting.test.js +0 -230
- package/server/http/lib/controller.js +0 -183
- package/server/http/lib/controller.test.js +0 -143
- package/server/http/lib/index.js +0 -5
- package/server/runtime/canonicalJson.js +0 -74
- package/server/runtime/entityChangeEvents.js +0 -307
- package/server/runtime/entityChangeEvents.test.js +0 -274
- package/server/runtime/fastifyBootstrap.js +0 -553
- package/server/runtime/moduleConfig.js +0 -223
- package/server/runtime/moduleConfig.test.js +0 -165
- package/server/runtime/pagination.js +0 -13
- package/server/runtime/realtimeNormalization.js +0 -21
- package/server/runtime/routeUtils.js +0 -20
- package/server/runtime/securityAudit.js +0 -269
- package/server/runtime/securityAudit.test.js +0 -41
- package/server/support/pageTargets.js +0 -772
- package/server/support/pageTargets.test.js +0 -659
- package/server/support/shellOutlets.js +0 -576
- package/server/support/shellOutlets.test.js +0 -336
- package/server/surface/index.js +0 -18
- package/shared/actions/audit.js +0 -7
- package/shared/actions/idempotency.js +0 -62
- package/shared/actions/observability.js +0 -10
- package/shared/actions/pipeline.js +0 -309
- package/shared/actions/requestMeta.js +0 -36
- package/shared/actions/textNormalization.js +0 -3
- package/shared/actions/withActionDefaults.js +0 -34
- /package/{shared → server}/actions/executionContext.test.js +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { normalizeMobileCallbackPath, normalizeMobileConfig } from "./mobileConfig.js";
|
|
4
|
+
|
|
5
|
+
test("normalizeMobileCallbackPath normalizes auth callback paths", () => {
|
|
6
|
+
assert.equal(normalizeMobileCallbackPath("auth/login"), "/auth/login");
|
|
7
|
+
assert.equal(normalizeMobileCallbackPath("/auth/login"), "/auth/login");
|
|
8
|
+
assert.equal(
|
|
9
|
+
normalizeMobileCallbackPath("", {
|
|
10
|
+
fallback: "/login/callback"
|
|
11
|
+
}),
|
|
12
|
+
"/login/callback"
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("normalizeMobileConfig normalizes the mobile config contract", () => {
|
|
17
|
+
const mobileConfig = normalizeMobileConfig({
|
|
18
|
+
enabled: true,
|
|
19
|
+
strategy: " Capacitor ",
|
|
20
|
+
appId: "com.example.convict",
|
|
21
|
+
appName: "Convict",
|
|
22
|
+
assetMode: " dev_server ",
|
|
23
|
+
devServerUrl: " http://192.168.1.10:5173 ",
|
|
24
|
+
apiBaseUrl: " https://api.example.com ",
|
|
25
|
+
auth: {
|
|
26
|
+
callbackPath: "auth/login",
|
|
27
|
+
customScheme: " Convict ",
|
|
28
|
+
appLinkDomains: ["App.Example.com", " app.example.com "]
|
|
29
|
+
},
|
|
30
|
+
android: {
|
|
31
|
+
packageName: "com.example.convict",
|
|
32
|
+
minSdk: "28",
|
|
33
|
+
targetSdk: "35",
|
|
34
|
+
versionCode: "4",
|
|
35
|
+
versionName: "2.0.0"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
assert.deepEqual(mobileConfig, {
|
|
40
|
+
enabled: true,
|
|
41
|
+
strategy: "capacitor",
|
|
42
|
+
appId: "com.example.convict",
|
|
43
|
+
appName: "Convict",
|
|
44
|
+
assetMode: "dev_server",
|
|
45
|
+
devServerUrl: "http://192.168.1.10:5173",
|
|
46
|
+
apiBaseUrl: "https://api.example.com",
|
|
47
|
+
auth: {
|
|
48
|
+
callbackPath: "/auth/login",
|
|
49
|
+
customScheme: "convict",
|
|
50
|
+
appLinkDomains: ["app.example.com"]
|
|
51
|
+
},
|
|
52
|
+
android: {
|
|
53
|
+
packageName: "com.example.convict",
|
|
54
|
+
minSdk: 28,
|
|
55
|
+
targetSdk: 35,
|
|
56
|
+
versionCode: 4,
|
|
57
|
+
versionName: "2.0.0"
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("normalizeMobileConfig applies defaults for missing fields", () => {
|
|
63
|
+
const mobileConfig = normalizeMobileConfig({
|
|
64
|
+
auth: {
|
|
65
|
+
appLinkDomains: "app.example.com"
|
|
66
|
+
},
|
|
67
|
+
android: {
|
|
68
|
+
minSdk: "bad",
|
|
69
|
+
targetSdk: 0,
|
|
70
|
+
versionCode: "",
|
|
71
|
+
versionName: ""
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(mobileConfig, {
|
|
76
|
+
enabled: false,
|
|
77
|
+
strategy: "",
|
|
78
|
+
appId: "",
|
|
79
|
+
appName: "",
|
|
80
|
+
assetMode: "bundled",
|
|
81
|
+
devServerUrl: "",
|
|
82
|
+
apiBaseUrl: "",
|
|
83
|
+
auth: {
|
|
84
|
+
callbackPath: "/auth/login",
|
|
85
|
+
customScheme: "",
|
|
86
|
+
appLinkDomains: ["app.example.com"]
|
|
87
|
+
},
|
|
88
|
+
android: {
|
|
89
|
+
packageName: "",
|
|
90
|
+
minSdk: 26,
|
|
91
|
+
targetSdk: 35,
|
|
92
|
+
versionCode: 1,
|
|
93
|
+
versionName: "1.0.0"
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("normalizeMobileConfig preserves explicit false values from string input", () => {
|
|
99
|
+
const mobileConfig = normalizeMobileConfig({
|
|
100
|
+
enabled: "false"
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.equal(mobileConfig.enabled, false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("normalizeMobileConfig rejects invalid assetMode values", () => {
|
|
107
|
+
assert.throws(
|
|
108
|
+
() => normalizeMobileConfig({
|
|
109
|
+
assetMode: "weird"
|
|
110
|
+
}),
|
|
111
|
+
/config\.mobile\.assetMode must be "bundled" or "dev_server"\./u
|
|
112
|
+
);
|
|
113
|
+
});
|
|
@@ -134,20 +134,6 @@ function normalizeIfInSource(source, normalized, fieldName, normalizer = (value)
|
|
|
134
134
|
return normalized;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
function normalizeIfPresent(value, normalizer = (entry) => entry) {
|
|
138
|
-
if (typeof normalizer !== "function") {
|
|
139
|
-
throw new TypeError("normalizeIfPresent requires normalizer function.");
|
|
140
|
-
}
|
|
141
|
-
return value == null ? value : normalizer(value);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function normalizeOrNull(value, normalizer = (entry) => entry) {
|
|
145
|
-
if (typeof normalizer !== "function") {
|
|
146
|
-
throw new TypeError("normalizeOrNull requires normalizer function.");
|
|
147
|
-
}
|
|
148
|
-
return value == null ? null : normalizer(value);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
137
|
function isRecord(value) {
|
|
152
138
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
153
139
|
}
|
|
@@ -260,94 +246,6 @@ function normalizeOneOf(value, allowedValues = [], fallback = "") {
|
|
|
260
246
|
return supported[0] || "";
|
|
261
247
|
}
|
|
262
248
|
|
|
263
|
-
const MOBILE_ASSET_MODES = Object.freeze(["bundled", "dev_server"]);
|
|
264
|
-
const MOBILE_STRATEGIES = Object.freeze(["capacitor"]);
|
|
265
|
-
const DEFAULT_MOBILE_AUTH_CONFIG = Object.freeze({
|
|
266
|
-
callbackPath: "/auth/login",
|
|
267
|
-
customScheme: "",
|
|
268
|
-
appLinkDomains: Object.freeze([])
|
|
269
|
-
});
|
|
270
|
-
const DEFAULT_MOBILE_ANDROID_CONFIG = Object.freeze({
|
|
271
|
-
packageName: "",
|
|
272
|
-
minSdk: 26,
|
|
273
|
-
targetSdk: 35,
|
|
274
|
-
versionCode: 1,
|
|
275
|
-
versionName: "1.0.0"
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
function normalizeMobileStrategy(value = "", { fallback = "" } = {}) {
|
|
279
|
-
const normalized = normalizeLowerText(value);
|
|
280
|
-
return MOBILE_STRATEGIES.includes(normalized) ? normalized : normalizeLowerText(fallback);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function normalizeMobileAssetMode(value = "", { fallback = "bundled" } = {}) {
|
|
284
|
-
const normalized = normalizeLowerText(value);
|
|
285
|
-
if (!normalized) {
|
|
286
|
-
return normalizeLowerText(fallback, {
|
|
287
|
-
fallback: "bundled"
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (!MOBILE_ASSET_MODES.includes(normalized)) {
|
|
291
|
-
throw new TypeError('config.mobile.assetMode must be "bundled" or "dev_server".');
|
|
292
|
-
}
|
|
293
|
-
return normalized;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function normalizeMobileCallbackPath(value = "", { fallback = "/auth/login" } = {}) {
|
|
297
|
-
const normalized = normalizeText(value, {
|
|
298
|
-
fallback
|
|
299
|
-
});
|
|
300
|
-
if (!normalized) {
|
|
301
|
-
return "";
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function normalizeMobileConfig(source = {}) {
|
|
308
|
-
const mobile = normalizeObject(source);
|
|
309
|
-
const auth = normalizeObject(mobile.auth);
|
|
310
|
-
const android = normalizeObject(mobile.android);
|
|
311
|
-
|
|
312
|
-
return Object.freeze({
|
|
313
|
-
enabled: hasValue(mobile.enabled) ? normalizeBoolean(mobile.enabled) : false,
|
|
314
|
-
strategy: normalizeMobileStrategy(mobile.strategy),
|
|
315
|
-
appId: normalizeText(mobile.appId),
|
|
316
|
-
appName: normalizeText(mobile.appName),
|
|
317
|
-
assetMode: normalizeMobileAssetMode(mobile.assetMode),
|
|
318
|
-
devServerUrl: normalizeText(mobile.devServerUrl),
|
|
319
|
-
apiBaseUrl: normalizeText(mobile.apiBaseUrl),
|
|
320
|
-
auth: Object.freeze({
|
|
321
|
-
callbackPath: normalizeMobileCallbackPath(auth.callbackPath, {
|
|
322
|
-
fallback: DEFAULT_MOBILE_AUTH_CONFIG.callbackPath
|
|
323
|
-
}),
|
|
324
|
-
customScheme: normalizeLowerText(auth.customScheme),
|
|
325
|
-
appLinkDomains: Object.freeze([
|
|
326
|
-
...new Set(
|
|
327
|
-
normalizeUniqueTextList(auth.appLinkDomains, {
|
|
328
|
-
acceptSingle: true
|
|
329
|
-
}).map((entry) => normalizeLowerText(entry))
|
|
330
|
-
)
|
|
331
|
-
])
|
|
332
|
-
}),
|
|
333
|
-
android: Object.freeze({
|
|
334
|
-
packageName: normalizeText(android.packageName),
|
|
335
|
-
minSdk: normalizePositiveInteger(android.minSdk, {
|
|
336
|
-
fallback: DEFAULT_MOBILE_ANDROID_CONFIG.minSdk
|
|
337
|
-
}),
|
|
338
|
-
targetSdk: normalizePositiveInteger(android.targetSdk, {
|
|
339
|
-
fallback: DEFAULT_MOBILE_ANDROID_CONFIG.targetSdk
|
|
340
|
-
}),
|
|
341
|
-
versionCode: normalizePositiveInteger(android.versionCode, {
|
|
342
|
-
fallback: DEFAULT_MOBILE_ANDROID_CONFIG.versionCode
|
|
343
|
-
}),
|
|
344
|
-
versionName: normalizeText(android.versionName, {
|
|
345
|
-
fallback: DEFAULT_MOBILE_ANDROID_CONFIG.versionName
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
249
|
function ensureNonEmptyText(value, label = "value") {
|
|
352
250
|
const normalized = normalizeText(value);
|
|
353
251
|
if (!normalized) {
|
|
@@ -368,8 +266,6 @@ export {
|
|
|
368
266
|
normalizeQueryToken,
|
|
369
267
|
normalizeObject,
|
|
370
268
|
normalizeIfInSource,
|
|
371
|
-
normalizeIfPresent,
|
|
372
|
-
normalizeOrNull,
|
|
373
269
|
isRecord,
|
|
374
270
|
normalizeArray,
|
|
375
271
|
normalizeUniqueTextList,
|
|
@@ -379,9 +275,5 @@ export {
|
|
|
379
275
|
normalizeRecordId,
|
|
380
276
|
normalizeOpaqueId,
|
|
381
277
|
normalizeOneOf,
|
|
382
|
-
normalizeMobileAssetMode,
|
|
383
|
-
normalizeMobileCallbackPath,
|
|
384
|
-
normalizeMobileConfig,
|
|
385
|
-
normalizeMobileStrategy,
|
|
386
278
|
ensureNonEmptyText
|
|
387
279
|
};
|
|
@@ -7,10 +7,6 @@ import {
|
|
|
7
7
|
normalizeFiniteInteger,
|
|
8
8
|
normalizeFiniteNumber,
|
|
9
9
|
normalizeIfInSource,
|
|
10
|
-
normalizeIfPresent,
|
|
11
|
-
normalizeMobileCallbackPath,
|
|
12
|
-
normalizeMobileConfig,
|
|
13
|
-
normalizeOrNull,
|
|
14
10
|
normalizeRecordId,
|
|
15
11
|
normalizeOpaqueId,
|
|
16
12
|
normalizePositiveInteger,
|
|
@@ -166,26 +162,6 @@ test("normalizeIfInSource validates target and function arguments", () => {
|
|
|
166
162
|
);
|
|
167
163
|
});
|
|
168
164
|
|
|
169
|
-
test("normalizeIfPresent normalizes non-nullish values and preserves nullish values", () => {
|
|
170
|
-
assert.equal(normalizeIfPresent(" Ada ", normalizeText), "Ada");
|
|
171
|
-
assert.equal(normalizeIfPresent(null, normalizeText), null);
|
|
172
|
-
assert.equal(normalizeIfPresent(undefined, normalizeText), undefined);
|
|
173
|
-
assert.throws(
|
|
174
|
-
() => normalizeIfPresent("Ada", null),
|
|
175
|
-
/requires normalizer function/
|
|
176
|
-
);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("normalizeOrNull normalizes non-nullish values and coerces nullish to null", () => {
|
|
180
|
-
assert.equal(normalizeOrNull(" Ada ", normalizeText), "Ada");
|
|
181
|
-
assert.equal(normalizeOrNull(null, normalizeText), null);
|
|
182
|
-
assert.equal(normalizeOrNull(undefined, normalizeText), null);
|
|
183
|
-
assert.throws(
|
|
184
|
-
() => normalizeOrNull("Ada", null),
|
|
185
|
-
/requires normalizer function/
|
|
186
|
-
);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
165
|
test("normalizeRecordId accepts canonical string, safe integer, and bigint identifiers", () => {
|
|
190
166
|
const unsafeNumericId = Number(9007199254740993n);
|
|
191
167
|
assert.equal(normalizeRecordId(" 7 "), "7");
|
|
@@ -224,113 +200,3 @@ test("normalizeUniqueTextList trims, dedupes, and supports optional single value
|
|
|
224
200
|
["one"]
|
|
225
201
|
);
|
|
226
202
|
});
|
|
227
|
-
|
|
228
|
-
test("normalizeMobileCallbackPath normalizes auth callback paths", () => {
|
|
229
|
-
assert.equal(normalizeMobileCallbackPath("auth/login"), "/auth/login");
|
|
230
|
-
assert.equal(normalizeMobileCallbackPath("/auth/login"), "/auth/login");
|
|
231
|
-
assert.equal(
|
|
232
|
-
normalizeMobileCallbackPath("", {
|
|
233
|
-
fallback: "/login/callback"
|
|
234
|
-
}),
|
|
235
|
-
"/login/callback"
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
test("normalizeMobileConfig normalizes the mobile config contract", () => {
|
|
240
|
-
const mobileConfig = normalizeMobileConfig({
|
|
241
|
-
enabled: true,
|
|
242
|
-
strategy: " Capacitor ",
|
|
243
|
-
appId: "com.example.convict",
|
|
244
|
-
appName: "Convict",
|
|
245
|
-
assetMode: " dev_server ",
|
|
246
|
-
devServerUrl: " http://192.168.1.10:5173 ",
|
|
247
|
-
apiBaseUrl: " https://api.example.com ",
|
|
248
|
-
auth: {
|
|
249
|
-
callbackPath: "auth/login",
|
|
250
|
-
customScheme: " Convict ",
|
|
251
|
-
appLinkDomains: ["App.Example.com", " app.example.com "]
|
|
252
|
-
},
|
|
253
|
-
android: {
|
|
254
|
-
packageName: "com.example.convict",
|
|
255
|
-
minSdk: "28",
|
|
256
|
-
targetSdk: "35",
|
|
257
|
-
versionCode: "4",
|
|
258
|
-
versionName: "2.0.0"
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
assert.deepEqual(mobileConfig, {
|
|
263
|
-
enabled: true,
|
|
264
|
-
strategy: "capacitor",
|
|
265
|
-
appId: "com.example.convict",
|
|
266
|
-
appName: "Convict",
|
|
267
|
-
assetMode: "dev_server",
|
|
268
|
-
devServerUrl: "http://192.168.1.10:5173",
|
|
269
|
-
apiBaseUrl: "https://api.example.com",
|
|
270
|
-
auth: {
|
|
271
|
-
callbackPath: "/auth/login",
|
|
272
|
-
customScheme: "convict",
|
|
273
|
-
appLinkDomains: ["app.example.com"]
|
|
274
|
-
},
|
|
275
|
-
android: {
|
|
276
|
-
packageName: "com.example.convict",
|
|
277
|
-
minSdk: 28,
|
|
278
|
-
targetSdk: 35,
|
|
279
|
-
versionCode: 4,
|
|
280
|
-
versionName: "2.0.0"
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test("normalizeMobileConfig applies defaults for missing fields", () => {
|
|
286
|
-
const mobileConfig = normalizeMobileConfig({
|
|
287
|
-
auth: {
|
|
288
|
-
appLinkDomains: "app.example.com"
|
|
289
|
-
},
|
|
290
|
-
android: {
|
|
291
|
-
minSdk: "bad",
|
|
292
|
-
targetSdk: 0,
|
|
293
|
-
versionCode: "",
|
|
294
|
-
versionName: ""
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
assert.deepEqual(mobileConfig, {
|
|
299
|
-
enabled: false,
|
|
300
|
-
strategy: "",
|
|
301
|
-
appId: "",
|
|
302
|
-
appName: "",
|
|
303
|
-
assetMode: "bundled",
|
|
304
|
-
devServerUrl: "",
|
|
305
|
-
apiBaseUrl: "",
|
|
306
|
-
auth: {
|
|
307
|
-
callbackPath: "/auth/login",
|
|
308
|
-
customScheme: "",
|
|
309
|
-
appLinkDomains: ["app.example.com"]
|
|
310
|
-
},
|
|
311
|
-
android: {
|
|
312
|
-
packageName: "",
|
|
313
|
-
minSdk: 26,
|
|
314
|
-
targetSdk: 35,
|
|
315
|
-
versionCode: 1,
|
|
316
|
-
versionName: "1.0.0"
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
test("normalizeMobileConfig preserves explicit false values from string input", () => {
|
|
322
|
-
const mobileConfig = normalizeMobileConfig({
|
|
323
|
-
enabled: "false"
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
assert.equal(mobileConfig.enabled, false);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test("normalizeMobileConfig rejects invalid assetMode values", () => {
|
|
330
|
-
assert.throws(
|
|
331
|
-
() => normalizeMobileConfig({
|
|
332
|
-
assetMode: "weird"
|
|
333
|
-
}),
|
|
334
|
-
/config\.mobile\.assetMode must be "bundled" or "dev_server"\./u
|
|
335
|
-
);
|
|
336
|
-
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
|
|
4
|
-
import { hasPermission } from "./
|
|
4
|
+
import { hasPermission } from "./permissions.js";
|
|
5
5
|
|
|
6
6
|
test("hasPermission allows wildcard and direct matches", () => {
|
|
7
7
|
assert.equal(hasPermission(["*"], "workspace.billing.manage"), true);
|
|
@@ -89,24 +89,6 @@ function normalizeBlockedPathnames(blockedPathnames = []) {
|
|
|
89
89
|
return normalizedPathnames;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function resolveAllowedOriginsFromPlacementContext(contextValue = null, { includeWindowOrigin = true } = {}) {
|
|
93
|
-
const seedOrigins = [];
|
|
94
|
-
|
|
95
|
-
if (includeWindowOrigin && typeof window === "object" && window?.location?.origin) {
|
|
96
|
-
appendNormalizedOrigin(seedOrigins, window.location.origin);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const surfaceConfig =
|
|
100
|
-
contextValue && typeof contextValue === "object" && contextValue.surfaceConfig && typeof contextValue.surfaceConfig === "object"
|
|
101
|
-
? contextValue.surfaceConfig
|
|
102
|
-
: {};
|
|
103
|
-
const surfacesById =
|
|
104
|
-
surfaceConfig.surfacesById && typeof surfaceConfig.surfacesById === "object" ? surfaceConfig.surfacesById : {};
|
|
105
|
-
return resolveAllowedOriginsFromSurfaceDefinitions(surfacesById, {
|
|
106
|
-
seedOrigins
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
92
|
function normalizeReturnToPath(
|
|
111
93
|
value,
|
|
112
94
|
{
|
|
@@ -163,6 +145,5 @@ export {
|
|
|
163
145
|
normalizeAllowedOrigins,
|
|
164
146
|
normalizeHttpOrigin,
|
|
165
147
|
normalizeReturnToPath,
|
|
166
|
-
resolveAllowedOriginsFromSurfaceDefinitions
|
|
167
|
-
resolveAllowedOriginsFromPlacementContext
|
|
148
|
+
resolveAllowedOriginsFromSurfaceDefinitions
|
|
168
149
|
};
|
|
@@ -5,8 +5,7 @@ import {
|
|
|
5
5
|
normalizeAllowedOrigins,
|
|
6
6
|
normalizeHttpOrigin,
|
|
7
7
|
normalizeReturnToPath,
|
|
8
|
-
resolveAllowedOriginsFromSurfaceDefinitions
|
|
9
|
-
resolveAllowedOriginsFromPlacementContext
|
|
8
|
+
resolveAllowedOriginsFromSurfaceDefinitions
|
|
10
9
|
} from "./returnToPath.js";
|
|
11
10
|
|
|
12
11
|
test("normalizeHttpOrigin keeps valid http origins only", () => {
|
|
@@ -88,40 +87,6 @@ test("resolveAllowedOriginsFromSurfaceDefinitions includes seed origins and surf
|
|
|
88
87
|
]);
|
|
89
88
|
});
|
|
90
89
|
|
|
91
|
-
test("resolveAllowedOriginsFromPlacementContext collects current and surface origins", () => {
|
|
92
|
-
const originalWindow = globalThis.window;
|
|
93
|
-
globalThis.window = {
|
|
94
|
-
location: {
|
|
95
|
-
origin: "https://current.example.com"
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const origins = resolveAllowedOriginsFromPlacementContext({
|
|
101
|
-
surfaceConfig: {
|
|
102
|
-
surfacesById: {
|
|
103
|
-
home: { origin: "https://home.example.com" },
|
|
104
|
-
app: { origin: "https://app.example.com" },
|
|
105
|
-
duplicate: { origin: "https://home.example.com" },
|
|
106
|
-
invalid: { origin: "mailto:hello@example.com" }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
assert.deepEqual(origins, [
|
|
112
|
-
"https://current.example.com",
|
|
113
|
-
"https://home.example.com",
|
|
114
|
-
"https://app.example.com"
|
|
115
|
-
]);
|
|
116
|
-
} finally {
|
|
117
|
-
if (typeof originalWindow === "undefined") {
|
|
118
|
-
delete globalThis.window;
|
|
119
|
-
} else {
|
|
120
|
-
globalThis.window = originalWindow;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
90
|
test("normalizeReturnToPath refuses paths a browser reads as another site", () => {
|
|
126
91
|
for (const value of ["/\\evil.example", "/\\/evil.example/x", "/\t/evil.example", "/\n/evil.example", "\\\\evil.example"]) {
|
|
127
92
|
assert.equal(normalizeReturnToPath(value, { fallback: "/home" }), "/home", JSON.stringify(value));
|
|
@@ -3,28 +3,9 @@ import {
|
|
|
3
3
|
normalizeText
|
|
4
4
|
} from "./normalize.js";
|
|
5
5
|
|
|
6
|
-
const SHELL_OUTLET_TAG_PATTERN = /<ShellOutlet\b([^>]*)\/?>/g;
|
|
7
|
-
const ATTRIBUTE_PATTERN = /([:@]?[A-Za-z_][A-Za-z0-9_-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'))?/g;
|
|
8
6
|
const PLACEMENT_LAYOUT_CLASSES = Object.freeze(["compact", "medium", "expanded"]);
|
|
9
7
|
const WEB_PLACEMENT_SURFACE_ANY = "*";
|
|
10
8
|
|
|
11
|
-
function parseTagAttributes(attributesSource = "") {
|
|
12
|
-
const attributes = {};
|
|
13
|
-
const source = String(attributesSource || "");
|
|
14
|
-
for (const match of source.matchAll(ATTRIBUTE_PATTERN)) {
|
|
15
|
-
const attributeName = normalizeText(match[1]);
|
|
16
|
-
if (!attributeName) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const hasValue = match[2] != null || match[3] != null;
|
|
21
|
-
const attributeValue = hasValue ? String(match[2] ?? match[3] ?? "") : true;
|
|
22
|
-
attributes[attributeName] = attributeValue;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return attributes;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
9
|
function normalizeShellOutletTargetId(value = "") {
|
|
29
10
|
const normalizedValue = normalizeText(value);
|
|
30
11
|
if (!normalizedValue) {
|
|
@@ -250,126 +231,9 @@ function normalizePlacementTopologyDefinition(
|
|
|
250
231
|
});
|
|
251
232
|
}
|
|
252
233
|
|
|
253
|
-
function resolveShellOutletTargetParts(
|
|
254
|
-
{
|
|
255
|
-
target = ""
|
|
256
|
-
} = {}
|
|
257
|
-
) {
|
|
258
|
-
const normalizedTargetId = normalizeShellOutletTargetId(target);
|
|
259
|
-
if (!normalizedTargetId) {
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return Object.freeze({
|
|
264
|
-
id: normalizedTargetId
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function findShellOutletTargetById(targets = [], targetId = "") {
|
|
269
|
-
const entries = Array.isArray(targets) ? targets : [];
|
|
270
|
-
const normalizedTargetId = normalizeShellOutletTargetId(targetId);
|
|
271
|
-
if (!normalizedTargetId) {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return entries.find((entry) => normalizeShellOutletTargetId(entry?.id) === normalizedTargetId) || null;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function describeShellOutletTargets(targets = []) {
|
|
279
|
-
return (Array.isArray(targets) ? targets : [])
|
|
280
|
-
.map((entry) => normalizeShellOutletTargetId(entry?.id))
|
|
281
|
-
.filter(Boolean)
|
|
282
|
-
.join(", ");
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function isDefaultAttributeEnabled(value) {
|
|
286
|
-
if (value === true) {
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
291
|
-
if (!normalized) {
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return normalized !== "false" && normalized !== "0" && normalized !== "no" && normalized !== "off";
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function normalizeShellOutletTargetRecord(
|
|
299
|
-
value = {},
|
|
300
|
-
{
|
|
301
|
-
context = "shell layout"
|
|
302
|
-
} = {}
|
|
303
|
-
) {
|
|
304
|
-
const record = normalizeObject(value);
|
|
305
|
-
const resolvedContext = normalizeText(context) || "shell layout";
|
|
306
|
-
const targetParts = resolveShellOutletTargetParts(
|
|
307
|
-
{
|
|
308
|
-
target: record.target
|
|
309
|
-
},
|
|
310
|
-
{ context: resolvedContext }
|
|
311
|
-
);
|
|
312
|
-
if (!targetParts) {
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return Object.freeze({
|
|
317
|
-
...targetParts,
|
|
318
|
-
default:
|
|
319
|
-
Object.hasOwn(record, "default") &&
|
|
320
|
-
isDefaultAttributeEnabled(record.default)
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function discoverShellOutletTargetsFromVueSource(
|
|
325
|
-
source = "",
|
|
326
|
-
{
|
|
327
|
-
context = "shell layout",
|
|
328
|
-
enforceSingleDefault = true
|
|
329
|
-
} = {}
|
|
330
|
-
) {
|
|
331
|
-
const sourceText = String(source || "");
|
|
332
|
-
const resolvedContext = normalizeText(context) || "shell layout";
|
|
333
|
-
const targetById = new Map();
|
|
334
|
-
let defaultTargetId = "";
|
|
335
|
-
|
|
336
|
-
for (const tagMatch of sourceText.matchAll(SHELL_OUTLET_TAG_PATTERN)) {
|
|
337
|
-
const attributes = parseTagAttributes(tagMatch[1]);
|
|
338
|
-
const normalizedTarget = normalizeShellOutletTargetRecord(attributes, {
|
|
339
|
-
context: resolvedContext
|
|
340
|
-
});
|
|
341
|
-
if (!normalizedTarget) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
if (targetById.has(normalizedTarget.id)) {
|
|
345
|
-
throw new Error(`${resolvedContext} contains duplicate ShellOutlet target "${normalizedTarget.id}".`);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (normalizedTarget.default) {
|
|
349
|
-
if (enforceSingleDefault === true && defaultTargetId) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
`${resolvedContext} defines multiple default ShellOutlet targets: "${defaultTargetId}" and "${normalizedTarget.id}".`
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
if (!defaultTargetId) {
|
|
355
|
-
defaultTargetId = normalizedTarget.id;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
targetById.set(normalizedTarget.id, normalizedTarget);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return Object.freeze({
|
|
363
|
-
targets: Object.freeze([...targetById.values()]),
|
|
364
|
-
defaultTargetId
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
234
|
export {
|
|
369
235
|
PLACEMENT_LAYOUT_CLASSES,
|
|
370
|
-
|
|
371
|
-
discoverShellOutletTargetsFromVueSource,
|
|
372
|
-
findShellOutletTargetById,
|
|
236
|
+
WEB_PLACEMENT_SURFACE_ANY,
|
|
373
237
|
normalizePlacementKind,
|
|
374
238
|
normalizePlacementLayoutClass,
|
|
375
239
|
normalizePlacementOwnerId,
|
|
@@ -380,7 +244,5 @@ export {
|
|
|
380
244
|
normalizePlacementTopologyVariant,
|
|
381
245
|
normalizeSemanticPlacementId,
|
|
382
246
|
normalizeShellOutletTargetId,
|
|
383
|
-
|
|
384
|
-
resolvePlacementTargetReference,
|
|
385
|
-
resolveShellOutletTargetParts
|
|
247
|
+
resolvePlacementTargetReference
|
|
386
248
|
};
|