@pwa-platform/entry-resilience 0.1.0
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/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/browser/index.d.ts +28 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +39 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/indexeddb.d.ts +12 -0
- package/dist/browser/indexeddb.d.ts.map +1 -0
- package/dist/browser/indexeddb.js +61 -0
- package/dist/browser/indexeddb.js.map +1 -0
- package/dist/browser/probes.d.ts +13 -0
- package/dist/browser/probes.d.ts.map +1 -0
- package/dist/browser/probes.js +36 -0
- package/dist/browser/probes.js.map +1 -0
- package/dist/check.d.ts +21 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +48 -0
- package/dist/check.js.map +1 -0
- package/dist/client/index.d.ts +19 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +49 -0
- package/dist/client/index.js.map +1 -0
- package/dist/decide.d.ts +19 -0
- package/dist/decide.d.ts.map +1 -0
- package/dist/decide.js +38 -0
- package/dist/decide.js.map +1 -0
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +51 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/base64url.d.ts +8 -0
- package/dist/internal/base64url.d.ts.map +1 -0
- package/dist/internal/base64url.js +32 -0
- package/dist/internal/base64url.js.map +1 -0
- package/dist/internal/iso-date.d.ts +8 -0
- package/dist/internal/iso-date.d.ts.map +1 -0
- package/dist/internal/iso-date.js +39 -0
- package/dist/internal/iso-date.js.map +1 -0
- package/dist/internal/origin.d.ts +6 -0
- package/dist/internal/origin.d.ts.map +1 -0
- package/dist/internal/origin.js +19 -0
- package/dist/internal/origin.js.map +1 -0
- package/dist/internal/plain-object.d.ts +8 -0
- package/dist/internal/plain-object.d.ts.map +1 -0
- package/dist/internal/plain-object.js +16 -0
- package/dist/internal/plain-object.js.map +1 -0
- package/dist/internal/theme-key.d.ts +2 -0
- package/dist/internal/theme-key.d.ts.map +1 -0
- package/dist/internal/theme-key.js +13 -0
- package/dist/internal/theme-key.js.map +1 -0
- package/dist/manifest.d.ts +13 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +153 -0
- package/dist/manifest.js.map +1 -0
- package/dist/page/main.d.ts +2 -0
- package/dist/page/main.d.ts.map +1 -0
- package/dist/page/main.js +88 -0
- package/dist/page/main.js.map +1 -0
- package/dist/page/messages.d.ts +24 -0
- package/dist/page/messages.d.ts.map +1 -0
- package/dist/page/messages.js +43 -0
- package/dist/page/messages.js.map +1 -0
- package/dist/page/model.d.ts +16 -0
- package/dist/page/model.d.ts.map +1 -0
- package/dist/page/model.js +51 -0
- package/dist/page/model.js.map +1 -0
- package/dist/page/render.d.ts +24 -0
- package/dist/page/render.d.ts.map +1 -0
- package/dist/page/render.js +61 -0
- package/dist/page/render.js.map +1 -0
- package/dist/resolve.d.ts +31 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +65 -0
- package/dist/resolve.js.map +1 -0
- package/dist/return-path.d.ts +10 -0
- package/dist/return-path.d.ts.map +1 -0
- package/dist/return-path.js +75 -0
- package/dist/return-path.js.map +1 -0
- package/dist/select.d.ts +16 -0
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +17 -0
- package/dist/select.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/update.d.ts +28 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +52 -0
- package/dist/update.js.map +1 -0
- package/dist/vite/default-style.d.ts +2 -0
- package/dist/vite/default-style.d.ts.map +1 -0
- package/dist/vite/default-style.js +109 -0
- package/dist/vite/default-style.js.map +1 -0
- package/dist/vite/entry-page.d.ts +6 -0
- package/dist/vite/entry-page.d.ts.map +1 -0
- package/dist/vite/entry-page.js +22 -0
- package/dist/vite/entry-page.js.map +1 -0
- package/dist/vite/index.d.ts +24 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +190 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/options.d.ts +31 -0
- package/dist/vite/options.d.ts.map +1 -0
- package/dist/vite/options.js +118 -0
- package/dist/vite/options.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type PwaEntryPageLocale = "zh-CN" | "en";
|
|
2
|
+
export type PwaEntryPageMessages = {
|
|
3
|
+
readonly documentTitle: string;
|
|
4
|
+
readonly loading: string;
|
|
5
|
+
readonly empty: string;
|
|
6
|
+
readonly headlineMigrating: string;
|
|
7
|
+
readonly headlineIncident: string;
|
|
8
|
+
readonly headlineUnconfirmedOutage: string;
|
|
9
|
+
/** Must contain the literal sequence `{expiresAt}` exactly once — see src/vite/options.ts's validation. */
|
|
10
|
+
readonly expiry: string;
|
|
11
|
+
/** Must contain the literal sequence `{host}` exactly once — see src/vite/options.ts's validation. */
|
|
12
|
+
readonly go: string;
|
|
13
|
+
};
|
|
14
|
+
/** Every key `PwaEntryPageMessages` has, in the order spec's contract table lists them — the closed set
|
|
15
|
+
* src/vite/options.ts checks a host's `messages` override's keys against. */
|
|
16
|
+
export declare const ENTRY_PAGE_MESSAGE_KEYS: readonly (keyof PwaEntryPageMessages)[];
|
|
17
|
+
/** Built-in copy per locale — spec's contract table, character for character. The `zh-CN` column is exactly the
|
|
18
|
+
* strings the page rendered before this revision (test/page/messages.test.ts pins this against the previous
|
|
19
|
+
* hard-coded literals so it cannot drift). */
|
|
20
|
+
export declare const ENTRY_PAGE_MESSAGES: Readonly<Record<PwaEntryPageLocale, PwaEntryPageMessages>>;
|
|
21
|
+
/** Combines the built-in table for `locale` with a host's (already validated) partial override, one key at a
|
|
22
|
+
* time — an override key always wins, an absent one falls back to the built-in copy. */
|
|
23
|
+
export declare function mergeEntryPageMessages(locale: PwaEntryPageLocale, overrides?: Partial<PwaEntryPageMessages>): PwaEntryPageMessages;
|
|
24
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/page/messages.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,IAAI,CAAC;AAEhD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;IAC3C,2GAA2G;IAC3G,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sGAAsG;IACtG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;8EAC8E;AAC9E,eAAO,MAAM,uBAAuB,EAAE,SAAS,CAAC,MAAM,oBAAoB,CAAC,EASf,CAAC;AAE7D;;+CAE+C;AAC/C,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,oBAAoB,CAAC,CAqB1F,CAAC;AAEF;yFACyF;AACzF,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,kBAAkB,EAC1B,SAAS,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GACxC,oBAAoB,CAEtB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Every key `PwaEntryPageMessages` has, in the order spec's contract table lists them — the closed set
|
|
2
|
+
* src/vite/options.ts checks a host's `messages` override's keys against. */
|
|
3
|
+
export const ENTRY_PAGE_MESSAGE_KEYS = [
|
|
4
|
+
"documentTitle",
|
|
5
|
+
"loading",
|
|
6
|
+
"empty",
|
|
7
|
+
"headlineMigrating",
|
|
8
|
+
"headlineIncident",
|
|
9
|
+
"headlineUnconfirmedOutage",
|
|
10
|
+
"expiry",
|
|
11
|
+
"go",
|
|
12
|
+
];
|
|
13
|
+
/** Built-in copy per locale — spec's contract table, character for character. The `zh-CN` column is exactly the
|
|
14
|
+
* strings the page rendered before this revision (test/page/messages.test.ts pins this against the previous
|
|
15
|
+
* hard-coded literals so it cannot drift). */
|
|
16
|
+
export const ENTRY_PAGE_MESSAGES = {
|
|
17
|
+
"zh-CN": {
|
|
18
|
+
documentTitle: "备用入口",
|
|
19
|
+
loading: "正在检查备用入口…",
|
|
20
|
+
empty: "当前没有可用的备用入口",
|
|
21
|
+
headlineMigrating: "应用正在迁移到新地址",
|
|
22
|
+
headlineIncident: "应用当前的入口出现故障",
|
|
23
|
+
headlineUnconfirmedOutage: "主入口可能暂时无法访问(未经确认)",
|
|
24
|
+
expiry: "此通知有效期至 {expiresAt}",
|
|
25
|
+
go: "前往 {host}",
|
|
26
|
+
},
|
|
27
|
+
en: {
|
|
28
|
+
documentTitle: "Alternative entry",
|
|
29
|
+
loading: "Checking for alternative entries…",
|
|
30
|
+
empty: "No alternative entry is available right now",
|
|
31
|
+
headlineMigrating: "This app is moving to a new address",
|
|
32
|
+
headlineIncident: "This app's usual address is having problems",
|
|
33
|
+
headlineUnconfirmedOutage: "The usual address may be unreachable (unconfirmed)",
|
|
34
|
+
expiry: "This notice is valid until {expiresAt}",
|
|
35
|
+
go: "Go to {host}",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/** Combines the built-in table for `locale` with a host's (already validated) partial override, one key at a
|
|
39
|
+
* time — an override key always wins, an absent one falls back to the built-in copy. */
|
|
40
|
+
export function mergeEntryPageMessages(locale, overrides) {
|
|
41
|
+
return { ...ENTRY_PAGE_MESSAGES[locale], ...overrides };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/page/messages.ts"],"names":[],"mappings":"AAoBA;8EAC8E;AAC9E,MAAM,CAAC,MAAM,uBAAuB,GAA4C;IAC9E,eAAe;IACf,SAAS;IACT,OAAO;IACP,mBAAmB;IACnB,kBAAkB;IAClB,2BAA2B;IAC3B,QAAQ;IACR,IAAI;CACsD,CAAC;AAE7D;;+CAE+C;AAC/C,MAAM,CAAC,MAAM,mBAAmB,GAA+D;IAC7F,OAAO,EAAE;QACP,aAAa,EAAE,MAAM;QACrB,OAAO,EAAE,WAAW;QACpB,KAAK,EAAE,aAAa;QACpB,iBAAiB,EAAE,YAAY;QAC/B,gBAAgB,EAAE,aAAa;QAC/B,yBAAyB,EAAE,mBAAmB;QAC9C,MAAM,EAAE,qBAAqB;QAC7B,EAAE,EAAE,WAAW;KAChB;IACD,EAAE,EAAE;QACF,aAAa,EAAE,mBAAmB;QAClC,OAAO,EAAE,mCAAmC;QAC5C,KAAK,EAAE,6CAA6C;QACpD,iBAAiB,EAAE,qCAAqC;QACxD,gBAAgB,EAAE,6CAA6C;QAC/D,yBAAyB,EAAE,oDAAoD;QAC/E,MAAM,EAAE,wCAAwC;QAChD,EAAE,EAAE,cAAc;KACnB;CACF,CAAC;AAEF;yFACyF;AACzF,MAAM,UAAU,sBAAsB,CACpC,MAA0B,EAC1B,SAAyC;IAEzC,OAAO,EAAE,GAAG,mBAAmB,CAAC,MAAM,CAAC,EAAE,GAAG,SAAS,EAAE,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EntryResolution } from "../resolve.js";
|
|
2
|
+
export type EntryPageEntry = {
|
|
3
|
+
readonly host: string;
|
|
4
|
+
readonly href: string;
|
|
5
|
+
};
|
|
6
|
+
export type EntryPageModel = {
|
|
7
|
+
readonly kind: "none";
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: "entries";
|
|
10
|
+
readonly status: "migrating" | "incident" | "unconfirmed-outage";
|
|
11
|
+
readonly message: string | null;
|
|
12
|
+
readonly expiresAt: string;
|
|
13
|
+
readonly entries: readonly EntryPageEntry[];
|
|
14
|
+
};
|
|
15
|
+
export declare function buildPageModel(resolution: EntryResolution, returnPath: string | null): EntryPageModel;
|
|
16
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/page/model.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,oBAAoB,CAAC;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;CAC7C,CAAC;AAqCN,wBAAgB,cAAc,CAAC,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAiBrG"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `"2026-10-01T08:00:00Z"` -> `"2026-10-01 08:00 UTC"`. String slicing only, never `Date`: the manifest contract
|
|
3
|
+
* (spec's "契约" table) guarantees `expiresAt` is strictly `YYYY-MM-DDTHH:mm:ssZ`, so this cannot depend on the
|
|
4
|
+
* viewer's time zone or locale the way `toLocaleString` would.
|
|
5
|
+
*/
|
|
6
|
+
function formatExpiresAt(value) {
|
|
7
|
+
return `${value.slice(0, 10)} ${value.slice(11, 16)} UTC`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Builds one entry's host/href, or `undefined` if it must be dropped.
|
|
11
|
+
*
|
|
12
|
+
* `startPath` was already validated at sign time (see this package's T1 record, "给 T6 的记录" in
|
|
13
|
+
* tasks/pwa-entry-resilience/plan.md), but this re-asserts it as defense in depth rather than trusting that to
|
|
14
|
+
* still hold: `new URL(startPath, origin)` is resolved and its own `origin` is required to still equal the
|
|
15
|
+
* manifest's declared `origin` — a `startPath` such as `"//evil.example/x"` or an absolute
|
|
16
|
+
* `"https://evil.example/"` would otherwise escape the approved origin at render time.
|
|
17
|
+
*/
|
|
18
|
+
function buildEntry(entry, returnPath) {
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(entry.startPath, entry.origin);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
if (url.origin !== entry.origin)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (returnPath !== null)
|
|
29
|
+
url.searchParams.set("pwa-return", returnPath);
|
|
30
|
+
return { host: url.host, href: url.href };
|
|
31
|
+
}
|
|
32
|
+
export function buildPageModel(resolution, returnPath) {
|
|
33
|
+
if (resolution.kind === "none")
|
|
34
|
+
return { kind: "none" };
|
|
35
|
+
const entries = [];
|
|
36
|
+
for (const entry of resolution.entries) {
|
|
37
|
+
const built = buildEntry(entry, returnPath);
|
|
38
|
+
if (built !== undefined)
|
|
39
|
+
entries.push(built);
|
|
40
|
+
}
|
|
41
|
+
if (entries.length === 0)
|
|
42
|
+
return { kind: "none" };
|
|
43
|
+
return {
|
|
44
|
+
kind: "entries",
|
|
45
|
+
status: resolution.status,
|
|
46
|
+
message: resolution.manifest.reason.message ?? null,
|
|
47
|
+
expiresAt: formatExpiresAt(resolution.manifest.expiresAt),
|
|
48
|
+
entries,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/page/model.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC;AAC5D,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,UAAU,CACjB,KAA8D,EAC9D,UAAyB;IAEzB,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAElD,IAAI,UAAU,KAAK,IAAI;QAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAExE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAA2B,EAAE,UAAyB;IACnF,IAAI,UAAU,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAExD,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAC5C,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAElD,OAAO;QACL,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,OAAO,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI;QACnD,SAAS,EAAE,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC;QACzD,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EntryPageModel } from "./model.js";
|
|
2
|
+
import type { PwaEntryPageMessages } from "./messages.js";
|
|
3
|
+
export type PageElement = {
|
|
4
|
+
textContent: string | null;
|
|
5
|
+
append(...children: PageElement[]): void;
|
|
6
|
+
setAttribute(name: string, value: string): void;
|
|
7
|
+
addEventListener(type: "click", listener: () => void): void;
|
|
8
|
+
};
|
|
9
|
+
export type PageDocument = {
|
|
10
|
+
createElement(tag: "h1" | "p" | "ul" | "li" | "button"): PageElement;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Renders `model` into `root`, replacing whatever was there. `navigate` is only ever registered as a click
|
|
14
|
+
* listener here — it is never called during rendering, including when there is exactly one entry, and no
|
|
15
|
+
* attribute carries an entry's `href`: it lives only in the closure the listener captures.
|
|
16
|
+
*
|
|
17
|
+
* Every element created here — including `root` itself — carries the class from spec/pwa-entry-resilience.md's
|
|
18
|
+
* "修订:恢复页的默认样式与宿主定制" class table, and no other: test/page/render.test.ts and
|
|
19
|
+
* test/page/class-alignment.test.ts hold both halves of that contract (every class used here appears in the
|
|
20
|
+
* default stylesheet, src/vite/default-style.ts, and vice versa) so structure and styling cannot silently drift
|
|
21
|
+
* apart from one another.
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderPage(root: PageElement, model: EntryPageModel | "loading", doc: PageDocument, navigate: (href: string) => void, messages: PwaEntryPageMessages): void;
|
|
24
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/page/render.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,GAAG,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACzC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAE,aAAa,CAAC,GAAG,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,QAAQ,GAAG,WAAW,CAAA;CAAE,CAAC;AAmBpG;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,GAAG,EAAE,YAAY,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAChC,QAAQ,EAAE,oBAAoB,GAC7B,IAAI,CAuCN"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Maps a model status to the `messages` key carrying its headline — see spec/pwa-entry-resilience.md's "修订:恢
|
|
2
|
+
* 复页的构建期语言与文案覆盖" contract table. */
|
|
3
|
+
const STATUS_HEADLINE_KEY = {
|
|
4
|
+
migrating: "headlineMigrating",
|
|
5
|
+
incident: "headlineIncident",
|
|
6
|
+
"unconfirmed-outage": "headlineUnconfirmedOutage",
|
|
7
|
+
};
|
|
8
|
+
function paragraph(doc, text, className) {
|
|
9
|
+
const element = doc.createElement("p");
|
|
10
|
+
element.textContent = text;
|
|
11
|
+
element.setAttribute("class", className);
|
|
12
|
+
return element;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Renders `model` into `root`, replacing whatever was there. `navigate` is only ever registered as a click
|
|
16
|
+
* listener here — it is never called during rendering, including when there is exactly one entry, and no
|
|
17
|
+
* attribute carries an entry's `href`: it lives only in the closure the listener captures.
|
|
18
|
+
*
|
|
19
|
+
* Every element created here — including `root` itself — carries the class from spec/pwa-entry-resilience.md's
|
|
20
|
+
* "修订:恢复页的默认样式与宿主定制" class table, and no other: test/page/render.test.ts and
|
|
21
|
+
* test/page/class-alignment.test.ts hold both halves of that contract (every class used here appears in the
|
|
22
|
+
* default stylesheet, src/vite/default-style.ts, and vice versa) so structure and styling cannot silently drift
|
|
23
|
+
* apart from one another.
|
|
24
|
+
*/
|
|
25
|
+
export function renderPage(root, model, doc, navigate, messages) {
|
|
26
|
+
root.textContent = "";
|
|
27
|
+
root.setAttribute("class", "pwa-entry");
|
|
28
|
+
if (model === "loading") {
|
|
29
|
+
root.append(paragraph(doc, messages.loading, "pwa-entry__loading"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (model.kind === "none") {
|
|
33
|
+
root.append(paragraph(doc, messages.empty, "pwa-entry__empty"));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const heading = doc.createElement("h1");
|
|
37
|
+
heading.textContent = messages[STATUS_HEADLINE_KEY[model.status]];
|
|
38
|
+
heading.setAttribute("class", "pwa-entry__headline");
|
|
39
|
+
root.append(heading);
|
|
40
|
+
if (model.message !== null)
|
|
41
|
+
root.append(paragraph(doc, model.message, "pwa-entry__message"));
|
|
42
|
+
root.append(paragraph(doc, messages.expiry.replace("{expiresAt}", () => model.expiresAt), "pwa-entry__expiry"));
|
|
43
|
+
const list = doc.createElement("ul");
|
|
44
|
+
list.setAttribute("class", "pwa-entry__list");
|
|
45
|
+
for (const entry of model.entries) {
|
|
46
|
+
const item = doc.createElement("li");
|
|
47
|
+
item.setAttribute("class", "pwa-entry__item");
|
|
48
|
+
const button = doc.createElement("button");
|
|
49
|
+
// A replacement function, not a string: a host may contain `$` (a valid URL host), and a string replacement would
|
|
50
|
+
// read `$&` or `$'` in it as a pattern.
|
|
51
|
+
button.textContent = messages.go.replace("{host}", () => entry.host);
|
|
52
|
+
button.setAttribute("type", "button");
|
|
53
|
+
button.setAttribute("class", "pwa-entry__button");
|
|
54
|
+
const href = entry.href;
|
|
55
|
+
button.addEventListener("click", () => navigate(href));
|
|
56
|
+
item.append(button);
|
|
57
|
+
list.append(item);
|
|
58
|
+
}
|
|
59
|
+
root.append(list);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/page/render.ts"],"names":[],"mappings":"AAiBA;qCACqC;AACrC,MAAM,mBAAmB,GAErB;IACF,SAAS,EAAE,mBAAmB;IAC9B,QAAQ,EAAE,kBAAkB;IAC5B,oBAAoB,EAAE,2BAA2B;CAClD,CAAC;AAEF,SAAS,SAAS,CAAC,GAAiB,EAAE,IAAY,EAAE,SAAiB;IACnE,MAAM,OAAO,GAAG,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CACxB,IAAiB,EACjB,KAAiC,EACjC,GAAiB,EACjB,QAAgC,EAChC,QAA8B;IAE9B,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;IACtB,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAExC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,CAAC,WAAW,GAAG,QAAQ,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,YAAY,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAErB,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;QAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAC7F,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAEhH,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAC9C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC3C,kHAAkH;QAClH,wCAAwC;QACxC,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACtC,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACxB,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { EntryProbes } from "./decide.js";
|
|
2
|
+
import type { EntryDiagnostic } from "./diagnostics.js";
|
|
3
|
+
import type { EntryManifest, EntryManifestEntry } from "./types.js";
|
|
4
|
+
export type EntryRuntimeConfig = {
|
|
5
|
+
readonly appId: string;
|
|
6
|
+
readonly environment: string;
|
|
7
|
+
readonly scope: string;
|
|
8
|
+
readonly recoveryPagePath: string;
|
|
9
|
+
/** Not read by this file's own read path; carried here so the one virtual config module (src/vite/index.ts) can
|
|
10
|
+
* also supply it to `updateEntryManifest`'s wiring (src/update.ts's `EntryManifestUpdateConfig`, shared via
|
|
11
|
+
* src/client/index.ts) without a second config shape. */
|
|
12
|
+
readonly maxValidityDays: number;
|
|
13
|
+
};
|
|
14
|
+
export type EntryRuntimePorts = EntryProbes & {
|
|
15
|
+
now(): number;
|
|
16
|
+
currentOrigin(): string;
|
|
17
|
+
loadStored(): Promise<EntryManifest | null>;
|
|
18
|
+
};
|
|
19
|
+
export type EntryResolution = {
|
|
20
|
+
readonly kind: "none";
|
|
21
|
+
readonly diagnostics: readonly EntryDiagnostic[];
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "available";
|
|
24
|
+
readonly status: "migrating" | "incident" | "unconfirmed-outage";
|
|
25
|
+
readonly manifest: EntryManifest;
|
|
26
|
+
/** The entries the recovery page may show — see `EntryDecision`'s own field of the same name. */
|
|
27
|
+
readonly entries: readonly EntryManifestEntry[];
|
|
28
|
+
readonly diagnostics: readonly EntryDiagnostic[];
|
|
29
|
+
};
|
|
30
|
+
export declare function resolveEntryRecovery(config: EntryRuntimeConfig, ports: EntryRuntimePorts): Promise<EntryResolution>;
|
|
31
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;;8DAE0D;IAC1D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG;IAC5C,GAAG,IAAI,MAAM,CAAC;IACd,aAAa,IAAI,MAAM,CAAC;IACxB,UAAU,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,GAC3E;IACE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,oBAAoB,CAAC;IACjE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,iGAAiG;IACjG,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAC;CAClD,CAAC;AAWN,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,eAAe,CAAC,CA6C1B"}
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Read path for `checkEntryRecovery`, per spec/pwa-entry-resilience.md's revised "页面侧 API": reads the one
|
|
2
|
+
// manifest IndexedDB stores and, if it has not expired, runs the display decision (src/decide.ts) on it. No
|
|
3
|
+
// network request happens here (probes aside) and no multi-source selection: ADR-0033 (2026-09-23) removed the
|
|
4
|
+
// build-time seed and the discovery-source fetch, so there is exactly one manifest to consider, already accepted
|
|
5
|
+
// by a prior `updateEntryManifest` call (src/update.ts).
|
|
6
|
+
//
|
|
7
|
+
// Two behaviors worth calling out explicitly, both confirmed during EM3 (tasks/pwa-entry-resilience/plan.md):
|
|
8
|
+
// - The `currentOrigin`/`now` fail-closed guard below runs before any storage access, even though this file's own
|
|
9
|
+
// pipeline no longer compares `currentOrigin`'s value against anything (see the guard's own comment).
|
|
10
|
+
// - Expiry is re-checked here, on every read, not only once at `updateEntryManifest` time (see the re-check below).
|
|
11
|
+
import { decideRecovery } from "./decide.js";
|
|
12
|
+
import { diagnostic } from "./diagnostics.js";
|
|
13
|
+
import { parseStrictIsoUtc } from "./internal/iso-date.js";
|
|
14
|
+
/** Runs `fn`, returning `undefined` if it throws. */
|
|
15
|
+
function tryCall(fn) {
|
|
16
|
+
try {
|
|
17
|
+
return fn();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function resolveEntryRecovery(config, ports) {
|
|
24
|
+
const diagnostics = [];
|
|
25
|
+
// `currentOrigin` no longer filters any entry (src/select.ts dropped the "no entry may equal the current origin"
|
|
26
|
+
// rule along with its multi-source selection), but calling it here and failing closed if it throws is kept, on
|
|
27
|
+
// purpose (confirmed EM3, tasks/pwa-entry-resilience/plan.md): it is this pipeline's one check that the runtime
|
|
28
|
+
// port layer itself is sane before any storage access, mirroring `now()` right below it and `update.ts`'s own
|
|
29
|
+
// `now()`-only version of the same guard. `currentOrigin`'s *value* still matters one call later — `check.ts`'s
|
|
30
|
+
// `runEntryRecovery` calls it again to build the recovery page's return-path URL — so confirming here that it is
|
|
31
|
+
// callable at all avoids reading storage in an environment where that downstream use would already be broken.
|
|
32
|
+
const currentOrigin = tryCall(() => ports.currentOrigin());
|
|
33
|
+
const now = tryCall(() => ports.now());
|
|
34
|
+
if (currentOrigin === undefined || now === undefined || !Number.isFinite(now)) {
|
|
35
|
+
diagnostics.push(diagnostic("entry.runtime-unavailable", ""));
|
|
36
|
+
return { kind: "none", diagnostics };
|
|
37
|
+
}
|
|
38
|
+
let stored;
|
|
39
|
+
try {
|
|
40
|
+
stored = await ports.loadStored();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
diagnostics.push(diagnostic("entry.storage-unavailable", ""));
|
|
44
|
+
return { kind: "none", diagnostics };
|
|
45
|
+
}
|
|
46
|
+
if (stored === null)
|
|
47
|
+
return { kind: "none", diagnostics };
|
|
48
|
+
// Confirmed, kept (EM3, tasks/pwa-entry-resilience/plan.md): nothing re-validates the stored manifest's other
|
|
49
|
+
// fields on read (that already happened once, at `updateEntryManifest` time) — except expiry, re-checked here on
|
|
50
|
+
// every read. Signing is gone, so nothing else would stop an expired manifest from staying displayed once it is
|
|
51
|
+
// stored, and the spec requires "过期后不展示" regardless of how long ago the record was written. Config no
|
|
52
|
+
// longer carries `appId`/`environment` for anything past this loop.
|
|
53
|
+
const expiresAtMs = parseStrictIsoUtc(stored.expiresAt);
|
|
54
|
+
if (expiresAtMs === undefined || now >= expiresAtMs) {
|
|
55
|
+
diagnostics.push(diagnostic("entry.expired", ""));
|
|
56
|
+
return { kind: "none", diagnostics };
|
|
57
|
+
}
|
|
58
|
+
const decision = await decideRecovery(stored, ports);
|
|
59
|
+
if (decision.kind === "none") {
|
|
60
|
+
diagnostics.push(...decision.diagnostics);
|
|
61
|
+
return { kind: "none", diagnostics };
|
|
62
|
+
}
|
|
63
|
+
return { kind: "available", status: decision.status, manifest: decision.manifest, entries: decision.entries, diagnostics };
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,4GAA4G;AAC5G,+GAA+G;AAC/G,iHAAiH;AACjH,yDAAyD;AACzD,EAAE;AACF,8GAA8G;AAC9G,kHAAkH;AAClH,wGAAwG;AACxG,oHAAoH;AACpH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AA+B3D,qDAAqD;AACrD,SAAS,OAAO,CAAI,EAAW;IAC7B,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAA0B,EAC1B,KAAwB;IAExB,MAAM,WAAW,GAAsB,EAAE,CAAC;IAE1C,iHAAiH;IACjH,+GAA+G;IAC/G,gHAAgH;IAChH,8GAA8G;IAC9G,gHAAgH;IAChH,iHAAiH;IACjH,8GAA8G;IAC9G,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,aAAa,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9E,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,MAA4B,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAE1D,8GAA8G;IAC9G,iHAAiH;IACjH,gHAAgH;IAChH,sGAAsG;IACtG,oEAAoE;IACpE,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACxD,IAAI,WAAW,KAAK,SAAS,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;QACpD,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC;QAClD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC7B,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACvC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC;AAC7H,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type EntryReturnPathContext = {
|
|
2
|
+
readonly origin: string;
|
|
3
|
+
readonly scope: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Validates a return path per spec/pwa-entry-resilience.md's "返回路径校验". Returns the input
|
|
7
|
+
* string unchanged when it is legal, `null` otherwise. Never throws.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeReturnPath(value: unknown, context: EntryReturnPathContext): string | null;
|
|
10
|
+
//# sourceMappingURL=return-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-path.d.ts","sourceRoot":"","sources":["../src/return-path.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AA2BF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAwBlG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Return-path validation for `?return=` on the platform recovery page. Every rule mirrors
|
|
2
|
+
// spec/pwa-entry-resilience.md's "返回路径校验" section, in the order it lists them:
|
|
3
|
+
// structural checks on the raw string -> a ".." segment check on the raw string -> decode once -> the same
|
|
4
|
+
// structural and ".." checks re-run on the decoded string -> a URL-based origin/scope check against `value`
|
|
5
|
+
// itself (not the decoded form). On any failure the whole path is discarded; nothing is ever
|
|
6
|
+
// echoed back to a caller, only `null`.
|
|
7
|
+
//
|
|
8
|
+
// The ".." check splits the *entire* string on `/`, `?` and `#` rather than truncating at the first `?`/`#`: a
|
|
9
|
+
// literal `?` introduced by decoding (e.g. `%3f`) would otherwise let a later `..` segment hide past where the
|
|
10
|
+
// raw string's own `?`/`#` truncation stopped looking (independent review finding, 2026-09-17).
|
|
11
|
+
const MAX_LENGTH = 1024;
|
|
12
|
+
/** True if `value` contains a C0 control character (U+0000-U+001F) or DEL (U+007F). */
|
|
13
|
+
function hasControlCharacter(value) {
|
|
14
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
15
|
+
const code = value.charCodeAt(index);
|
|
16
|
+
if (code <= 0x1f || code === 0x7f)
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
/** Length, single leading `/`, no backslash, no control character — checked both before and after decoding. */
|
|
22
|
+
function isStructurallyValid(value) {
|
|
23
|
+
if (value.length < 1 || value.length > MAX_LENGTH)
|
|
24
|
+
return false;
|
|
25
|
+
if (value[0] !== "/")
|
|
26
|
+
return false;
|
|
27
|
+
if (value.length > 1 && (value[1] === "/" || value[1] === "\\"))
|
|
28
|
+
return false;
|
|
29
|
+
if (value.includes("\\"))
|
|
30
|
+
return false;
|
|
31
|
+
if (hasControlCharacter(value))
|
|
32
|
+
return false;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/** True if any `/`-, `?`- or `#`-delimited segment of `value` is exactly `..`. Never truncates at `?`/`#`: the
|
|
36
|
+
* whole string is split, so a `..` segment cannot hide past a literal `?`/`#` (see this module's header comment). */
|
|
37
|
+
function hasDotDotSegment(value) {
|
|
38
|
+
return value.split(/[/?#]/).some((segment) => segment === "..");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validates a return path per spec/pwa-entry-resilience.md's "返回路径校验". Returns the input
|
|
42
|
+
* string unchanged when it is legal, `null` otherwise. Never throws.
|
|
43
|
+
*/
|
|
44
|
+
export function normalizeReturnPath(value, context) {
|
|
45
|
+
if (typeof value !== "string")
|
|
46
|
+
return null;
|
|
47
|
+
if (!isStructurallyValid(value))
|
|
48
|
+
return null;
|
|
49
|
+
if (hasDotDotSegment(value))
|
|
50
|
+
return null;
|
|
51
|
+
let decoded;
|
|
52
|
+
try {
|
|
53
|
+
decoded = decodeURIComponent(value);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (!isStructurallyValid(decoded))
|
|
59
|
+
return null;
|
|
60
|
+
if (hasDotDotSegment(decoded))
|
|
61
|
+
return null;
|
|
62
|
+
let url;
|
|
63
|
+
try {
|
|
64
|
+
url = new URL(value, context.origin);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (url.origin !== context.origin)
|
|
70
|
+
return null;
|
|
71
|
+
if (!url.pathname.startsWith(context.scope))
|
|
72
|
+
return null;
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=return-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-path.js","sourceRoot":"","sources":["../src/return-path.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,+EAA+E;AAC/E,2GAA2G;AAC3G,4GAA4G;AAC5G,6FAA6F;AAC7F,wCAAwC;AACxC,EAAE;AACF,+GAA+G;AAC/G,+GAA+G;AAC/G,gGAAgG;AAEhG,MAAM,UAAU,GAAG,IAAI,CAAC;AAOxB,uFAAuF;AACvF,SAAS,mBAAmB,CAAC,KAAa;IACxC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+GAA+G;AAC/G,SAAS,mBAAmB,CAAC,KAAa;IACxC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,UAAU;QAAE,OAAO,KAAK,CAAC;IAChE,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,mBAAmB,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;sHACsH;AACtH,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAc,EAAE,OAA+B;IACjF,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,gBAAgB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,gBAAgB,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/select.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EntryDiagnostic } from "./diagnostics.js";
|
|
2
|
+
import type { EntryManifest } from "./types.js";
|
|
3
|
+
export type EntrySelectionInput = {
|
|
4
|
+
readonly stored: EntryManifest | null;
|
|
5
|
+
readonly incoming: EntryManifest;
|
|
6
|
+
};
|
|
7
|
+
export type EntrySelection = {
|
|
8
|
+
readonly kind: "accepted";
|
|
9
|
+
readonly manifest: EntryManifest;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: "rejected";
|
|
12
|
+
readonly diagnostics: readonly EntryDiagnostic[];
|
|
13
|
+
};
|
|
14
|
+
/** Highest `sequence` wins; equal or lower is rejected without writing. */
|
|
15
|
+
export declare function selectManifest(input: EntrySelectionInput): EntrySelection;
|
|
16
|
+
//# sourceMappingURL=select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../src/select.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CAAE,GAC/D;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,CAAC;AAEpF,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,cAAc,CAKzE"}
|
package/dist/select.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Write-time "does this beat what is already stored" decision — spec/pwa-entry-resilience.md's revised
|
|
2
|
+
// "updateEntryManifest 在校验通过且 sequence 大于已存记录时写入 IndexedDB...序号不大于已存记录时不写入". Pure,
|
|
3
|
+
// synchronous, driven only by its arguments: no clock, storage or network access of its own.
|
|
4
|
+
//
|
|
5
|
+
// ADR-0033 (2026-09-23) removed the three-way (build-time seed / stored / discovery-source) selection this module
|
|
6
|
+
// used to perform, along with the signature verification that decided which candidates were even eligible: there
|
|
7
|
+
// is now exactly one incoming candidate (already field-validated by src/manifest.ts) and one stored record, and the
|
|
8
|
+
// only question is whether the incoming one's `sequence` is strictly greater.
|
|
9
|
+
import { diagnostic } from "./diagnostics.js";
|
|
10
|
+
/** Highest `sequence` wins; equal or lower is rejected without writing. */
|
|
11
|
+
export function selectManifest(input) {
|
|
12
|
+
if (input.stored !== null && input.incoming.sequence <= input.stored.sequence) {
|
|
13
|
+
return { kind: "rejected", diagnostics: [diagnostic("entry.sequence-not-greater", "/sequence")] };
|
|
14
|
+
}
|
|
15
|
+
return { kind: "accepted", manifest: input.incoming };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../src/select.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,mFAAmF;AACnF,6FAA6F;AAC7F,EAAE;AACF,kHAAkH;AAClH,iHAAiH;AACjH,oHAAoH;AACpH,8EAA8E;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAa9C,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC9E,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,UAAU,CAAC,4BAA4B,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;IACpG,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;AACxD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const ENTRY_MANIFEST_STATUSES: readonly ["normal", "migrating", "incident"];
|
|
2
|
+
export type EntryManifestStatus = (typeof ENTRY_MANIFEST_STATUSES)[number];
|
|
3
|
+
export declare const ENTRY_MANIFEST_REASON_CODES: readonly ["planned-migration", "incident", "none"];
|
|
4
|
+
export type EntryManifestReasonCode = (typeof ENTRY_MANIFEST_REASON_CODES)[number];
|
|
5
|
+
export type EntryManifestReason = {
|
|
6
|
+
readonly code: EntryManifestReasonCode;
|
|
7
|
+
/** Plain text, at most 200 UTF-16 code units, no control characters. */
|
|
8
|
+
readonly message?: string;
|
|
9
|
+
};
|
|
10
|
+
export type EntryManifestEntry = {
|
|
11
|
+
/** Normalized origin: HTTPS, or HTTP on a loopback host for local development. Not checked against any
|
|
12
|
+
* build-time approval list — ADR-0033 removed that list along with the signature it used to backstop. */
|
|
13
|
+
readonly origin: string;
|
|
14
|
+
/** Absolute path: starts with `/`, no `..` segments, no backslashes, no `//`, at most 512 characters. */
|
|
15
|
+
readonly startPath: string;
|
|
16
|
+
};
|
|
17
|
+
/** The plain object the application hands in to `updateEntryManifest`, once parsed and field-validated. */
|
|
18
|
+
export type EntryManifest = {
|
|
19
|
+
/** Non-negative safe integer; only a manifest with a strictly greater `sequence` than what is already stored
|
|
20
|
+
* replaces it (see src/select.ts). */
|
|
21
|
+
readonly sequence: number;
|
|
22
|
+
/** ISO 8601 UTC (`YYYY-MM-DDTHH:mm:ssZ`); must be in the future when handed in, and no further out than the
|
|
23
|
+
* configured `maxValidityDays`. */
|
|
24
|
+
readonly expiresAt: string;
|
|
25
|
+
readonly status: EntryManifestStatus;
|
|
26
|
+
readonly reason: EntryManifestReason;
|
|
27
|
+
/** 0 to 5 entries. */
|
|
28
|
+
readonly entries: readonly EntryManifestEntry[];
|
|
29
|
+
/** Optional; when given, must equal the application's identity exactly. */
|
|
30
|
+
readonly appId?: string;
|
|
31
|
+
/** Optional; when given, must equal the application's identity exactly. */
|
|
32
|
+
readonly environment?: string;
|
|
33
|
+
};
|
|
34
|
+
/** Everything `parseEntryManifest` needs and cannot read from ambient state — the clock included. */
|
|
35
|
+
export type EntryManifestValidationContext = {
|
|
36
|
+
readonly appId: string;
|
|
37
|
+
readonly environment: string;
|
|
38
|
+
/** Integer 1-90: the maximum number of days between the moment the manifest is handed in and its `expiresAt`. */
|
|
39
|
+
readonly maxValidityDays: number;
|
|
40
|
+
/** Current time in epoch milliseconds, injected so validation never reads the system clock. */
|
|
41
|
+
readonly now: number;
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,uBAAuB,8CAA+C,CAAC;AACpF,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3E,eAAO,MAAM,2BAA2B,oDAAqD,CAAC;AAC9F,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B;8GAC0G;IAC1G,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yGAAyG;IACzG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,2GAA2G;AAC3G,MAAM,MAAM,aAAa,GAAG;IAC1B;2CACuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;wCACoC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,sBAAsB;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAChD,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,qGAAqG;AACrG,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iHAAiH;IACjH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,+FAA+F;IAC/F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Public contract types for the entry-recovery manifest the application hands in. Field names and shapes mirror
|
|
2
|
+
// spec/pwa-entry-resilience.md's "修订:入口清单由业务应用提供" -> "契约增量" section exactly; this module only
|
|
3
|
+
// declares types, it performs no checks (see src/manifest.ts for validation).
|
|
4
|
+
//
|
|
5
|
+
// ADR-0033 (2026-09-23) removed the package's cryptographic trust root: there is no more signed envelope, build-
|
|
6
|
+
// time public key set or `schemaVersion`. `EntryKey`, `EntryEnvelope`, `EntryVerifyContext` and `EntryVerification`
|
|
7
|
+
// existed only for that model and were deleted along with src/verify.ts and src/keys.ts.
|
|
8
|
+
export const ENTRY_MANIFEST_STATUSES = ["normal", "migrating", "incident"];
|
|
9
|
+
export const ENTRY_MANIFEST_REASON_CODES = ["planned-migration", "incident", "none"];
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,8FAA8F;AAC9F,8EAA8E;AAC9E,EAAE;AACF,iHAAiH;AACjH,oHAAoH;AACpH,yFAAyF;AAEzF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAU,CAAC;AAGpF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,mBAAmB,EAAE,UAAU,EAAE,MAAM,CAAU,CAAC"}
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EntryDiagnostic } from "./diagnostics.js";
|
|
2
|
+
import type { EntryManifest } from "./types.js";
|
|
3
|
+
export type EntryManifestUpdateConfig = {
|
|
4
|
+
readonly appId: string;
|
|
5
|
+
readonly environment: string;
|
|
6
|
+
/** Integer 1-90: the maximum number of days between now and the incoming manifest's `expiresAt`. */
|
|
7
|
+
readonly maxValidityDays: number;
|
|
8
|
+
};
|
|
9
|
+
export type EntryManifestUpdatePorts = {
|
|
10
|
+
now(): number;
|
|
11
|
+
loadStored(): Promise<EntryManifest | null>;
|
|
12
|
+
saveStored(manifest: EntryManifest): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export type EntryManifestUpdateResult = {
|
|
15
|
+
readonly accepted: true;
|
|
16
|
+
readonly sequence: number;
|
|
17
|
+
} | {
|
|
18
|
+
readonly accepted: false;
|
|
19
|
+
readonly diagnostics: readonly EntryDiagnostic[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Validates `data`, compares it against the stored record, and writes it when it wins. Never throws: every port
|
|
23
|
+
* failure (an unavailable clock, a failed read, a failed write) becomes `{ accepted: false, diagnostics }` rather
|
|
24
|
+
* than a rejection — there is no room for a diagnostic on the `accepted: true` branch, so any failure that cannot
|
|
25
|
+
* be represented there must reject instead of silently pretending to have succeeded.
|
|
26
|
+
*/
|
|
27
|
+
export declare function runEntryManifestUpdate(data: unknown, config: EntryManifestUpdateConfig, ports: EntryManifestUpdatePorts): Promise<EntryManifestUpdateResult>;
|
|
28
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oGAAoG;IACpG,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,IAAI,MAAM,CAAC;IACd,UAAU,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC5C,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GACjC;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,CAAC;AAEnF;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,yBAAyB,EACjC,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,yBAAyB,CAAC,CAoCpC"}
|