@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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/dist/browser/index.d.ts +28 -0
  4. package/dist/browser/index.d.ts.map +1 -0
  5. package/dist/browser/index.js +39 -0
  6. package/dist/browser/index.js.map +1 -0
  7. package/dist/browser/indexeddb.d.ts +12 -0
  8. package/dist/browser/indexeddb.d.ts.map +1 -0
  9. package/dist/browser/indexeddb.js +61 -0
  10. package/dist/browser/indexeddb.js.map +1 -0
  11. package/dist/browser/probes.d.ts +13 -0
  12. package/dist/browser/probes.d.ts.map +1 -0
  13. package/dist/browser/probes.js +36 -0
  14. package/dist/browser/probes.js.map +1 -0
  15. package/dist/check.d.ts +21 -0
  16. package/dist/check.d.ts.map +1 -0
  17. package/dist/check.js +48 -0
  18. package/dist/check.js.map +1 -0
  19. package/dist/client/index.d.ts +19 -0
  20. package/dist/client/index.d.ts.map +1 -0
  21. package/dist/client/index.js +49 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/decide.d.ts +19 -0
  24. package/dist/decide.d.ts.map +1 -0
  25. package/dist/decide.js +38 -0
  26. package/dist/decide.js.map +1 -0
  27. package/dist/diagnostics.d.ts +11 -0
  28. package/dist/diagnostics.d.ts.map +1 -0
  29. package/dist/diagnostics.js +51 -0
  30. package/dist/diagnostics.js.map +1 -0
  31. package/dist/index.d.ts +13 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +27 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/internal/base64url.d.ts +8 -0
  36. package/dist/internal/base64url.d.ts.map +1 -0
  37. package/dist/internal/base64url.js +32 -0
  38. package/dist/internal/base64url.js.map +1 -0
  39. package/dist/internal/iso-date.d.ts +8 -0
  40. package/dist/internal/iso-date.d.ts.map +1 -0
  41. package/dist/internal/iso-date.js +39 -0
  42. package/dist/internal/iso-date.js.map +1 -0
  43. package/dist/internal/origin.d.ts +6 -0
  44. package/dist/internal/origin.d.ts.map +1 -0
  45. package/dist/internal/origin.js +19 -0
  46. package/dist/internal/origin.js.map +1 -0
  47. package/dist/internal/plain-object.d.ts +8 -0
  48. package/dist/internal/plain-object.d.ts.map +1 -0
  49. package/dist/internal/plain-object.js +16 -0
  50. package/dist/internal/plain-object.js.map +1 -0
  51. package/dist/internal/theme-key.d.ts +2 -0
  52. package/dist/internal/theme-key.d.ts.map +1 -0
  53. package/dist/internal/theme-key.js +13 -0
  54. package/dist/internal/theme-key.js.map +1 -0
  55. package/dist/manifest.d.ts +13 -0
  56. package/dist/manifest.d.ts.map +1 -0
  57. package/dist/manifest.js +153 -0
  58. package/dist/manifest.js.map +1 -0
  59. package/dist/page/main.d.ts +2 -0
  60. package/dist/page/main.d.ts.map +1 -0
  61. package/dist/page/main.js +88 -0
  62. package/dist/page/main.js.map +1 -0
  63. package/dist/page/messages.d.ts +24 -0
  64. package/dist/page/messages.d.ts.map +1 -0
  65. package/dist/page/messages.js +43 -0
  66. package/dist/page/messages.js.map +1 -0
  67. package/dist/page/model.d.ts +16 -0
  68. package/dist/page/model.d.ts.map +1 -0
  69. package/dist/page/model.js +51 -0
  70. package/dist/page/model.js.map +1 -0
  71. package/dist/page/render.d.ts +24 -0
  72. package/dist/page/render.d.ts.map +1 -0
  73. package/dist/page/render.js +61 -0
  74. package/dist/page/render.js.map +1 -0
  75. package/dist/resolve.d.ts +31 -0
  76. package/dist/resolve.d.ts.map +1 -0
  77. package/dist/resolve.js +65 -0
  78. package/dist/resolve.js.map +1 -0
  79. package/dist/return-path.d.ts +10 -0
  80. package/dist/return-path.d.ts.map +1 -0
  81. package/dist/return-path.js +75 -0
  82. package/dist/return-path.js.map +1 -0
  83. package/dist/select.d.ts +16 -0
  84. package/dist/select.d.ts.map +1 -0
  85. package/dist/select.js +17 -0
  86. package/dist/select.js.map +1 -0
  87. package/dist/types.d.ts +43 -0
  88. package/dist/types.d.ts.map +1 -0
  89. package/dist/types.js +10 -0
  90. package/dist/types.js.map +1 -0
  91. package/dist/update.d.ts +28 -0
  92. package/dist/update.d.ts.map +1 -0
  93. package/dist/update.js +52 -0
  94. package/dist/update.js.map +1 -0
  95. package/dist/vite/default-style.d.ts +2 -0
  96. package/dist/vite/default-style.d.ts.map +1 -0
  97. package/dist/vite/default-style.js +109 -0
  98. package/dist/vite/default-style.js.map +1 -0
  99. package/dist/vite/entry-page.d.ts +6 -0
  100. package/dist/vite/entry-page.d.ts.map +1 -0
  101. package/dist/vite/entry-page.js +22 -0
  102. package/dist/vite/entry-page.js.map +1 -0
  103. package/dist/vite/index.d.ts +24 -0
  104. package/dist/vite/index.d.ts.map +1 -0
  105. package/dist/vite/index.js +190 -0
  106. package/dist/vite/index.js.map +1 -0
  107. package/dist/vite/options.d.ts +31 -0
  108. package/dist/vite/options.d.ts.map +1 -0
  109. package/dist/vite/options.js +118 -0
  110. package/dist/vite/options.js.map +1 -0
  111. package/package.json +53 -0
