@farm.js/scripts 0.1.0-beta.0 → 0.1.0-beta.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/dist/client.d.ts.map +1 -1
- package/dist/client.js +40 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/shared.js +23 -0
- package/package.json +2 -2
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EAId,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,oBAAoB,GACrB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EAId,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAgCpB,qBAAa,sBAAuB,SAAQ,KAAK;IACtC,IAAI,SAA4B;CAC1C;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IACxC,IAAI,SAA8B;CAC5C;AAED,qBAAa,0BAA2B,SAAQ,KAAK;IAGjD,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM;IAHlB,IAAI,SAAgC;gBAElC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM;CAM5B;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAGtC,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM;IAHb,IAAI,SAAqB;gBAEvB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM;CAIvB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM;IAHnB,IAAI,SAAwB;gBAE1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM;CAI7B;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAG/C,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM;IAHpB,IAAI,SAA8B;gBAEhC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM;CAM9B;AAED,+FAA+F;AAC/F,wBAAgB,YAAY,CAAC,CAAC,GAAG,IAAI,EAAE,UAAU,EAAE,gBAAgB,GAAG,YAAY,CAAC,CAAC,CAAC,CA0CpF;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CA6BlF;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAKrE;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,SAAS,wBAAwB,EAAE,EAChD,aAAa,GAAE,MAAM,GAAG,OAAO,UAAmB,EAClD,QAAQ,SAAM,GACb,aAAa,CAkMf"}
|
package/dist/client.js
CHANGED
|
@@ -80,7 +80,7 @@ export function defineScript(definition) {
|
|
|
80
80
|
const store = currentStore();
|
|
81
81
|
if (!store)
|
|
82
82
|
return () => { };
|
|
83
|
-
const entry = ensureEntry(store, resolved,
|
|
83
|
+
const entry = ensureEntry(store, resolved, null);
|
|
84
84
|
entry.listeners.add(listener);
|
|
85
85
|
callListener(store, listener, snapshot(entry));
|
|
86
86
|
return () => entry.listeners.delete(listener);
|
|
@@ -141,7 +141,10 @@ export function getScriptConsent(category) {
|
|
|
141
141
|
/** @internal Start the browser lifecycle for definitions serialized by the Farm plugin. */
|
|
142
142
|
export function startScriptRuntime(definitions, runtimeWindow = window, basePath = "/") {
|
|
143
143
|
const store = getStore(runtimeWindow, basePath);
|
|
144
|
-
|
|
144
|
+
// This runtime's claim on its configured scripts. Released on close so a
|
|
145
|
+
// successor (an HMR re-evaluation) can re-register changed definitions.
|
|
146
|
+
const owner = Symbol("farm-scripts-runtime");
|
|
147
|
+
const entries = definitions.map((definition) => ensureEntry(store, cloneScriptDefinition(definition), owner));
|
|
145
148
|
let closed = false;
|
|
146
149
|
let idleHandle;
|
|
147
150
|
let interactionHandler;
|
|
@@ -303,6 +306,8 @@ export function startScriptRuntime(definitions, runtimeWindow = window, basePath
|
|
|
303
306
|
if (closed)
|
|
304
307
|
return;
|
|
305
308
|
closed = true;
|
|
309
|
+
for (const entry of entries)
|
|
310
|
+
entry.owners.delete(owner);
|
|
306
311
|
if (idleHandle !== undefined) {
|
|
307
312
|
if (typeof runtimeWindow.cancelIdleCallback === "function") {
|
|
308
313
|
runtimeWindow.cancelIdleCallback(idleHandle);
|
|
@@ -361,21 +366,35 @@ function getStore(runtimeWindow, basePath) {
|
|
|
361
366
|
function registerLocalDefinition(definition) {
|
|
362
367
|
const store = currentStore();
|
|
363
368
|
if (store)
|
|
364
|
-
ensureEntry(store, definition,
|
|
369
|
+
ensureEntry(store, definition, null);
|
|
365
370
|
}
|
|
366
|
-
function ensureEntry(store, definition,
|
|
371
|
+
function ensureEntry(store, definition, owner) {
|
|
367
372
|
const existing = store.entries.get(definition.name);
|
|
368
373
|
if (existing) {
|
|
369
374
|
if (!sameScriptDefinition(existing.definition, definition)) {
|
|
370
|
-
|
|
375
|
+
if (existing.owners.size > 0) {
|
|
376
|
+
// Two live registrations disagreeing about the same script is a real
|
|
377
|
+
// application error and keeps failing loudly.
|
|
378
|
+
throw new TypeError(`Script ${JSON.stringify(definition.name)} was defined with conflicting options`);
|
|
379
|
+
}
|
|
380
|
+
// No live runtime owns the old definition — its runtime has closed, as
|
|
381
|
+
// happens on every HMR configuration change. Adopt the new definition
|
|
382
|
+
// and reset load state so it loads fresh; a previously injected element
|
|
383
|
+
// cannot be un-executed, but the replacement no longer throws. Status
|
|
384
|
+
// subscribers survive the swap and are told about the reset.
|
|
385
|
+
existing.definition = definition;
|
|
386
|
+
existing.triggered = false;
|
|
387
|
+
existing.attempt = 0;
|
|
388
|
+
existing.promise = undefined;
|
|
389
|
+
setEntryState(store, existing, "idle");
|
|
371
390
|
}
|
|
372
|
-
if (
|
|
373
|
-
existing.
|
|
391
|
+
if (owner)
|
|
392
|
+
existing.owners.add(owner);
|
|
374
393
|
return existing;
|
|
375
394
|
}
|
|
376
395
|
const entry = {
|
|
377
396
|
definition,
|
|
378
|
-
|
|
397
|
+
owners: new Set(owner ? [owner] : []),
|
|
379
398
|
status: "idle",
|
|
380
399
|
attempt: 0,
|
|
381
400
|
triggered: false,
|
|
@@ -387,7 +406,7 @@ function ensureEntry(store, definition, configured) {
|
|
|
387
406
|
}
|
|
388
407
|
async function loadRegisteredScript(store, name, stack = []) {
|
|
389
408
|
const entry = store.entries.get(name);
|
|
390
|
-
if (!entry
|
|
409
|
+
if (!entry || entry.owners.size === 0) {
|
|
391
410
|
throw new ScriptNotRegisteredError(`Script ${JSON.stringify(name)} is not registered. Pass its defineScript() handle to scripts({ scripts: [...] })`);
|
|
392
411
|
}
|
|
393
412
|
entry.triggered = true;
|
|
@@ -403,10 +422,7 @@ async function loadRegisteredScript(store, name, stack = []) {
|
|
|
403
422
|
}
|
|
404
423
|
if (entry.promise)
|
|
405
424
|
return entry.promise;
|
|
406
|
-
|
|
407
|
-
setEntryState(store, entry, "blocked");
|
|
408
|
-
throw new ScriptConsentRequiredError(name, entry.definition.consent);
|
|
409
|
-
}
|
|
425
|
+
assertScriptConsent(store, entry);
|
|
410
426
|
if (entry.definition.preconnect)
|
|
411
427
|
addPreconnect(store, entry.definition);
|
|
412
428
|
if (stack.includes(name)) {
|
|
@@ -414,6 +430,7 @@ async function loadRegisteredScript(store, name, stack = []) {
|
|
|
414
430
|
}
|
|
415
431
|
const promise = (async () => {
|
|
416
432
|
for (const dependency of entry.definition.dependsOn) {
|
|
433
|
+
assertScriptConsent(store, entry);
|
|
417
434
|
try {
|
|
418
435
|
await loadRegisteredScript(store, dependency, [...stack, name]);
|
|
419
436
|
}
|
|
@@ -431,6 +448,9 @@ async function loadRegisteredScript(store, name, stack = []) {
|
|
|
431
448
|
for (let attempt = 1; attempt <= maximumAttempts; attempt += 1) {
|
|
432
449
|
entry.attempt = attempt;
|
|
433
450
|
notify(store, entry);
|
|
451
|
+
// Dependencies, retry delays, and status listeners can change consent.
|
|
452
|
+
// A blocked load must not be treated as a retryable network failure.
|
|
453
|
+
assertScriptConsent(store, entry);
|
|
434
454
|
try {
|
|
435
455
|
const value = await loadElement(store, entry.definition);
|
|
436
456
|
setEntryState(store, entry, "ready");
|
|
@@ -455,6 +475,13 @@ async function loadRegisteredScript(store, name, stack = []) {
|
|
|
455
475
|
entry.promise = undefined;
|
|
456
476
|
}
|
|
457
477
|
}
|
|
478
|
+
function assertScriptConsent(store, entry) {
|
|
479
|
+
const category = entry.definition.consent;
|
|
480
|
+
if (category && getConsent(store, category) !== "granted") {
|
|
481
|
+
setEntryState(store, entry, "blocked");
|
|
482
|
+
throw new ScriptConsentRequiredError(entry.definition.name, category);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
458
485
|
async function loadElement(store, definition) {
|
|
459
486
|
const existingValue = findLoadedValue(store, definition);
|
|
460
487
|
if (existingValue.found)
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import { type FarmPlugin } from "@farm.js/core/plugin";
|
|
|
2
2
|
import type { ResolvedScriptsOptions, ScriptRuntime, ScriptsOptions } from "./types.js";
|
|
3
3
|
export type { AnyScriptHandle, ResolvedScriptDefinition, ResolvedScriptLoadStrategy, ScriptConsentState, ScriptDefinition, ScriptDuration, ScriptHandle, ScriptLoadStrategy, ScriptPlacement, ScriptSnapshot, ScriptStatus, ScriptStatusListener, ScriptType, ScriptsOptions, } from "./types.js";
|
|
4
4
|
/** Manage explicitly registered third-party browser scripts across Farm's client lifecycle. */
|
|
5
|
-
export declare function scripts(options: ScriptsOptions): FarmPlugin<unknown, Record<string, unknown>, ScriptRuntime, ResolvedScriptsOptions>;
|
|
5
|
+
export declare function scripts(options: ScriptsOptions): FarmPlugin<unknown, Record<string, unknown>, ScriptRuntime, ResolvedScriptsOptions, unknown, false, import("@farm.js/core/plugin").PluginRoutes>;
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAExF,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,0BAA0B,EAC1B,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,+FAA+F;AAC/F,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAExF,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,0BAA0B,EAC1B,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,+FAA+F;AAC/F,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,oJA0C9C"}
|
package/dist/shared.js
CHANGED
|
@@ -204,6 +204,10 @@ function normalizeScriptSource(value) {
|
|
|
204
204
|
if (src.includes("\\") || hasControlCharacter) {
|
|
205
205
|
throw new TypeError("script root-relative src cannot contain backslashes or control characters");
|
|
206
206
|
}
|
|
207
|
+
const pathname = src.split(/[?#]/, 1)[0] || "/";
|
|
208
|
+
if (pathname.split("/").some((segment) => !isStableRootRelativeSegment(segment))) {
|
|
209
|
+
throw new TypeError("script root-relative src contains a browser-unstable path segment");
|
|
210
|
+
}
|
|
207
211
|
return src;
|
|
208
212
|
}
|
|
209
213
|
if (!/^https?:\/\//i.test(src)) {
|
|
@@ -224,6 +228,25 @@ function normalizeScriptSource(value) {
|
|
|
224
228
|
}
|
|
225
229
|
return src;
|
|
226
230
|
}
|
|
231
|
+
function isStableRootRelativeSegment(segment) {
|
|
232
|
+
if (!segment)
|
|
233
|
+
return true;
|
|
234
|
+
let decoded = segment;
|
|
235
|
+
try {
|
|
236
|
+
decoded = decodeURIComponent(segment);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// Malformed escapes remain literal in browser pathnames.
|
|
240
|
+
}
|
|
241
|
+
return (decoded !== "." &&
|
|
242
|
+
decoded !== ".." &&
|
|
243
|
+
!decoded.includes("/") &&
|
|
244
|
+
!decoded.includes("\\") &&
|
|
245
|
+
!Array.from(decoded).some((character) => {
|
|
246
|
+
const code = character.charCodeAt(0);
|
|
247
|
+
return code <= 31 || (code >= 127 && code <= 159);
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
227
250
|
function normalizeDataAttributes(value) {
|
|
228
251
|
if (value === undefined)
|
|
229
252
|
return {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/scripts",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
4
|
"description": "Typed, lifecycle-aware third-party browser scripts for Farm.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"consent",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@farm.js/core": "0.1.0
|
|
39
|
+
"@farm.js/core": "0.1.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^20.10.5",
|