@@ -0,0 +1,51 @@
1
+ // Every diagnostic this package can produce, plus the closed shape every one of them takes. Codes are the only
2
+ // thing callers may branch on; `path` is a JSON-Pointer-like location built from field names and array indices
3
+ // that are always known ahead of time — never from a value read out of the manifest being checked. That is what
4
+ // keeps the "never echo input" rule (see the module's trust model) mechanically true rather than merely intended.
5
+ //
6
+ // ADR-0033 (2026-09-23) removed the package's cryptographic trust root: the platform no longer verifies a signed
7
+ // envelope, so every code that named an envelope, a public key set, a build-time seed or a discovery-source fetch
8
+ // against an origin approval list was deleted here along with src/verify.ts, src/keys.ts (see tasks/pwa-entry-
9
+ // resilience/plan.md, "EM2") and src/browser/discovery.ts's `entry.discovery-*` codes ("EM3").
10
+ export const ENTRY_DIAGNOSTIC_CODES = [
11
+ // Manifest field validation (src/manifest.ts): the plain object the application hands in.
12
+ "entry.manifest-invalid-shape",
13
+ "entry.app-id-mismatch",
14
+ "entry.environment-mismatch",
15
+ "entry.sequence-invalid",
16
+ "entry.expires-at-invalid",
17
+ "entry.expired",
18
+ "entry.validity-period-too-long",
19
+ "entry.status-invalid",
20
+ "entry.reason-invalid-shape",
21
+ "entry.reason-code-invalid",
22
+ "entry.reason-message-invalid",
23
+ "entry.entries-too-many",
24
+ "entry.entry-invalid-shape",
25
+ "entry.entry-origin-invalid",
26
+ "entry.entry-start-path-invalid",
27
+ // Write-time "does this beat what is already stored" decision (src/select.ts, src/update.ts).
28
+ "entry.sequence-not-greater",
29
+ // Display decision (src/decide.ts) and storage/runtime ports (src/resolve.ts, src/update.ts).
30
+ "entry.no-entries",
31
+ "entry.storage-unavailable",
32
+ "entry.storage-write-failed",
33
+ "entry.runtime-unavailable",
34
+ "entry.return-path-dropped",
35
+ // pwaEntryResilience plugin option validation and build integration (src/vite/), see
36
+ // spec/pwa-entry-resilience.md's "构建集成". Build-time-only: they name a plugin option or a build state, never a
37
+ // manifest field.
38
+ "entry.max-validity-days-invalid",
39
+ "entry.css-invalid",
40
+ "entry.option-removed",
41
+ "entry.locale-invalid",
42
+ "entry.message-invalid",
43
+ "entry.base-mismatch",
44
+ "entry.platform-plugin-missing",
45
+ "entry.platform-plan-unavailable",
46
+ "entry.recovery-page-not-precached",
47
+ ];
48
+ export function diagnostic(code, path) {
49
+ return { code, path };
50
+ }
51
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,+GAA+G;AAC/G,+GAA+G;AAC/G,gHAAgH;AAChH,kHAAkH;AAClH,EAAE;AACF,iHAAiH;AACjH,kHAAkH;AAClH,+GAA+G;AAC/G,+FAA+F;AAE/F,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,0FAA0F;IAC1F,8BAA8B;IAC9B,uBAAuB;IACvB,4BAA4B;IAC5B,wBAAwB;IACxB,0BAA0B;IAC1B,eAAe;IACf,gCAAgC;IAChC,sBAAsB;IACtB,4BAA4B;IAC5B,2BAA2B;IAC3B,8BAA8B;IAC9B,wBAAwB;IACxB,2BAA2B;IAC3B,4BAA4B;IAC5B,gCAAgC;IAChC,8FAA8F;IAC9F,4BAA4B;IAC5B,8FAA8F;IAC9F,kBAAkB;IAClB,2BAA2B;IAC3B,4BAA4B;IAC5B,2BAA2B;IAC3B,2BAA2B;IAC3B,qFAAqF;IACrF,8GAA8G;IAC9G,kBAAkB;IAClB,iCAAiC;IACjC,mBAAmB;IACnB,sBAAsB;IACtB,sBAAsB;IACtB,uBAAuB;IACvB,qBAAqB;IACrB,+BAA+B;IAC/B,iCAAiC;IACjC,mCAAmC;CAC3B,CAAC;AAaX,MAAM,UAAU,UAAU,CAAC,IAAyB,EAAE,IAAyB;IAC7E,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,13 @@
1
+ export { runEntryRecovery } from "./check.js";
2
+ export type { EntryRecoveryResult, EntryRuntimeConfig, EntryRuntimePorts } from "./check.js";
3
+ export { decideRecovery } from "./decide.js";
4
+ export type { EntryDecision, EntryProbes } from "./decide.js";
5
+ export { ENTRY_DIAGNOSTIC_CODES } from "./diagnostics.js";
6
+ export type { EntryDiagnostic, EntryDiagnosticCode, EntryDiagnosticPath } from "./diagnostics.js";
7
+ export { parseEntryManifest } from "./manifest.js";
8
+ export type { ManifestResult as EntryManifestParseResult } from "./manifest.js";
9
+ export { ENTRY_MANIFEST_REASON_CODES, ENTRY_MANIFEST_STATUSES, } from "./types.js";
10
+ export type { EntryManifest, EntryManifestEntry, EntryManifestReason, EntryManifestReasonCode, EntryManifestStatus, EntryManifestValidationContext, } from "./types.js";
11
+ export { createEntryRecoveryChecker, createIndexedDbStore, createProbes, } from "./browser/index.js";
12
+ export type { CreateIndexedDbStoreOptions, CreateProbesOptions, EntryCheckerConfig, EntryCheckOptions, EntryIndexedDbStore, } from "./browser/index.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,cAAc,IAAI,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,8BAA8B,GAC/B,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,2BAA2B,EAC3B,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ // Public entry: the entry-recovery manifest contract, its standalone validator, the display decision, the
2
+ // page-side query function and its browser adapters. The recovery page (src/page/) and the Vite plugin (src/vite/)
3
+ // are implemented but not re-exported here: the plugin has its own entry point (see the package's `./vite`
4
+ // export), and the recovery page is only ever loaded by the build the plugin emits, never imported directly by an
5
+ // application.
6
+ //
7
+ // This package's actual page-side API for applications — `checkEntryRecovery` and `updateEntryManifest` — lives on
8
+ // the `./client` export (src/client/index.ts) instead, since both need the Vite plugin's virtual config module.
9
+ // What is exported below is everything under that: the pure orchestration (`runEntryRecovery`), the manifest
10
+ // contract's types and validator, and the browser port builders, for anything that wants to assemble its own
11
+ // wiring rather than use the client facade.
12
+ //
13
+ // `parseEntryManifest` (spec/pwa-entry-resilience.md's "修订:导出清单校验函数") is exported here specifically so a
14
+ // caller can validate a manifest in Node — a CI check or a backend self-check before handing it to
15
+ // `updateEntryManifest` — without a browser. It is re-exported verbatim from src/manifest.ts, which stays the only
16
+ // place its rules are implemented; its result type is renamed on the way out (`ManifestResult` ->
17
+ // `EntryManifestParseResult`) to read as public API, but the internal name in src/manifest.ts is unchanged.
18
+ //
19
+ // The write-time "does this beat what's stored" decision (src/select.ts) and `runEntryManifestUpdate`
20
+ // (src/update.ts) stay internal — only `updateEntryManifest`, wired on `./client`, exposes that path.
21
+ export { runEntryRecovery } from "./check.js";
22
+ export { decideRecovery } from "./decide.js";
23
+ export { ENTRY_DIAGNOSTIC_CODES } from "./diagnostics.js";
24
+ export { parseEntryManifest } from "./manifest.js";
25
+ export { ENTRY_MANIFEST_REASON_CODES, ENTRY_MANIFEST_STATUSES, } from "./types.js";
26
+ export { createEntryRecoveryChecker, createIndexedDbStore, createProbes, } from "./browser/index.js";
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,mHAAmH;AACnH,2GAA2G;AAC3G,kHAAkH;AAClH,eAAe;AACf,EAAE;AACF,mHAAmH;AACnH,gHAAgH;AAChH,6GAA6G;AAC7G,6GAA6G;AAC7G,4CAA4C;AAC5C,EAAE;AACF,yGAAyG;AACzG,mGAAmG;AACnG,mHAAmH;AACnH,kGAAkG;AAClG,4GAA4G;AAC5G,EAAE;AACF,sGAAsG;AACtG,sGAAsG;AACtG,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,YAAY,CAAC;AAUpB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,YAAY,GACb,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Strict base64url decode: only `A-Z a-z 0-9 - _`, no `=` padding, no whitespace, and no non-canonical
3
+ * trailing bits (the "spare" bits in the last character group must be zero). Never throws; an invalid
4
+ * encoding returns `undefined`. Deliberately does not use `atob`/`Buffer`, neither of which reject this
5
+ * strictly.
6
+ */
7
+ export declare function decodeBase64Url(value: string): Uint8Array<ArrayBuffer> | undefined;
8
+ //# sourceMappingURL=base64url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../../src/internal/base64url.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,SAAS,CAoBlF"}
@@ -0,0 +1,32 @@
1
+ const BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2
+ const CHAR_VALUES = new Map([...BASE64URL_ALPHABET].map((char, index) => [char, index]));
3
+ const STRICT_CHARSET = /^[A-Za-z0-9_-]*$/;
4
+ /**
5
+ * Strict base64url decode: only `A-Z a-z 0-9 - _`, no `=` padding, no whitespace, and no non-canonical
6
+ * trailing bits (the "spare" bits in the last character group must be zero). Never throws; an invalid
7
+ * encoding returns `undefined`. Deliberately does not use `atob`/`Buffer`, neither of which reject this
8
+ * strictly.
9
+ */
10
+ export function decodeBase64Url(value) {
11
+ if (!STRICT_CHARSET.test(value) || value.length % 4 === 1)
12
+ return undefined;
13
+ const bytes = [];
14
+ let bits = 0;
15
+ let bitCount = 0;
16
+ for (const char of value) {
17
+ const charValue = CHAR_VALUES.get(char);
18
+ if (charValue === undefined)
19
+ return undefined;
20
+ bits = ((bits << 6) | charValue) & 0xffff;
21
+ bitCount += 6;
22
+ if (bitCount >= 8) {
23
+ bitCount -= 8;
24
+ bytes.push((bits >>> bitCount) & 0xff);
25
+ }
26
+ }
27
+ // Whatever is left over is padding bits that carry no byte; a canonical encoding always zeroes them.
28
+ if (bitCount > 0 && (bits & ((1 << bitCount) - 1)) !== 0)
29
+ return undefined;
30
+ return new Uint8Array(bytes);
31
+ }
32
+ //# sourceMappingURL=base64url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64url.js","sourceRoot":"","sources":["../../src/internal/base64url.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG,kEAAkE,CAAC;AAC9F,MAAM,WAAW,GAAgC,IAAI,GAAG,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AACtH,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE5E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC9C,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC;QAC1C,QAAQ,IAAI,CAAC,CAAC;QACd,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAClB,QAAQ,IAAI,CAAC,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IACD,qGAAqG;IACrG,IAAI,QAAQ,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE3E,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Strict ISO 8601 UTC parse: exactly `YYYY-MM-DDTHH:mm:ssZ`, with every field range-checked (including
3
+ * leap years). `Date.parse` accepts many non-conformant formats and silently rolls over out-of-range
4
+ * fields instead of rejecting them, so it is never used here. Returns epoch milliseconds, or `undefined`
5
+ * when `value` is not in this exact form.
6
+ */
7
+ export declare function parseStrictIsoUtc(value: string): number | undefined;
8
+ //# sourceMappingURL=iso-date.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iso-date.d.ts","sourceRoot":"","sources":["../../src/internal/iso-date.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuBnE"}
@@ -0,0 +1,39 @@
1
+ const ISO_UTC = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/;
2
+ const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
3
+ function isLeapYear(year) {
4
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
5
+ }
6
+ /**
7
+ * Strict ISO 8601 UTC parse: exactly `YYYY-MM-DDTHH:mm:ssZ`, with every field range-checked (including
8
+ * leap years). `Date.parse` accepts many non-conformant formats and silently rolls over out-of-range
9
+ * fields instead of rejecting them, so it is never used here. Returns epoch milliseconds, or `undefined`
10
+ * when `value` is not in this exact form.
11
+ */
12
+ export function parseStrictIsoUtc(value) {
13
+ const match = ISO_UTC.exec(value);
14
+ if (!match)
15
+ return undefined;
16
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match;
17
+ if (!yearText || !monthText || !dayText || !hourText || !minuteText || !secondText)
18
+ return undefined;
19
+ const year = Number(yearText);
20
+ const month = Number(monthText);
21
+ const day = Number(dayText);
22
+ const hour = Number(hourText);
23
+ const minute = Number(minuteText);
24
+ const second = Number(secondText);
25
+ // Below the Unix epoch's year is out of range for this contract: every manifest/key timestamp this package
26
+ // compares is epoch-relative, so a pre-1970 year could otherwise parse to a negative-but-finite value that
27
+ // silently satisfies every ordering check it is compared against (independent review finding, 2026-09-17).
28
+ if (year < 1970)
29
+ return undefined;
30
+ if (month < 1 || month > 12)
31
+ return undefined;
32
+ const daysInMonth = month === 2 && isLeapYear(year) ? 29 : DAYS_IN_MONTH[month - 1];
33
+ if (daysInMonth === undefined || day < 1 || day > daysInMonth)
34
+ return undefined;
35
+ if (hour > 23 || minute > 59 || second > 59)
36
+ return undefined;
37
+ return Date.UTC(year, month - 1, day, hour, minute, second);
38
+ }
39
+ //# sourceMappingURL=iso-date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iso-date.js","sourceRoot":"","sources":["../../src/internal/iso-date.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,oDAAoD,CAAC;AACrE,MAAM,aAAa,GAAsB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAE1F,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,IAAI,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC;IACjF,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAErG,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAElC,2GAA2G;IAC3G,2GAA2G;IAC3G,2GAA2G;IAC3G,IAAI,IAAI,GAAG,IAAI;QAAE,OAAO,SAAS,CAAC;IAClC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,SAAS,CAAC;IAC9C,MAAM,WAAW,GAAG,KAAK,KAAK,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACpF,IAAI,WAAW,KAAK,SAAS,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,WAAW;QAAE,OAAO,SAAS,CAAC;IAChF,IAAI,IAAI,GAAG,EAAE,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM,GAAG,EAAE;QAAE,OAAO,SAAS,CAAC;IAE9D,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Normalized origin per the manifest contract: `new URL(value).origin === value` (no userinfo, path, query
3
+ * or fragment), protocol `https:`, with the sole exception of `http:` on a loopback host (any port).
4
+ */
5
+ export declare function isNormalizedOrigin(value: string): boolean;
6
+ //# sourceMappingURL=origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/internal/origin.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMzD"}
@@ -0,0 +1,19 @@
1
+ // Loopback hosts the spec treats as potentially trustworthy for local development and tests, matching
2
+ // contracts' internal/paths.ts `isSecureOrigin` exemption list. `URL#hostname` keeps the brackets around an
3
+ // IPv6 literal (`new URL("http://[::1]").hostname === "[::1]"`), so the bracketed form is the correct key.
4
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
5
+ /**
6
+ * Normalized origin per the manifest contract: `new URL(value).origin === value` (no userinfo, path, query
7
+ * or fragment), protocol `https:`, with the sole exception of `http:` on a loopback host (any port).
8
+ */
9
+ export function isNormalizedOrigin(value) {
10
+ if (!URL.canParse(value))
11
+ return false;
12
+ const url = new URL(value);
13
+ if (url.origin !== value)
14
+ return false;
15
+ if (url.protocol === "https:")
16
+ return true;
17
+ return url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
18
+ }
19
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/internal/origin.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,4GAA4G;AAC5G,2GAA2G;AAC3G,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAEzF;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,8 @@
1
+ /** True for `{}`/`Object.create(null)` shaped values; rejects arrays, class instances, `null`, primitives. */
2
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
3
+ /**
4
+ * Closed-shape check: every own key must be in `required` or `optional`, and every `required` key must be
5
+ * present. This is what "unknown field" rejection and "field may be omitted" rest on throughout this package.
6
+ */
7
+ export declare function hasKeysWithin(value: Record<string, unknown>, required: readonly string[], optional?: readonly string[]): boolean;
8
+ //# sourceMappingURL=plain-object.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-object.d.ts","sourceRoot":"","sources":["../../src/internal/plain-object.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,GAAE,SAAS,MAAM,EAAO,GAC/B,OAAO,CAGT"}
@@ -0,0 +1,16 @@
1
+ /** True for `{}`/`Object.create(null)` shaped values; rejects arrays, class instances, `null`, primitives. */
2
+ export function isPlainObject(value) {
3
+ if (typeof value !== "object" || value === null)
4
+ return false;
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ }
8
+ /**
9
+ * Closed-shape check: every own key must be in `required` or `optional`, and every `required` key must be
10
+ * present. This is what "unknown field" rejection and "field may be omitted" rest on throughout this package.
11
+ */
12
+ export function hasKeysWithin(value, required, optional = []) {
13
+ const allowed = new Set([...required, ...optional]);
14
+ return Object.keys(value).every((key) => allowed.has(key)) && required.every((key) => Object.hasOwn(value, key));
15
+ }
16
+ //# sourceMappingURL=plain-object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-object.js","sourceRoot":"","sources":["../../src/internal/plain-object.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAY,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACxD,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,KAA8B,EAC9B,QAA2B,EAC3B,WAA8B,EAAE;IAEhC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC5D,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AACnH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function themeStorageKey(appId: string, environment: string): string;
2
+ //# sourceMappingURL=theme-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-key.d.ts","sourceRoot":"","sources":["../../src/internal/theme-key.ts"],"names":[],"mappings":"AASA,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE1E"}
@@ -0,0 +1,13 @@
1
+ // The `localStorage` key that carries the app's theme preference — spec/pwa-entry-resilience.md's "跟随宿主应用的
2
+ // 主题设置": "公开契约是这个键,不是 setPwaTheme 这个函数". Shared by src/client/index.ts (`setPwaTheme` writes it)
3
+ // and src/page/main.ts (the recovery page reads it before its first render), which must agree on the same key for
4
+ // one app/environment — kept here, rather than duplicated in both, so that agreement cannot drift.
5
+ //
6
+ // Same segment-encoding as src/browser/indexeddb.ts's database name (`pwa-entry:<appId>:<environment>`), but under
7
+ // the unrelated `pwa:theme:` prefix the spec pins character for character, since this is a different storage
8
+ // mechanism (`localStorage`, not IndexedDB) that the recovery page must be able to read without depending on any
9
+ // other part of this package's browser adapters.
10
+ export function themeStorageKey(appId, environment) {
11
+ return `pwa:theme:${encodeURIComponent(appId)}:${encodeURIComponent(environment)}`;
12
+ }
13
+ //# sourceMappingURL=theme-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-key.js","sourceRoot":"","sources":["../../src/internal/theme-key.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,iGAAiG;AACjG,kHAAkH;AAClH,mGAAmG;AACnG,EAAE;AACF,mHAAmH;AACnH,6GAA6G;AAC7G,iHAAiH;AACjH,iDAAiD;AACjD,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,WAAmB;IAChE,OAAO,aAAa,kBAAkB,CAAC,KAAK,CAAC,IAAI,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;AACrF,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { EntryDiagnostic } from "./diagnostics.js";
2
+ import type { EntryManifest, EntryManifestValidationContext } from "./types.js";
3
+ export type ManifestResult = {
4
+ readonly ok: true;
5
+ readonly manifest: EntryManifest;
6
+ } | {
7
+ readonly ok: false;
8
+ readonly diagnostics: readonly EntryDiagnostic[];
9
+ };
10
+ /** Validates the manifest object an application hands in. Whole-manifest rejection: never partially accepted.
11
+ * Never throws. */
12
+ export declare function parseEntryManifest(value: unknown, context: EntryManifestValidationContext): ManifestResult;
13
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAKxD,OAAO,KAAK,EAAE,aAAa,EAAuB,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAuBrG,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAA;CAAE,CAAC;AAE7E;oBACoB;AACpB,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,8BAA8B,GAAG,cAAc,CAa1G"}
@@ -0,0 +1,153 @@
1
+ // Validates the plain manifest object the application hands in to `updateEntryManifest` — see
2
+ // spec/pwa-entry-resilience.md's "修订:入口清单由业务应用提供" -> "契约增量". ADR-0033 (2026-09-23) removed the
3
+ // package's cryptographic trust root, so there is no more envelope or signature stage before this runs: this
4
+ // function is now the whole of manifest validation, not (as before that revision) only the part that ran after a
5
+ // signature had already verified.
6
+ import { diagnostic } from "./diagnostics.js";
7
+ import { isNormalizedOrigin } from "./internal/origin.js";
8
+ import { parseStrictIsoUtc } from "./internal/iso-date.js";
9
+ import { hasKeysWithin, isPlainObject } from "./internal/plain-object.js";
10
+ import { ENTRY_MANIFEST_REASON_CODES, ENTRY_MANIFEST_STATUSES } from "./types.js";
11
+ const MANIFEST_REQUIRED_FIELDS = ["sequence", "expiresAt", "status", "reason", "entries"];
12
+ const MANIFEST_OPTIONAL_FIELDS = ["appId", "environment"];
13
+ const REASON_REQUIRED_FIELDS = ["code"];
14
+ const REASON_OPTIONAL_FIELDS = ["message"];
15
+ const ENTRY_FIELDS = ["origin", "startPath"];
16
+ const MAX_ENTRIES = 5;
17
+ const MAX_REASON_MESSAGE_LENGTH = 200;
18
+ const MAX_START_PATH_LENGTH = 512;
19
+ const DAY_MS = 24 * 60 * 60 * 1000;
20
+ /** True if `value` contains a C0 control character (U+0000-U+001F) or DEL (U+007F). Avoids a control-character
21
+ * regex literal, which most lint configs (including this repo's) flag as suspicious. */
22
+ function hasControlCharacter(value) {
23
+ for (let index = 0; index < value.length; index += 1) {
24
+ const code = value.charCodeAt(index);
25
+ if (code <= 0x1f || code === 0x7f)
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+ /** Validates the manifest object an application hands in. Whole-manifest rejection: never partially accepted.
31
+ * Never throws. */
32
+ export function parseEntryManifest(value, context) {
33
+ if (!isPlainObject(value) || !hasKeysWithin(value, MANIFEST_REQUIRED_FIELDS, MANIFEST_OPTIONAL_FIELDS)) {
34
+ return { ok: false, diagnostics: [diagnostic("entry.manifest-invalid-shape", "")] };
35
+ }
36
+ const diagnostics = [
37
+ ...topLevelDiagnostics(value, context),
38
+ ...reasonDiagnostics(value["reason"]),
39
+ ...entriesDiagnostics(value["entries"]),
40
+ ];
41
+ if (diagnostics.length > 0)
42
+ return { ok: false, diagnostics };
43
+ return { ok: true, manifest: value };
44
+ }
45
+ function topLevelDiagnostics(value, context) {
46
+ const diagnostics = [];
47
+ if ("appId" in value && value["appId"] !== context.appId) {
48
+ diagnostics.push(diagnostic("entry.app-id-mismatch", "/appId"));
49
+ }
50
+ if ("environment" in value && value["environment"] !== context.environment) {
51
+ diagnostics.push(diagnostic("entry.environment-mismatch", "/environment"));
52
+ }
53
+ const sequence = value["sequence"];
54
+ if (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence < 0) {
55
+ diagnostics.push(diagnostic("entry.sequence-invalid", "/sequence"));
56
+ }
57
+ const expiresAt = value["expiresAt"];
58
+ const expiresAtMs = typeof expiresAt === "string" ? parseStrictIsoUtc(expiresAt) : undefined;
59
+ if (expiresAtMs === undefined) {
60
+ diagnostics.push(diagnostic("entry.expires-at-invalid", "/expiresAt"));
61
+ }
62
+ else if (context.now >= expiresAtMs) {
63
+ // Must be in the future at the moment it is handed in (spec: "过期后不展示" applies to the write path too —
64
+ // an already-expired manifest could otherwise never be displayed, making acceptance pointless).
65
+ diagnostics.push(diagnostic("entry.expired", "/expiresAt"));
66
+ }
67
+ else if (expiresAtMs - context.now > context.maxValidityDays * DAY_MS) {
68
+ diagnostics.push(diagnostic("entry.validity-period-too-long", "/expiresAt"));
69
+ }
70
+ if (!isEntryManifestStatus(value["status"]))
71
+ diagnostics.push(diagnostic("entry.status-invalid", "/status"));
72
+ return diagnostics;
73
+ }
74
+ function isEntryManifestStatus(value) {
75
+ return typeof value === "string" && ENTRY_MANIFEST_STATUSES.includes(value);
76
+ }
77
+ function reasonDiagnostics(value) {
78
+ if (!isPlainObject(value) || !hasKeysWithin(value, REASON_REQUIRED_FIELDS, REASON_OPTIONAL_FIELDS)) {
79
+ return [diagnostic("entry.reason-invalid-shape", "/reason")];
80
+ }
81
+ const diagnostics = [];
82
+ const code = value["code"];
83
+ if (typeof code !== "string" || !ENTRY_MANIFEST_REASON_CODES.includes(code)) {
84
+ diagnostics.push(diagnostic("entry.reason-code-invalid", "/reason/code"));
85
+ }
86
+ if ("message" in value) {
87
+ const message = value["message"];
88
+ if (typeof message !== "string" || message.length > MAX_REASON_MESSAGE_LENGTH || hasControlCharacter(message)) {
89
+ diagnostics.push(diagnostic("entry.reason-message-invalid", "/reason/message"));
90
+ }
91
+ }
92
+ return diagnostics;
93
+ }
94
+ function entriesDiagnostics(value) {
95
+ if (!Array.isArray(value))
96
+ return [diagnostic("entry.entry-invalid-shape", "/entries")];
97
+ const diagnostics = [];
98
+ if (value.length > MAX_ENTRIES)
99
+ diagnostics.push(diagnostic("entry.entries-too-many", "/entries"));
100
+ value.forEach((entry, index) => diagnostics.push(...entryDiagnostics(entry, index)));
101
+ return diagnostics;
102
+ }
103
+ function entryDiagnostics(value, index) {
104
+ if (!isPlainObject(value) || !hasKeysWithin(value, ENTRY_FIELDS)) {
105
+ return [diagnostic("entry.entry-invalid-shape", `/entries/${index}`)];
106
+ }
107
+ const diagnostics = [];
108
+ const origin = value["origin"];
109
+ if (typeof origin !== "string" || !isNormalizedOrigin(origin)) {
110
+ diagnostics.push(diagnostic("entry.entry-origin-invalid", `/entries/${index}/origin`));
111
+ }
112
+ const startPath = value["startPath"];
113
+ if (typeof startPath !== "string" || !isValidStartPath(startPath)) {
114
+ diagnostics.push(diagnostic("entry.entry-start-path-invalid", `/entries/${index}/startPath`));
115
+ }
116
+ return diagnostics;
117
+ }
118
+ /** True if any `/`-delimited segment of `value` is exactly `..`. */
119
+ function hasDotDotSegment(value) {
120
+ return value.split("/").some((segment) => segment === "..");
121
+ }
122
+ /**
123
+ * `startPath`'s rules, checked on the raw string and again on its single percent-decoding — the same "raw, then
124
+ * decoded" shape src/return-path.ts uses for `?return=`, so a percent-encoded `..`, `\`, `//` or control character
125
+ * cannot slip past the raw-string checks alone. A percent-encoding that fails to decode is rejected outright.
126
+ */
127
+ function isValidStartPath(value) {
128
+ if (value.length === 0 || value.length > MAX_START_PATH_LENGTH)
129
+ return false;
130
+ if (!value.startsWith("/"))
131
+ return false;
132
+ if (value.includes("\\") || value.includes("//"))
133
+ return false;
134
+ if (hasControlCharacter(value))
135
+ return false;
136
+ if (hasDotDotSegment(value))
137
+ return false;
138
+ let decoded;
139
+ try {
140
+ decoded = decodeURIComponent(value);
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ if (decoded.includes("\\") || decoded.includes("//"))
146
+ return false;
147
+ if (hasControlCharacter(decoded))
148
+ return false;
149
+ if (hasDotDotSegment(decoded))
150
+ return false;
151
+ return true;
152
+ }
153
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/page/main.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ // The platform's entry-recovery page, published by the Vite plugin at `<mountPath>pwa-entry.html` (see
2
+ // src/vite/index.ts). Independently re-runs the full selection and decision (src/resolve.ts) rather than trusting
3
+ // anything in the URL besides the return path — see spec/pwa-entry-resilience.md's "入口恢复页". Confirmation and
4
+ // navigation happen only here, per docs/adr/0018-entry-resilience-delivery-boundary.md's
5
+ // "确认与导航只在平台恢复页上发生": `navigate` calls the top-level-navigation assignment method on `location`, only
6
+ // ever from a button's click handler, never automatically. Opening a new tab/window and the two other
7
+ // full-address-navigation forms this module must not use are enumerated, and checked for by name, in
8
+ // test/page/source-scan.test.ts — deliberately not spelled out here, so this comment cannot itself trip that scan.
9
+ import config from "virtual:pwa-entry-config";
10
+ import { createEntryRuntimePorts } from "../browser/index.js";
11
+ import { themeStorageKey } from "../internal/theme-key.js";
12
+ import { resolveEntryRecovery } from "../resolve.js";
13
+ import { normalizeReturnPath } from "../return-path.js";
14
+ import { buildPageModel } from "./model.js";
15
+ import { renderPage } from "./render.js";
16
+ /** Maps each adapted `PageElement` back to the native node `adaptedElement` wrapped, so `append` can unwrap its
17
+ * children without widening `PageElement`'s own type to expose the native node. */
18
+ const WRAPPED = new WeakMap();
19
+ function unwrap(child) {
20
+ const native = WRAPPED.get(child);
21
+ if (native === undefined)
22
+ throw new Error("append: child was not created by this adapter's PageDocument");
23
+ return native;
24
+ }
25
+ /**
26
+ * Adapts a real DOM element to `PageElement`. `render.ts`'s `append(...children: PageElement[])` cannot accept a
27
+ * real `HTMLElement` directly — the DOM's own `append` takes `(string | Node)[]`, which `PageElement` is not — so
28
+ * this keeps a reference to the wrapped node (via `WRAPPED`) and unwraps children back to it before delegating.
29
+ */
30
+ function adaptedElement(native) {
31
+ const element = {
32
+ get textContent() {
33
+ return native.textContent;
34
+ },
35
+ set textContent(value) {
36
+ native.textContent = value;
37
+ },
38
+ append(...children) {
39
+ native.append(...children.map(unwrap));
40
+ },
41
+ setAttribute(name, value) {
42
+ native.setAttribute(name, value);
43
+ },
44
+ addEventListener(type, listener) {
45
+ native.addEventListener(type, listener);
46
+ },
47
+ };
48
+ WRAPPED.set(element, native);
49
+ return element;
50
+ }
51
+ const pageDocument = { createElement: (tag) => adaptedElement(document.createElement(tag)) };
52
+ /**
53
+ * Reads the stored theme preference `setPwaTheme` (src/client/index.ts) writes, so it can be applied before the
54
+ * first render — see spec/pwa-entry-resilience.md's "恢复页在渲染前读取该键". Never throws: a missing key, a value
55
+ * other than exactly `"light"`/`"dark"`, or `localStorage` itself being unavailable (privacy mode, disabled
56
+ * storage) all fall back to `undefined`, which leaves the page following the system preference.
57
+ */
58
+ function readStoredTheme() {
59
+ try {
60
+ const value = localStorage.getItem(themeStorageKey(config.appId, config.environment));
61
+ return value === "light" || value === "dark" ? value : undefined;
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ }
67
+ const rootNative = document.getElementById("pwa-entry");
68
+ if (rootNative !== null) {
69
+ // Set before the first render (below), directly on the native node: `renderPage` only ever manages the `class`
70
+ // attribute (src/page/render.ts), never `data-theme`, so this cannot be clobbered by a later render call either.
71
+ const theme = readStoredTheme();
72
+ if (theme !== undefined)
73
+ rootNative.setAttribute("data-theme", theme);
74
+ const root = adaptedElement(rootNative);
75
+ const navigate = (href) => {
76
+ location.assign(href);
77
+ };
78
+ renderPage(root, "loading", pageDocument, navigate, config.messages);
79
+ const returnPath = normalizeReturnPath(new URLSearchParams(location.search).get("return"), {
80
+ origin: location.origin,
81
+ scope: config.scope,
82
+ });
83
+ // resolveEntryRecovery never rejects, but assembling the ports runs synchronously and could throw. Either way the
84
+ // page must not stay on "checking" forever: any failure renders "no entry", which offers nothing to click.
85
+ const resolution = (async () => resolveEntryRecovery(config, createEntryRuntimePorts(config)))();
86
+ void resolution.then((resolved) => renderPage(root, buildPageModel(resolved, returnPath), pageDocument, navigate, config.messages), () => renderPage(root, { kind: "none" }, pageDocument, navigate, config.messages));
87
+ }
88
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/page/main.ts"],"names":[],"mappings":"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"}