@tileflow/react-native 0.0.0-bootstrap.0 → 0.1.0-alpha.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 (86) hide show
  1. package/README.md +216 -4
  2. package/TileflowNativeAdmission.podspec +34 -0
  3. package/android/build.gradle +43 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrap.kt +144 -0
  6. package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrapOkHttpNetwork.kt +89 -0
  7. package/android/src/main/java/dev/tileflow/reactnative/AdmissionCatalog.kt +137 -0
  8. package/android/src/main/java/dev/tileflow/reactnative/AdmissionDocumentScope.kt +22 -0
  9. package/android/src/main/java/dev/tileflow/reactnative/AdmissionEngine.kt +335 -0
  10. package/android/src/main/java/dev/tileflow/reactnative/AdmissionModuleProvider.kt +68 -0
  11. package/android/src/main/java/dev/tileflow/reactnative/AdmissionOkHttpNetwork.kt +86 -0
  12. package/android/src/main/java/dev/tileflow/reactnative/AdmissionProviderLease.kt +56 -0
  13. package/android/src/main/java/dev/tileflow/reactnative/AdmissionTypes.kt +85 -0
  14. package/android/src/main/java/dev/tileflow/reactnative/AdmissionUrl.kt +100 -0
  15. package/android/src/main/java/dev/tileflow/reactnative/HandlerAdmissionScheduler.kt +16 -0
  16. package/android/src/main/java/dev/tileflow/reactnative/MobileConfiguration.kt +95 -0
  17. package/android/src/main/java/dev/tileflow/reactnative/MobileConfigurationResources.kt +43 -0
  18. package/android/src/main/java/dev/tileflow/reactnative/NativeDocumentOkHttpNetwork.kt +101 -0
  19. package/android/src/main/java/dev/tileflow/reactnative/NativeDocumentRegistry.kt +136 -0
  20. package/android/src/main/java/dev/tileflow/reactnative/NativeSurfaceState.kt +115 -0
  21. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeAdmissionModule.kt +234 -0
  22. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeAdmissionPackage.kt +16 -0
  23. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeConfigurationModule.kt +43 -0
  24. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeDocumentsModule.kt +80 -0
  25. package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeSurfaceModule.kt +277 -0
  26. package/android/src/main/res/raw/tileflow_configuration_keep.xml +3 -0
  27. package/dist/contract-DWs-lDPo.d.ts +144 -0
  28. package/dist/index.d.ts +13 -0
  29. package/dist/index.js +6989 -0
  30. package/dist/internal/camera.d.ts +65 -0
  31. package/dist/internal/camera.js +325 -0
  32. package/dist/internal/hosted-binding.d.ts +18 -0
  33. package/dist/internal/hosted-binding.js +277 -0
  34. package/dist/internal/initial-view.d.ts +11 -0
  35. package/dist/internal/initial-view.js +8 -0
  36. package/dist/internal/interactions.d.ts +76 -0
  37. package/dist/internal/interactions.js +794 -0
  38. package/dist/internal/native-admission-bridge.d.ts +29 -0
  39. package/dist/internal/native-admission-bridge.js +351 -0
  40. package/dist/internal/native-admission.d.ts +78 -0
  41. package/dist/internal/native-admission.js +1614 -0
  42. package/dist/internal/native-appearance.d.ts +21 -0
  43. package/dist/internal/native-appearance.js +110 -0
  44. package/dist/internal/native-configuration-bridge.d.ts +9 -0
  45. package/dist/internal/native-configuration-bridge.js +343 -0
  46. package/dist/internal/session.d.ts +125 -0
  47. package/dist/internal/session.js +887 -0
  48. package/dist/internal/source-state.d.ts +11 -0
  49. package/dist/internal/source-state.js +26 -0
  50. package/dist/native-admission-contract-k7LKE1vW.d.ts +89 -0
  51. package/docs/native-admission.md +108 -0
  52. package/docs/native-configuration.md +210 -0
  53. package/docs/native-interactions.md +161 -0
  54. package/docs/native-location.md +148 -0
  55. package/ios/TFAdmissionBootstrap.h +17 -0
  56. package/ios/TFAdmissionBootstrap.mm +168 -0
  57. package/ios/TFAdmissionCatalog.h +13 -0
  58. package/ios/TFAdmissionCatalog.mm +193 -0
  59. package/ios/TFAdmissionDocumentScope.h +7 -0
  60. package/ios/TFAdmissionDocumentScope.mm +45 -0
  61. package/ios/TFAdmissionEngine.h +57 -0
  62. package/ios/TFAdmissionEngine.mm +371 -0
  63. package/ios/TFAdmissionInstallation.h +27 -0
  64. package/ios/TFAdmissionInstallation.mm +117 -0
  65. package/ios/TFAdmissionNetwork.h +16 -0
  66. package/ios/TFAdmissionNetwork.mm +268 -0
  67. package/ios/TFAdmissionURL.mm +116 -0
  68. package/ios/TFDocumentBounds.h +9 -0
  69. package/ios/TFDocumentBounds.mm +19 -0
  70. package/ios/TFMobileConfiguration.h +23 -0
  71. package/ios/TFMobileConfiguration.mm +139 -0
  72. package/ios/TFNativeAdmissionURLProtocol.mm +69 -0
  73. package/ios/TFNativeDocumentRegistry.h +28 -0
  74. package/ios/TFNativeDocumentRegistry.mm +193 -0
  75. package/ios/TFNativeDocumentURL.h +6 -0
  76. package/ios/TFNativeSurfaceState.h +28 -0
  77. package/ios/TFNativeSurfaceState.mm +128 -0
  78. package/ios/TileflowNativeAdmission.h +9 -0
  79. package/ios/TileflowNativeAdmission.mm +184 -0
  80. package/ios/TileflowNativeConfiguration.mm +41 -0
  81. package/ios/TileflowNativeDocuments.h +12 -0
  82. package/ios/TileflowNativeDocuments.mm +105 -0
  83. package/ios/TileflowNativeSurface.mm +403 -0
  84. package/ios/tileflow_native_admission.rb +45 -0
  85. package/package.json +51 -7
  86. package/react-native.config.cjs +13 -0
@@ -0,0 +1,1614 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __typeError = (msg) => {
3
+ throw TypeError(msg);
4
+ };
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
8
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
9
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
10
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
11
+
12
+ // src/native-admission-url.ts
13
+ import {
14
+ resolveTileflowNativeManifestUrl as resolveTileflowNativeManifestUrl2,
15
+ resolveTileflowNativeResourceUrl
16
+ } from "@tileflow/core/native";
17
+
18
+ // src/native-admission-contract.ts
19
+ var nativeAdmissionLimits = Object.freeze({
20
+ contexts: 16,
21
+ resources: 128,
22
+ queueDepth: 128,
23
+ batchSize: 8,
24
+ urlCharacters: 2048,
25
+ bridgeBytes: 524288,
26
+ grantCharacters: 24576,
27
+ waitMs: 3e4,
28
+ validitySafetyMs: 1e3,
29
+ redirects: 3,
30
+ responseBytes: 8388608
31
+ });
32
+ var nativeContextParameter = "__tf_native_context";
33
+
34
+ // src/native-resource-template.ts
35
+ import { resolveTileflowNativeManifestUrl } from "@tileflow/core/native";
36
+ var decimal = "(?:0|[1-9][0-9]{0,9})";
37
+ var coordinate = "-?(?:0|[1-9][0-9]{0,7})(?:\\.[0-9]{1,16})?";
38
+ var slots = Object.freeze({
39
+ z: "(?:0|[1-9][0-9]?)",
40
+ x: decimal,
41
+ y: decimal,
42
+ ratio: "(?:@2x|@3x)?",
43
+ quadkey: "[0-3]{0,30}",
44
+ prefix: "[0-9a-f]{2}",
45
+ "bbox-epsg-3857": `${coordinate},${coordinate},${coordinate},${coordinate}`,
46
+ fontstack: "(?:[A-Za-z0-9_.~,+-]|%[0-9a-fA-F]{2}){1,768}",
47
+ range: `${decimal}-${decimal}`
48
+ });
49
+ var escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
50
+ var invalid = () => new Error("Invalid native resource template");
51
+ var compiled = /* @__PURE__ */ new WeakMap();
52
+ function compile(resource) {
53
+ const names = [];
54
+ let cursor = 0;
55
+ let pattern = "^";
56
+ for (const match of resource.url.matchAll(/\{([a-z0-9-]+)\}/gu)) {
57
+ const name = match[1];
58
+ if (!Object.hasOwn(slots, name) || names.length >= 16 || names.length > 0 && match.index === cursor) {
59
+ throw invalid();
60
+ }
61
+ if (resource.template === "glyphs" ? !["fontstack", "range"].includes(name) : ["fontstack", "range"].includes(name)) {
62
+ throw invalid();
63
+ }
64
+ pattern += escape(resource.url.slice(cursor, match.index)) + `(${slots[name]})`;
65
+ cursor = match.index + match[0].length;
66
+ names.push(name);
67
+ }
68
+ if (!names.length || /[{}]/u.test(resource.url.replace(/\{[a-z0-9-]+\}/gu, ""))) throw invalid();
69
+ pattern += escape(resource.url.slice(cursor)) + "$";
70
+ return { pattern: new RegExp(pattern, "u"), names: Object.freeze(names) };
71
+ }
72
+ function normalizeNativeTemplate(resource) {
73
+ if (resource.template === void 0) {
74
+ if (resource.fontStacks !== void 0 || /[{}]/u.test(resource.url)) throw invalid();
75
+ return {};
76
+ }
77
+ if (resource.template !== "tile" && resource.template !== "glyphs") throw invalid();
78
+ if (resource.template === "tile") {
79
+ if (resource.scope !== "tile" || resource.fontStacks !== void 0) throw invalid();
80
+ compile(resource);
81
+ return { template: "tile" };
82
+ }
83
+ const stacks = resource.fontStacks;
84
+ if (resource.scope !== "glyph" || !Array.isArray(stacks) || stacks.length === 0 || stacks.length > 16 || !resource.url.includes("{fontstack}") || !resource.url.includes("{range}") || stacks.some(
85
+ (value) => typeof value !== "string" || value.length === 0 || value.length > 256 || value !== value.trim() || /[\p{Cc}\\/?#%&=]|tf_native_|tf_public_/iu.test(value) || value.split(",").some((part) => part.length === 0 || part !== part.trim())
86
+ ) || new Set(stacks).size !== stacks.length)
87
+ throw invalid();
88
+ compile(resource);
89
+ return { template: "glyphs", fontStacks: Object.freeze([...stacks]) };
90
+ }
91
+ function expansionMatches(resource, url) {
92
+ if (resource.template === void 0) return resource.url === url;
93
+ let grammar = compiled.get(resource);
94
+ if (!grammar) {
95
+ grammar = compile(resource);
96
+ compiled.set(resource, grammar);
97
+ }
98
+ const match = grammar.pattern.exec(url);
99
+ if (!match) return false;
100
+ const values = /* @__PURE__ */ new Map();
101
+ for (let index = 0; index < grammar.names.length; index++) {
102
+ const name = grammar.names[index];
103
+ const value = match[index + 1];
104
+ if (values.has(name) && values.get(name) !== value) return false;
105
+ values.set(name, value);
106
+ }
107
+ const z = values.get("z");
108
+ if (z !== void 0 && Number(z) > 30) return false;
109
+ for (const name of ["x", "y"]) {
110
+ const value = values.get(name);
111
+ if (value !== void 0 && Number(value) >= 2 ** (z === void 0 ? 30 : Number(z)))
112
+ return false;
113
+ }
114
+ const quadkey = values.get("quadkey");
115
+ if (quadkey !== void 0 && z !== void 0 && quadkey.length !== Number(z)) return false;
116
+ const box = values.get("bbox-epsg-3857");
117
+ if (box !== void 0) {
118
+ const [west, south, east, north] = box.split(",").map(Number);
119
+ if ([west, south, east, north].some(
120
+ (value) => !Number.isFinite(value) || Math.abs(value) > 20037508343e-3
121
+ ) || west >= east || south >= north)
122
+ return false;
123
+ }
124
+ const range = values.get("range");
125
+ if (range !== void 0) {
126
+ const [start, end] = range.split("-").map(Number);
127
+ if (start % 256 !== 0 || end !== start + 255 || end > 1114111) return false;
128
+ }
129
+ const stack = values.get("fontstack");
130
+ if (stack !== void 0) {
131
+ try {
132
+ if (!resource.fontStacks?.includes(decodeURIComponent(stack))) return false;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+ return true;
138
+ }
139
+ function matchNativeResource(resources, url) {
140
+ try {
141
+ if (url.length > 2048 || resolveTileflowNativeManifestUrl(url) !== url || /[{}]/u.test(url))
142
+ return void 0;
143
+ } catch {
144
+ return void 0;
145
+ }
146
+ let found;
147
+ for (const resource of resources) {
148
+ if (!expansionMatches(resource, url)) continue;
149
+ if (found) throw invalid();
150
+ found = Object.freeze({
151
+ url,
152
+ scope: resource.scope,
153
+ ...resource.tilesetId === void 0 ? {} : { tilesetId: resource.tilesetId }
154
+ });
155
+ }
156
+ return found;
157
+ }
158
+
159
+ // src/native-admission-url.ts
160
+ var scopes = /* @__PURE__ */ new Set(["style", "tilejson", "tile", "sprite", "glyph", "font"]);
161
+ var isNativeToken = (value) => typeof value === "string" && /^[A-Za-z0-9_.-]{1,96}$/u.test(value) && !value.startsWith("tf_");
162
+ function hasReservedNativeContext(url) {
163
+ const query = url.indexOf("?");
164
+ if (query < 0) return false;
165
+ return url.slice(query + 1).split("&").some((part) => {
166
+ const key = part.split("=", 1)[0].replace(
167
+ /%([0-9a-f]{2})/giu,
168
+ (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))
169
+ );
170
+ return key.toLowerCase().startsWith("__tf_native");
171
+ });
172
+ }
173
+ function nativeResourceOrigin(url) {
174
+ const match = /^(https:\/\/[^/?#]+)\//u.exec(url);
175
+ if (!match) throw new Error("Invalid native resource");
176
+ return match[1];
177
+ }
178
+ function nativeVersionedStyleMatchesMap(url, mapId) {
179
+ try {
180
+ const path = url.slice(nativeResourceOrigin(url).length);
181
+ const match = /^\/maps\/(map_[A-Za-z0-9_-]{16})\/native\/v([1-9][0-9]{0,15})\/([A-Za-z][A-Za-z0-9_-]{0,63})\.json$/u.exec(
182
+ path
183
+ );
184
+ return Boolean(match && match[1] === mapId && Number.isSafeInteger(Number(match[2])));
185
+ } catch {
186
+ return false;
187
+ }
188
+ }
189
+ function styleScopeAllowsMap(url, mapId) {
190
+ try {
191
+ const path = url.slice(nativeResourceOrigin(url).length);
192
+ return !/^\/maps\/[^/?#]+\/native(?:[/?#]|$)/u.test(decodeURIComponent(path)) || nativeVersionedStyleMatchesMap(url, mapId);
193
+ } catch {
194
+ return false;
195
+ }
196
+ }
197
+ function normalizeNativeResources(input) {
198
+ if (!Array.isArray(input) || input.length > nativeAdmissionLimits.resources)
199
+ throw new Error("Invalid native resource catalog");
200
+ const seen = /* @__PURE__ */ new Set();
201
+ return Object.freeze(
202
+ input.map((resource) => {
203
+ if (!resource || typeof resource.url !== "string" || resource.url.length > nativeAdmissionLimits.urlCharacters - 128 || !scopes.has(resource.scope) || hasReservedNativeContext(resource.url) || /[^\x21-\x7e]|[\\#]/u.test(resource.url) || seen.has(resource.url))
204
+ throw new Error("Invalid native resource");
205
+ const origin = nativeResourceOrigin(resource.url);
206
+ const canonical = resource.template === void 0 ? resolveTileflowNativeManifestUrl2(resource.url) : resolveTileflowNativeResourceUrl(resource.url, {
207
+ documentUrl: `${origin}/`,
208
+ template: resource.template
209
+ });
210
+ if (canonical !== resource.url) throw new Error("Invalid native resource");
211
+ const decoded = decodeURIComponent(resource.url);
212
+ if (/tf_native_|tf_public_/iu.test(decoded)) throw new Error("Invalid native resource");
213
+ if ((resource.scope === "tile" || resource.scope === "tilejson") && resource.tilesetId === void 0)
214
+ throw new Error("Missing native tileset");
215
+ if (resource.tilesetId !== void 0 && (!/^[A-Za-z0-9._:-]{1,255}$/u.test(resource.tilesetId) || /tf_native_|tf_public_/iu.test(resource.tilesetId)))
216
+ throw new Error("Invalid native tileset");
217
+ const template = normalizeNativeTemplate(resource);
218
+ seen.add(resource.url);
219
+ return Object.freeze({
220
+ url: resource.url,
221
+ scope: resource.scope,
222
+ ...resource.tilesetId === void 0 ? {} : { tilesetId: resource.tilesetId },
223
+ ...template
224
+ });
225
+ })
226
+ );
227
+ }
228
+ function authorityAllowsResource(authority, resource, mapId) {
229
+ return authority.mapId === mapId && authority.resourceOrigins.includes(nativeResourceOrigin(resource.url)) && authority.resourceScopes.includes(resource.scope) && (resource.scope !== "style" || styleScopeAllowsMap(resource.url, mapId)) && (resource.tilesetId === void 0 || authority.tilesetIds.includes(resource.tilesetId));
230
+ }
231
+ function discriminateNativeResourceForTest(url, context) {
232
+ if (!isNativeToken(context) || hasReservedNativeContext(url))
233
+ throw new Error("Invalid native context");
234
+ const result = `${url}${url.includes("?") ? "&" : "?"}${nativeContextParameter}=${context}`;
235
+ if (result.length > nativeAdmissionLimits.urlCharacters)
236
+ throw new Error("Invalid native context");
237
+ return result;
238
+ }
239
+
240
+ // src/native-admission-catalog.ts
241
+ function mergeGlyphResource(previous, next) {
242
+ if (previous.scope !== "glyph" || next.scope !== "glyph" || previous.template !== "glyphs" || next.template !== "glyphs" || previous.tilesetId !== next.tilesetId || !previous.fontStacks || !next.fontStacks)
243
+ return void 0;
244
+ const fontStacks = [.../* @__PURE__ */ new Set([...previous.fontStacks, ...next.fontStacks])].sort();
245
+ if (fontStacks.length > 16) return void 0;
246
+ return normalizeNativeResources([{ ...previous, fontStacks }])[0];
247
+ }
248
+ function createNativeAdmissionCatalog(initial, context, extendNative) {
249
+ let resources = normalizeNativeResources(initial);
250
+ let live = true;
251
+ let pending;
252
+ const invalid2 = () => new NativeAdmissionError("NATIVE_ADMISSION_INVALID");
253
+ const cancelled = () => new NativeAdmissionError("NATIVE_ADMISSION_CANCELLED");
254
+ return Object.freeze({
255
+ match(url) {
256
+ if (!live) return void 0;
257
+ return matchNativeResource(resources, url);
258
+ },
259
+ discriminate(url) {
260
+ try {
261
+ if (!live || !resources.some((item) => item.url === url) && !matchNativeResource(resources, url))
262
+ throw invalid2();
263
+ return discriminateNativeResourceForTest(url, context);
264
+ } catch {
265
+ throw invalid2();
266
+ }
267
+ },
268
+ extend(input) {
269
+ if (!live) return Promise.reject(cancelled());
270
+ if (pending) return Promise.reject(new NativeAdmissionError("NATIVE_ADMISSION_UNAVAILABLE"));
271
+ let next;
272
+ let additions;
273
+ try {
274
+ const requested = normalizeNativeResources(input);
275
+ const merged = new Map(resources.map((entry) => [entry.url, entry]));
276
+ const nativeUpdates = [];
277
+ for (const entry of requested) {
278
+ const previous = merged.get(entry.url);
279
+ if (!previous) {
280
+ merged.set(entry.url, entry);
281
+ nativeUpdates.push(entry);
282
+ continue;
283
+ }
284
+ if (JSON.stringify(previous) === JSON.stringify(entry)) continue;
285
+ const union = mergeGlyphResource(previous, entry);
286
+ if (!union) throw invalid2();
287
+ merged.set(entry.url, union);
288
+ nativeUpdates.push(union);
289
+ }
290
+ next = normalizeNativeResources([...merged.values()]);
291
+ additions = Object.freeze(nativeUpdates);
292
+ } catch {
293
+ return Promise.reject(invalid2());
294
+ }
295
+ if (additions.length === 0) return Promise.resolve(Object.freeze({ resources: next.length }));
296
+ let resolve;
297
+ let reject;
298
+ const result = new Promise((yes, no) => {
299
+ resolve = yes;
300
+ reject = no;
301
+ });
302
+ void result.catch(() => void 0);
303
+ const operation = { cancel: () => reject(cancelled()) };
304
+ pending = operation;
305
+ void Promise.resolve().then(() => {
306
+ if (!live || pending !== operation) throw cancelled();
307
+ return extendNative(additions);
308
+ }).then((ack) => {
309
+ if (!live || pending !== operation) return;
310
+ if (!ack || ack.resources !== next.length) throw invalid2();
311
+ resources = next;
312
+ pending = void 0;
313
+ resolve(Object.freeze({ resources: resources.length }));
314
+ }).catch(() => {
315
+ if (!live || pending !== operation) return;
316
+ pending = void 0;
317
+ reject(new NativeAdmissionError("NATIVE_ADMISSION_UNAVAILABLE"));
318
+ });
319
+ return result;
320
+ },
321
+ retire() {
322
+ if (!live) return;
323
+ live = false;
324
+ pending?.cancel();
325
+ pending = void 0;
326
+ resources = Object.freeze([]);
327
+ }
328
+ });
329
+ }
330
+
331
+ // src/session-controller.ts
332
+ import { resolveTileflowNativeManifestUrl as resolveTileflowNativeManifestUrl3 } from "@tileflow/core/native";
333
+ var RESPONSE_BYTE_LIMIT = 65536;
334
+ var GRANT_CHARACTER_LIMIT = 24576;
335
+ var MAX_TILESETS = 18;
336
+ var MAX_GRANT_LIFETIME_MS = 15 * 60 * 1e3;
337
+ var REFRESH_MARGIN_MS = 3e4;
338
+ var ROTATION_AGE_MS = 6 * 60 * 60 * 1e3;
339
+ var ROTATION_REQUEST_LIMIT = 1e4;
340
+ var FUTURE_ISSUE_SKEW_MS = 3e4;
341
+ var MOBILE_CREDENTIAL = /^tf_public_[0-9a-f]{48}$/u;
342
+ var MAP_ID = /^map_[A-Za-z0-9_-]{16}$/u;
343
+ var CREDENTIAL_ID = /^key_[A-Za-z0-9_-]{8,80}$/u;
344
+ var COMPACT_ID = /^[A-Za-z0-9._:-]{1,255}$/u;
345
+ var SURFACE_ID = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
346
+ var NATIVE_GRANT = /^tf_native_v1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u;
347
+ var EXACT_ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
348
+ var ALLOWED_SCOPES = /* @__PURE__ */ new Set([
349
+ "style",
350
+ "tilejson",
351
+ "tile",
352
+ "sprite",
353
+ "glyph",
354
+ "font"
355
+ ]);
356
+ var SUCCESS_KEYS = /* @__PURE__ */ new Set([
357
+ "ok",
358
+ "kind",
359
+ "version",
360
+ "usageMode",
361
+ "counted",
362
+ "grant",
363
+ "mapId",
364
+ "sessionId",
365
+ "surfaceId",
366
+ "credentialId",
367
+ "credentialRevision",
368
+ "deliveryPolicyRevision",
369
+ "issuedAt",
370
+ "serverTime",
371
+ "expiresAt",
372
+ "meterMode",
373
+ "disposition",
374
+ "resourceOrigins",
375
+ "resourceScopes",
376
+ "tilesetIds"
377
+ ]);
378
+ var RESTART_KEYS = /* @__PURE__ */ new Set(["code", "error", "retryWithNewSession", "sessionId"]);
379
+ var ERROR_MESSAGES = {
380
+ NATIVE_SESSION_INPUT_INVALID: "Native session configuration is invalid.",
381
+ NATIVE_SESSION_RESPONSE_TOO_LARGE: "Native session response exceeded its byte limit.",
382
+ NATIVE_SESSION_RESPONSE_UTF8_INVALID: "Native session response was not valid UTF-8.",
383
+ NATIVE_SESSION_RESPONSE_JSON_INVALID: "Native session response was not valid JSON.",
384
+ NATIVE_SESSION_RESPONSE_INVALID: "Native session response did not satisfy the expected contract.",
385
+ NATIVE_SESSION_CREDENTIAL_REJECTED: "Native session credential was rejected.",
386
+ NATIVE_SESSION_MAP_MISMATCH: "Native session authority does not match the requested Map.",
387
+ NATIVE_SESSION_MAP_NOT_FOUND: "Native session Map was not found.",
388
+ NATIVE_SESSION_QUOTA_EXCEEDED: "Native session quota was exceeded.",
389
+ NATIVE_SESSION_REJECTED: "Native session request was rejected.",
390
+ NATIVE_SESSION_UNAVAILABLE: "Native session service is unavailable.",
391
+ NATIVE_SESSION_REPLACED: "Native session binding was replaced.",
392
+ NATIVE_SESSION_DISPOSED: "Native session controller is disposed."
393
+ };
394
+ var HostedNativeSessionError = class extends Error {
395
+ constructor(code) {
396
+ super(ERROR_MESSAGES[code]);
397
+ __publicField(this, "code");
398
+ __publicField(this, "kind");
399
+ this.name = "HostedNativeSessionError";
400
+ this.code = code;
401
+ this.kind = errorKind(code);
402
+ }
403
+ };
404
+ function createHostedNativeSessionController(input) {
405
+ if (!isRecord(input) || typeof input.fetch !== "function" || typeof input.now !== "function" || typeof input.sessionIdFactory !== "function") {
406
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
407
+ }
408
+ const fetch = input.fetch;
409
+ const now = input.now;
410
+ const sessionIdFactory = input.sessionIdFactory;
411
+ const issuedSessionIds = /* @__PURE__ */ new Set();
412
+ let binding = normalizeBinding(input.binding);
413
+ let lifecycle = "foreground";
414
+ let disposed = false;
415
+ let bindingGeneration = 0;
416
+ const admittedAuthorities = /* @__PURE__ */ new WeakMap();
417
+ const clock = { lastWall: readWallClock(now), logical: 0, epoch: 0 };
418
+ let activeSession = binding.kind === "hosted" ? createSession(binding.surfaceId, 0) : null;
419
+ let state = snapshotIdle(binding, activeSession, lifecycle);
420
+ const listeners = /* @__PURE__ */ new Set();
421
+ const operations = /* @__PURE__ */ new Set();
422
+ function readClock() {
423
+ const wall = readWallClock(now);
424
+ if (wall < clock.lastWall) clock.epoch += 1;
425
+ else clock.logical += wall - clock.lastWall;
426
+ clock.lastWall = wall;
427
+ return clock.logical;
428
+ }
429
+ function bindingIsDirect() {
430
+ return binding.kind === "direct";
431
+ }
432
+ function createSession(surfaceId, startedAt) {
433
+ let sessionId;
434
+ try {
435
+ sessionId = sessionIdFactory();
436
+ } catch {
437
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
438
+ }
439
+ if (typeof sessionId !== "string" || !COMPACT_ID.test(sessionId) || secretShaped(sessionId) || issuedSessionIds.has(sessionId)) {
440
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
441
+ }
442
+ issuedSessionIds.add(sessionId);
443
+ return {
444
+ authority: null,
445
+ error: null,
446
+ lastServerTime: null,
447
+ operation: null,
448
+ pendingAdmissions: 0,
449
+ redirect: null,
450
+ requestCount: 0,
451
+ requestedSurfaceId: surfaceId,
452
+ serverStartedAt: null,
453
+ sessionId,
454
+ startedAt
455
+ };
456
+ }
457
+ function publish(next) {
458
+ if (disposed && next.status !== "disposed") return;
459
+ state = next;
460
+ for (const listener of [...listeners]) {
461
+ try {
462
+ listener(next);
463
+ } catch {
464
+ }
465
+ }
466
+ }
467
+ function publishSession(session, status, error) {
468
+ if (disposed || activeSession !== session || binding.kind !== "hosted") return;
469
+ if (status === "ready") {
470
+ if (!session.authority) return;
471
+ const authority = session.authority.public;
472
+ publish(
473
+ Object.freeze({
474
+ status: "ready",
475
+ source: "hosted",
476
+ lifecycle,
477
+ mapId: binding.mapId,
478
+ sessionId: session.sessionId,
479
+ surfaceId: authority.surfaceId,
480
+ credentialId: authority.credentialId,
481
+ credentialRevision: authority.credentialRevision,
482
+ deliveryPolicyRevision: authority.deliveryPolicyRevision
483
+ })
484
+ );
485
+ return;
486
+ }
487
+ if (status === "error") {
488
+ if (!error) return;
489
+ publish(
490
+ Object.freeze({
491
+ status: "error",
492
+ source: "hosted",
493
+ lifecycle,
494
+ mapId: binding.mapId,
495
+ sessionId: session.sessionId,
496
+ error: Object.freeze({ ...error })
497
+ })
498
+ );
499
+ return;
500
+ }
501
+ publish(
502
+ Object.freeze({
503
+ status,
504
+ source: "hosted",
505
+ lifecycle,
506
+ mapId: binding.mapId,
507
+ sessionId: session.sessionId
508
+ })
509
+ );
510
+ }
511
+ function rotateIfRequired(at) {
512
+ if (!activeSession || binding.kind !== "hosted") return;
513
+ if (at - activeSession.startedAt < ROTATION_AGE_MS && activeSession.requestCount < ROTATION_REQUEST_LIMIT) {
514
+ return;
515
+ }
516
+ const previous = activeSession;
517
+ activeSession = createSession(
518
+ previous.authority?.public.surfaceId ?? previous.requestedSurfaceId,
519
+ at
520
+ );
521
+ publishSession(activeSession, "idle");
522
+ }
523
+ function redirectSession(session, at) {
524
+ const replacement = createSession(
525
+ session.authority?.public.surfaceId ?? session.requestedSurfaceId,
526
+ at
527
+ );
528
+ replacement.requestCount = session.pendingAdmissions;
529
+ replacement.operation = session.operation;
530
+ session.redirect = replacement;
531
+ activeSession = replacement;
532
+ publishSession(replacement, "loading");
533
+ return replacement;
534
+ }
535
+ function latestSession(session) {
536
+ const seen = /* @__PURE__ */ new Set();
537
+ let current = session;
538
+ while (current.redirect && !seen.has(current.redirect)) {
539
+ seen.add(current);
540
+ current = current.redirect;
541
+ }
542
+ return current;
543
+ }
544
+ async function ensureAuthority(session, at) {
545
+ if (session.authority && session.authority.clockEpoch === clock.epoch && session.authority.validUntil - at > REFRESH_MARGIN_MS) {
546
+ return { authority: session.authority, session };
547
+ }
548
+ if (session.operation) return session.operation.promise;
549
+ if (binding.kind !== "hosted") {
550
+ throw new HostedNativeSessionError("NATIVE_SESSION_REPLACED");
551
+ }
552
+ const operationBinding = binding;
553
+ const abort = new SessionAbortController();
554
+ operations.add(abort);
555
+ const operation = {
556
+ abort,
557
+ promise: Promise.resolve().then(async () => {
558
+ session.error = null;
559
+ publishSession(session, "loading");
560
+ if (abort.signal.aborted) throw abort.error();
561
+ try {
562
+ const result = await bootstrap(session, operationBinding, abort, true, true);
563
+ if (abort.signal.aborted) throw abort.error();
564
+ result.session.authority = result.authority;
565
+ result.session.error = null;
566
+ if (activeSession === result.session) {
567
+ publishSession(result.session, "ready");
568
+ if (abort.signal.aborted) throw abort.error();
569
+ }
570
+ return result;
571
+ } catch (error) {
572
+ const normalized = abort.signal.aborted ? abort.error() : normalizeError(error);
573
+ const target = latestSession(session);
574
+ if (normalized.code !== "NATIVE_SESSION_REPLACED" && normalized.code !== "NATIVE_SESSION_DISPOSED") {
575
+ target.error = Object.freeze({ code: normalized.code, kind: normalized.kind });
576
+ publishSession(target, "error", target.error);
577
+ }
578
+ throw normalized;
579
+ } finally {
580
+ operations.delete(abort);
581
+ let current = session;
582
+ const seen = /* @__PURE__ */ new Set();
583
+ while (current && !seen.has(current)) {
584
+ seen.add(current);
585
+ if (current.operation === operation) current.operation = null;
586
+ current = current.redirect;
587
+ }
588
+ }
589
+ })
590
+ };
591
+ session.operation = operation;
592
+ return operation.promise;
593
+ }
594
+ async function bootstrap(session, hostedBinding, abort, allowRestart, allowServerAgeRotation) {
595
+ const requestStartedAt = readClock();
596
+ const requestClockEpoch = clock.epoch;
597
+ const requestBindingGeneration = bindingGeneration;
598
+ if (abort.signal.aborted) throw abort.error();
599
+ const expectedSurfaceId = session.authority?.public.surfaceId ?? session.requestedSurfaceId;
600
+ const response = await fetchResponse(hostedBinding, session, expectedSurfaceId, abort);
601
+ const source = await readBoundedResponse(response, abort);
602
+ let body;
603
+ try {
604
+ body = source.length ? JSON.parse(source) : null;
605
+ } catch {
606
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_JSON_INVALID");
607
+ }
608
+ if (response.status === 409 && allowRestart && isExactRestart(body, session.sessionId)) {
609
+ assertCurrentSession(session, hostedBinding);
610
+ const replacement = redirectSession(session, readClock());
611
+ if (abort.signal.aborted) throw abort.error();
612
+ return bootstrap(replacement, hostedBinding, abort, false, false);
613
+ }
614
+ if (response.status !== 201) throw statusError(response.status);
615
+ if (!hasNoStore(response.headers.get("cache-control"))) {
616
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
617
+ }
618
+ const payload = parseSuccess(body, hostedBinding, session.sessionId, expectedSurfaceId);
619
+ if (session.lastServerTime !== null && payload.serverTimeMs < session.lastServerTime) {
620
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
621
+ }
622
+ if (allowServerAgeRotation && session.serverStartedAt !== null && payload.serverTimeMs - session.serverStartedAt >= ROTATION_AGE_MS) {
623
+ assertCurrentSession(session, hostedBinding);
624
+ const replacement = redirectSession(session, readClock());
625
+ if (abort.signal.aborted) throw abort.error();
626
+ return bootstrap(replacement, hostedBinding, abort, false, false);
627
+ }
628
+ session.serverStartedAt ?? (session.serverStartedAt = payload.serverTimeMs);
629
+ session.lastServerTime = payload.serverTimeMs;
630
+ const ttl = payload.expiresAtMs - payload.serverTimeMs;
631
+ return {
632
+ authority: Object.freeze({
633
+ public: payload.authority,
634
+ validUntil: requestStartedAt + ttl,
635
+ clockEpoch: requestClockEpoch,
636
+ bindingGeneration: requestBindingGeneration
637
+ }),
638
+ session
639
+ };
640
+ }
641
+ function assertCurrentSession(session, expectedBinding) {
642
+ if (activeSession !== session || binding.kind !== "hosted" || !sameHostedBinding(binding, expectedBinding)) {
643
+ throw new HostedNativeSessionError("NATIVE_SESSION_REPLACED");
644
+ }
645
+ }
646
+ async function fetchResponse(hostedBinding, session, surfaceId, abort) {
647
+ const body = JSON.stringify({
648
+ mapId: hostedBinding.mapId,
649
+ sessionId: session.sessionId,
650
+ surfaceId
651
+ });
652
+ const init = Object.freeze({
653
+ body,
654
+ credentials: "omit",
655
+ headers: Object.freeze({
656
+ "Content-Type": "application/json",
657
+ "X-Tileflow-Mobile-Client": hostedBinding.credential
658
+ }),
659
+ method: "POST",
660
+ signal: abort.signal
661
+ });
662
+ let pending;
663
+ try {
664
+ pending = Promise.resolve(fetch(`${hostedBinding.apiOrigin}/v1/sessions/start`, init));
665
+ } catch {
666
+ if (abort.signal.aborted) throw abort.error();
667
+ throw new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE");
668
+ }
669
+ pending.then(
670
+ (response) => {
671
+ if (abort.signal.aborted) void cancelResponse(response);
672
+ },
673
+ () => void 0
674
+ );
675
+ try {
676
+ return await raceWithAbort(pending, abort);
677
+ } catch (error) {
678
+ if (abort.signal.aborted) throw abort.error();
679
+ throw normalizeError(error, "NATIVE_SESSION_UNAVAILABLE");
680
+ }
681
+ }
682
+ function currentSnapshot() {
683
+ if (disposed) return Object.freeze({ status: "disposed", lifecycle: "disposed" });
684
+ if (binding.kind === "direct" || !activeSession) {
685
+ return Object.freeze({ status: "idle", source: "direct", lifecycle });
686
+ }
687
+ if (activeSession.operation) {
688
+ return Object.freeze({
689
+ status: "loading",
690
+ source: "hosted",
691
+ lifecycle,
692
+ mapId: binding.mapId,
693
+ sessionId: activeSession.sessionId
694
+ });
695
+ }
696
+ if (activeSession.error) {
697
+ return Object.freeze({
698
+ status: "error",
699
+ source: "hosted",
700
+ lifecycle,
701
+ mapId: binding.mapId,
702
+ sessionId: activeSession.sessionId,
703
+ error: Object.freeze({ ...activeSession.error })
704
+ });
705
+ }
706
+ if (activeSession.authority) {
707
+ const authority = activeSession.authority.public;
708
+ return Object.freeze({
709
+ status: "ready",
710
+ source: "hosted",
711
+ lifecycle,
712
+ mapId: binding.mapId,
713
+ sessionId: activeSession.sessionId,
714
+ surfaceId: authority.surfaceId,
715
+ credentialId: authority.credentialId,
716
+ credentialRevision: authority.credentialRevision,
717
+ deliveryPolicyRevision: authority.deliveryPolicyRevision
718
+ });
719
+ }
720
+ return Object.freeze({
721
+ status: "idle",
722
+ source: "hosted",
723
+ lifecycle,
724
+ mapId: binding.mapId,
725
+ sessionId: activeSession.sessionId
726
+ });
727
+ }
728
+ async function acquireAuthority(session, at) {
729
+ const expectedGeneration = bindingGeneration;
730
+ let candidate = session;
731
+ let checkedAt = at;
732
+ for (let attempt = 0; attempt < 2; attempt += 1) {
733
+ const resolved = await ensureAuthority(candidate, checkedAt);
734
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
735
+ if (bindingGeneration !== expectedGeneration) {
736
+ throw new HostedNativeSessionError("NATIVE_SESSION_REPLACED");
737
+ }
738
+ checkedAt = readClock();
739
+ if (resolved.authority.clockEpoch === clock.epoch && resolved.authority.validUntil > checkedAt) {
740
+ admittedAuthorities.set(resolved.authority.public, resolved.authority);
741
+ return resolved.authority.public;
742
+ }
743
+ resolved.session.authority = null;
744
+ candidate = resolved.session;
745
+ }
746
+ const error = new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE");
747
+ candidate.error = Object.freeze({ code: error.code, kind: error.kind });
748
+ publishSession(candidate, "error", candidate.error);
749
+ throw error;
750
+ }
751
+ return Object.freeze({
752
+ get state() {
753
+ return state;
754
+ },
755
+ async prepare() {
756
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
757
+ if (bindingIsDirect()) return null;
758
+ if (lifecycle !== "foreground")
759
+ throw new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE");
760
+ const at = readClock();
761
+ rotateIfRequired(at);
762
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
763
+ if (bindingIsDirect() || !activeSession)
764
+ throw new HostedNativeSessionError("NATIVE_SESSION_REPLACED");
765
+ const authority = await acquireAuthority(activeSession, at);
766
+ if (lifecycle !== "foreground")
767
+ throw new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE");
768
+ return Object.freeze({
769
+ mapId: authority.mapId,
770
+ resourceOrigins: Object.freeze([...authority.resourceOrigins]),
771
+ resourceScopes: Object.freeze([...authority.resourceScopes]),
772
+ tilesetIds: Object.freeze([...authority.tilesetIds])
773
+ });
774
+ },
775
+ async acquire() {
776
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
777
+ if (bindingIsDirect()) return null;
778
+ const at = readClock();
779
+ rotateIfRequired(at);
780
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
781
+ if (bindingIsDirect() || !activeSession) return null;
782
+ const session = activeSession;
783
+ session.requestCount += 1;
784
+ session.pendingAdmissions += 1;
785
+ try {
786
+ return await acquireAuthority(session, at);
787
+ } finally {
788
+ session.pendingAdmissions = Math.max(0, session.pendingAdmissions - 1);
789
+ }
790
+ },
791
+ transportBudget(authority) {
792
+ if (disposed || lifecycle !== "foreground") return 0;
793
+ const stored = admittedAuthorities.get(authority);
794
+ if (!stored || stored.bindingGeneration !== bindingGeneration) return 0;
795
+ try {
796
+ const at = readClock();
797
+ if (stored.clockEpoch !== clock.epoch) return 0;
798
+ return Math.max(0, Math.floor(stored.validUntil - at));
799
+ } catch {
800
+ return 0;
801
+ }
802
+ },
803
+ background() {
804
+ if (disposed) return;
805
+ lifecycle = "background";
806
+ publish(currentSnapshot());
807
+ },
808
+ async resume() {
809
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
810
+ lifecycle = "foreground";
811
+ if (binding.kind === "direct" || !activeSession) {
812
+ publish(currentSnapshot());
813
+ return;
814
+ }
815
+ const previous = activeSession;
816
+ const hadAuthority = Boolean(previous.authority || previous.operation);
817
+ const at = readClock();
818
+ rotateIfRequired(at);
819
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
820
+ publish(currentSnapshot());
821
+ if (!hadAuthority || !activeSession) return;
822
+ if (activeSession !== previous || !activeSession.authority || activeSession.authority.clockEpoch !== clock.epoch || activeSession.authority.validUntil - at <= REFRESH_MARGIN_MS) {
823
+ await acquireAuthority(activeSession, at);
824
+ }
825
+ },
826
+ replaceBinding(nextBinding) {
827
+ if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
828
+ const next = normalizeBinding(nextBinding);
829
+ if (sameBinding(binding, next)) return;
830
+ bindingGeneration += 1;
831
+ for (const operation of operations) operation.abort("NATIVE_SESSION_REPLACED");
832
+ binding = next;
833
+ activeSession = next.kind === "hosted" ? createSession(next.surfaceId, readClock()) : null;
834
+ publish(snapshotIdle(binding, activeSession, lifecycle));
835
+ },
836
+ subscribe(listener) {
837
+ if (typeof listener !== "function") {
838
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
839
+ }
840
+ if (disposed) return () => void 0;
841
+ listeners.add(listener);
842
+ let released = false;
843
+ return () => {
844
+ if (released) return;
845
+ released = true;
846
+ listeners.delete(listener);
847
+ };
848
+ },
849
+ dispose() {
850
+ if (disposed) return;
851
+ disposed = true;
852
+ bindingGeneration += 1;
853
+ for (const operation of operations) operation.abort("NATIVE_SESSION_DISPOSED");
854
+ operations.clear();
855
+ activeSession = null;
856
+ binding = Object.freeze({ kind: "direct" });
857
+ publish(Object.freeze({ status: "disposed", lifecycle: "disposed" }));
858
+ listeners.clear();
859
+ }
860
+ });
861
+ }
862
+ function normalizeBinding(value) {
863
+ try {
864
+ if (!isRecord(value)) throw new Error();
865
+ if (value.kind === "direct") {
866
+ if (Object.keys(value).length !== 1) throw new Error();
867
+ return Object.freeze({ kind: "direct" });
868
+ }
869
+ if (value.kind !== "hosted" || Object.keys(value).some(
870
+ (key) => !["kind", "apiOrigin", "credential", "mapId", "surfaceId"].includes(key)
871
+ ) || typeof value.credential !== "string" || !MOBILE_CREDENTIAL.test(value.credential) || typeof value.mapId !== "string" || !MAP_ID.test(value.mapId)) {
872
+ throw new Error();
873
+ }
874
+ return Object.freeze({
875
+ kind: "hosted",
876
+ apiOrigin: canonicalOrigin(value.apiOrigin),
877
+ credential: value.credential,
878
+ mapId: value.mapId,
879
+ surfaceId: normalizeSurface(value.surfaceId)
880
+ });
881
+ } catch (error) {
882
+ if (error instanceof HostedNativeSessionError) throw error;
883
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
884
+ }
885
+ }
886
+ function canonicalOrigin(value) {
887
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048 || value !== value.trim()) {
888
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
889
+ }
890
+ try {
891
+ const resolved = resolveTileflowNativeManifestUrl3(value);
892
+ if (!resolved.endsWith("/")) throw new Error();
893
+ const origin = resolved.slice(0, -1);
894
+ if (value !== origin) throw new Error();
895
+ return origin;
896
+ } catch {
897
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
898
+ }
899
+ }
900
+ function normalizeSurface(value) {
901
+ return typeof value === "string" && SURFACE_ID.test(value) && !secretShaped(value) ? value : "default";
902
+ }
903
+ function secretShaped(value) {
904
+ return MOBILE_CREDENTIAL.test(value) || value.startsWith("tf_native_");
905
+ }
906
+ function sameHostedBinding(left, right) {
907
+ return left.apiOrigin === right.apiOrigin && left.credential === right.credential && left.mapId === right.mapId;
908
+ }
909
+ function sameBinding(left, right) {
910
+ if (left.kind !== right.kind) return false;
911
+ if (left.kind === "direct") return true;
912
+ return right.kind === "hosted" && sameHostedBinding(left, right);
913
+ }
914
+ function snapshotIdle(binding, session, lifecycle) {
915
+ return binding.kind === "direct" || !session ? Object.freeze({ status: "idle", source: "direct", lifecycle }) : Object.freeze({
916
+ status: "idle",
917
+ source: "hosted",
918
+ lifecycle,
919
+ mapId: binding.mapId,
920
+ sessionId: session.sessionId
921
+ });
922
+ }
923
+ function parseSuccess(value, binding, expectedSessionId, expectedSurfaceId) {
924
+ if (!isRecord(value) || Object.keys(value).length !== SUCCESS_KEYS.size || Object.keys(value).some((key) => !SUCCESS_KEYS.has(key)) || value.ok !== true || value.kind !== "native" || value.version !== 1 || value.usageMode !== "session" || value.counted !== false || value.mapId !== binding.mapId || value.sessionId !== expectedSessionId || value.surfaceId !== expectedSurfaceId || typeof value.surfaceId !== "string" || !SURFACE_ID.test(value.surfaceId) || secretShaped(value.surfaceId) || typeof value.credentialId !== "string" || !CREDENTIAL_ID.test(value.credentialId) || !positiveRevision(value.credentialRevision) || !positiveRevision(value.deliveryPolicyRevision) || typeof value.grant !== "string" || value.grant.length === 0 || value.grant.length > GRANT_CHARACTER_LIMIT || !NATIVE_GRANT.test(value.grant) || !["disabled", "shadow", "enforced"].includes(String(value.meterMode)) || !["unmetered", "ordinary", "unbilled_fail_open"].includes(String(value.disposition)) || value.disposition === "unmetered" && value.meterMode !== "disabled") {
925
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
926
+ }
927
+ const issuedAtMs = parseExactIso(value.issuedAt);
928
+ const serverTimeMs = parseExactIso(value.serverTime);
929
+ const expiresAtMs = parseExactIso(value.expiresAt);
930
+ if (issuedAtMs > serverTimeMs + FUTURE_ISSUE_SKEW_MS || expiresAtMs <= serverTimeMs || expiresAtMs <= issuedAtMs || expiresAtMs - issuedAtMs > MAX_GRANT_LIFETIME_MS || expiresAtMs - issuedAtMs > MAX_GRANT_LIFETIME_MS) {
931
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
932
+ }
933
+ const authority = {
934
+ mapId: value.mapId,
935
+ sessionId: value.sessionId,
936
+ surfaceId: value.surfaceId,
937
+ credentialId: value.credentialId,
938
+ credentialRevision: value.credentialRevision,
939
+ deliveryPolicyRevision: value.deliveryPolicyRevision,
940
+ issuedAt: value.issuedAt,
941
+ serverTime: value.serverTime,
942
+ expiresAt: value.expiresAt,
943
+ meterMode: value.meterMode,
944
+ disposition: value.disposition,
945
+ resourceOrigins: Object.freeze(parseOrigins(value.resourceOrigins)),
946
+ resourceScopes: Object.freeze(parseScopes(value.resourceScopes)),
947
+ tilesetIds: Object.freeze(parseTilesets(value.tilesetIds))
948
+ };
949
+ const withGrant = Object.defineProperty(authority, "grant", {
950
+ value: value.grant,
951
+ enumerable: false,
952
+ configurable: false,
953
+ writable: false
954
+ });
955
+ return { authority: Object.freeze(withGrant), serverTimeMs, expiresAtMs };
956
+ }
957
+ function parseOrigins(value) {
958
+ if (!Array.isArray(value) || value.length < 1 || value.length > 2) {
959
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
960
+ }
961
+ const result = [];
962
+ for (const item of value) {
963
+ if (typeof item !== "string") {
964
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
965
+ }
966
+ let origin;
967
+ try {
968
+ const resolved = resolveTileflowNativeManifestUrl3(item);
969
+ if (!resolved.endsWith("/")) throw new Error();
970
+ origin = resolved.slice(0, -1);
971
+ if (item !== origin) throw new Error();
972
+ } catch {
973
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
974
+ }
975
+ if (result.includes(origin)) {
976
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
977
+ }
978
+ result.push(origin);
979
+ }
980
+ return result;
981
+ }
982
+ function parseScopes(value) {
983
+ if (!Array.isArray(value) || value.length < 1 || value.length > ALLOWED_SCOPES.size) {
984
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
985
+ }
986
+ const result = [];
987
+ for (const item of value) {
988
+ if (typeof item !== "string" || !ALLOWED_SCOPES.has(item) || result.includes(item)) {
989
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
990
+ }
991
+ result.push(item);
992
+ }
993
+ return result;
994
+ }
995
+ function parseTilesets(value) {
996
+ if (!Array.isArray(value) || value.length > MAX_TILESETS) {
997
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
998
+ }
999
+ const result = [];
1000
+ for (const item of value) {
1001
+ if (typeof item !== "string" || !COMPACT_ID.test(item) || result.includes(item)) {
1002
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
1003
+ }
1004
+ result.push(item);
1005
+ }
1006
+ return result;
1007
+ }
1008
+ function parseExactIso(value) {
1009
+ if (typeof value !== "string" || !EXACT_ISO.test(value)) {
1010
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
1011
+ }
1012
+ const parsed = Date.parse(value);
1013
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
1014
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
1015
+ }
1016
+ return parsed;
1017
+ }
1018
+ function positiveRevision(value) {
1019
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
1020
+ }
1021
+ function isExactRestart(value, expectedSessionId) {
1022
+ return isRecord(value) && Object.keys(value).length === RESTART_KEYS.size && Object.keys(value).every((key) => RESTART_KEYS.has(key)) && value.code === "COMMERCIAL_SESSION_RESTART_REQUIRED" && typeof value.error === "string" && value.error.length > 0 && value.error.length <= 500 && value.retryWithNewSession === true && value.sessionId === expectedSessionId;
1023
+ }
1024
+ async function readBoundedResponse(response, abort) {
1025
+ let reader;
1026
+ try {
1027
+ if (!response || !Number.isSafeInteger(response.status) || !response.headers || typeof response.headers.get !== "function" || !response.body || typeof response.body.getReader !== "function") {
1028
+ throw new Error();
1029
+ }
1030
+ reader = response.body.getReader();
1031
+ } catch {
1032
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
1033
+ }
1034
+ const chunks = [];
1035
+ let size = 0;
1036
+ try {
1037
+ for (; ; ) {
1038
+ if (abort.signal.aborted) throw abort.error();
1039
+ const next = await raceWithAbort(Promise.resolve(reader.read()), abort);
1040
+ if (next.done) break;
1041
+ if (!(next.value instanceof Uint8Array)) {
1042
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
1043
+ }
1044
+ size += next.value.byteLength;
1045
+ if (size > RESPONSE_BYTE_LIMIT) {
1046
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_TOO_LARGE");
1047
+ }
1048
+ chunks.push(next.value.slice());
1049
+ }
1050
+ } catch (error) {
1051
+ void Promise.resolve(reader.cancel()).catch(() => void 0);
1052
+ if (abort.signal.aborted) throw abort.error();
1053
+ throw normalizeError(error, "NATIVE_SESSION_UNAVAILABLE");
1054
+ }
1055
+ const bytes = new Uint8Array(size);
1056
+ let offset = 0;
1057
+ for (const chunk of chunks) {
1058
+ bytes.set(chunk, offset);
1059
+ offset += chunk.byteLength;
1060
+ }
1061
+ return decodeUtf8(bytes);
1062
+ }
1063
+ function decodeUtf8(bytes) {
1064
+ let result = "";
1065
+ for (let index = 0; index < bytes.length; ) {
1066
+ const first = bytes[index++];
1067
+ let codePoint;
1068
+ let remaining;
1069
+ let minimum;
1070
+ if (first <= 127) {
1071
+ codePoint = first;
1072
+ remaining = 0;
1073
+ minimum = 0;
1074
+ } else if (first >= 194 && first <= 223) {
1075
+ codePoint = first & 31;
1076
+ remaining = 1;
1077
+ minimum = 128;
1078
+ } else if (first >= 224 && first <= 239) {
1079
+ codePoint = first & 15;
1080
+ remaining = 2;
1081
+ minimum = 2048;
1082
+ } else if (first >= 240 && first <= 244) {
1083
+ codePoint = first & 7;
1084
+ remaining = 3;
1085
+ minimum = 65536;
1086
+ } else {
1087
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_UTF8_INVALID");
1088
+ }
1089
+ if (index + remaining > bytes.length) {
1090
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_UTF8_INVALID");
1091
+ }
1092
+ for (let count = 0; count < remaining; count += 1) {
1093
+ const byte = bytes[index++];
1094
+ if ((byte & 192) !== 128) {
1095
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_UTF8_INVALID");
1096
+ }
1097
+ codePoint = codePoint << 6 | byte & 63;
1098
+ }
1099
+ if (codePoint < minimum || codePoint > 1114111 || codePoint >= 55296 && codePoint <= 57343) {
1100
+ throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_UTF8_INVALID");
1101
+ }
1102
+ result += String.fromCodePoint(codePoint);
1103
+ }
1104
+ return result;
1105
+ }
1106
+ function hasNoStore(value) {
1107
+ return typeof value === "string" && value.split(",").some((part) => part.trim().toLowerCase() === "no-store");
1108
+ }
1109
+ function statusError(status) {
1110
+ if (status === 401) return new HostedNativeSessionError("NATIVE_SESSION_CREDENTIAL_REJECTED");
1111
+ if (status === 403) return new HostedNativeSessionError("NATIVE_SESSION_MAP_MISMATCH");
1112
+ if (status === 404) return new HostedNativeSessionError("NATIVE_SESSION_MAP_NOT_FOUND");
1113
+ if (status === 429) return new HostedNativeSessionError("NATIVE_SESSION_QUOTA_EXCEEDED");
1114
+ if (status === 503) return new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE");
1115
+ return new HostedNativeSessionError("NATIVE_SESSION_REJECTED");
1116
+ }
1117
+ function errorKind(code) {
1118
+ if (code === "NATIVE_SESSION_DISPOSED" || code === "NATIVE_SESSION_REPLACED") return "cancelled";
1119
+ if (code === "NATIVE_SESSION_UNAVAILABLE") return "recoverable";
1120
+ return "terminal";
1121
+ }
1122
+ function normalizeError(error, fallback = "NATIVE_SESSION_RESPONSE_INVALID") {
1123
+ return error instanceof HostedNativeSessionError ? error : new HostedNativeSessionError(fallback);
1124
+ }
1125
+ function readWallClock(now) {
1126
+ try {
1127
+ const value = now();
1128
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new Error();
1129
+ return value.getTime();
1130
+ } catch {
1131
+ throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
1132
+ }
1133
+ }
1134
+ function isRecord(value) {
1135
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1136
+ }
1137
+ async function cancelResponse(response) {
1138
+ try {
1139
+ await response.body?.getReader().cancel();
1140
+ } catch {
1141
+ }
1142
+ }
1143
+ var _aborted, _code, _listeners;
1144
+ var SessionAbortController = class {
1145
+ constructor() {
1146
+ __publicField(this, "signal");
1147
+ __privateAdd(this, _aborted, false);
1148
+ __privateAdd(this, _code, "NATIVE_SESSION_REPLACED");
1149
+ __privateAdd(this, _listeners, /* @__PURE__ */ new Set());
1150
+ const isAborted = () => __privateGet(this, _aborted);
1151
+ this.signal = Object.freeze({
1152
+ get aborted() {
1153
+ return isAborted();
1154
+ },
1155
+ addEventListener: (type, listener, _options) => {
1156
+ if (type === "abort" && !__privateGet(this, _aborted)) __privateGet(this, _listeners).add(listener);
1157
+ },
1158
+ removeEventListener: (type, listener) => {
1159
+ if (type === "abort") __privateGet(this, _listeners).delete(listener);
1160
+ }
1161
+ });
1162
+ }
1163
+ abort(code) {
1164
+ if (__privateGet(this, _aborted)) return;
1165
+ __privateSet(this, _code, code);
1166
+ __privateSet(this, _aborted, true);
1167
+ for (const listener of [...__privateGet(this, _listeners)]) {
1168
+ try {
1169
+ listener();
1170
+ } catch {
1171
+ }
1172
+ }
1173
+ __privateGet(this, _listeners).clear();
1174
+ }
1175
+ error() {
1176
+ return new HostedNativeSessionError(__privateGet(this, _code));
1177
+ }
1178
+ };
1179
+ _aborted = new WeakMap();
1180
+ _code = new WeakMap();
1181
+ _listeners = new WeakMap();
1182
+ function raceWithAbort(promise, controller) {
1183
+ if (controller.signal.aborted) return Promise.reject(controller.error());
1184
+ return new Promise((resolve, reject) => {
1185
+ const onAbort = () => {
1186
+ cleanup();
1187
+ reject(controller.error());
1188
+ };
1189
+ const cleanup = () => controller.signal.removeEventListener("abort", onAbort);
1190
+ controller.signal.addEventListener("abort", onAbort, { once: true });
1191
+ promise.then(
1192
+ (value) => {
1193
+ cleanup();
1194
+ resolve(value);
1195
+ },
1196
+ () => {
1197
+ cleanup();
1198
+ reject(new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE"));
1199
+ }
1200
+ );
1201
+ });
1202
+ }
1203
+
1204
+ // src/native-admission-owner.ts
1205
+ var NativeAdmissionError = class extends Error {
1206
+ constructor(code) {
1207
+ super("Native resource admission failed.");
1208
+ __publicField(this, "code");
1209
+ this.name = "NativeAdmissionError";
1210
+ this.code = code;
1211
+ }
1212
+ };
1213
+ function createNativeAdmissionOwner(options) {
1214
+ const bridge = options.bridge;
1215
+ const factory = options.createController ?? createHostedNativeSessionController;
1216
+ const contexts = /* @__PURE__ */ new Map();
1217
+ const pendingControllers = /* @__PURE__ */ new Set();
1218
+ let installation = null;
1219
+ let installed;
1220
+ let installResult;
1221
+ let disposed = false;
1222
+ let foreground = true;
1223
+ let contextSequence = 0;
1224
+ let status = "idle";
1225
+ let disposal;
1226
+ function cancel(code = "NATIVE_ADMISSION_CANCELLED") {
1227
+ return new NativeAdmissionError(code);
1228
+ }
1229
+ function install() {
1230
+ if (disposed) return Promise.reject(cancel());
1231
+ if (installResult) return installResult;
1232
+ status = "installing";
1233
+ try {
1234
+ installed = bridge.install().then(
1235
+ (ack) => {
1236
+ if (!ack || !isNativeToken(ack.installation)) throw cancel("NATIVE_ADMISSION_INVALID");
1237
+ installation = ack.installation;
1238
+ return ack.installation;
1239
+ },
1240
+ () => {
1241
+ throw cancel("NATIVE_ADMISSION_UNAVAILABLE");
1242
+ }
1243
+ );
1244
+ } catch {
1245
+ installed = Promise.reject(cancel("NATIVE_ADMISSION_UNAVAILABLE"));
1246
+ }
1247
+ const attempt = installed.then(
1248
+ (id) => {
1249
+ if (disposed) throw cancel();
1250
+ status = "ready";
1251
+ return Object.freeze({ installation: id });
1252
+ },
1253
+ () => {
1254
+ if (!disposed) status = "error";
1255
+ throw cancel("NATIVE_ADMISSION_UNAVAILABLE");
1256
+ }
1257
+ );
1258
+ installResult = attempt;
1259
+ void attempt.catch(() => {
1260
+ if (!disposed && installation === null && installResult === attempt) {
1261
+ installed = void 0;
1262
+ installResult = void 0;
1263
+ }
1264
+ });
1265
+ return attempt;
1266
+ }
1267
+ function retire(context) {
1268
+ return context.handle.retire();
1269
+ }
1270
+ function failContext(context) {
1271
+ void retire(context).catch(() => void 0);
1272
+ }
1273
+ async function handleBatch(context, event) {
1274
+ if (!context.live || !foreground || event.generation !== context.generation) return;
1275
+ const serial = typeof event.batch === "string" && /^[1-9][0-9]{0,15}$/u.test(event.batch) ? Number(event.batch) : NaN;
1276
+ if (Number.isSafeInteger(serial) && serial <= context.lastBatch) return;
1277
+ if (!Number.isSafeInteger(serial) || context.batch !== null || !Array.isArray(event.tickets) || event.tickets.length === 0 || event.tickets.length > nativeAdmissionLimits.batchSize) {
1278
+ failContext(context);
1279
+ return;
1280
+ }
1281
+ const selected = /* @__PURE__ */ new Map();
1282
+ for (const ticket of event.tickets) {
1283
+ if (!ticket || !isNativeToken(ticket.ticket) || selected.has(ticket.ticket) || typeof ticket.url !== "string") {
1284
+ failContext(context);
1285
+ return;
1286
+ }
1287
+ const resource = context.catalog.match(ticket.url);
1288
+ if (!resource) {
1289
+ failContext(context);
1290
+ return;
1291
+ }
1292
+ selected.set(ticket.ticket, resource);
1293
+ }
1294
+ context.lastBatch = serial;
1295
+ context.batch = event.batch;
1296
+ const tickets = new Map(event.tickets.map((ticket) => [ticket.ticket, { cancelled: false }]));
1297
+ context.tickets = tickets;
1298
+ const acquisitions = event.tickets.map(
1299
+ async (ticket) => {
1300
+ if (!context.live || tickets.get(ticket.ticket)?.cancelled) return false;
1301
+ if (context.mapId === null) return null;
1302
+ try {
1303
+ return await context.controller.acquire();
1304
+ } catch {
1305
+ return false;
1306
+ }
1307
+ }
1308
+ );
1309
+ const authorities = await Promise.all(acquisitions);
1310
+ if (disposed || !context.live || context.batch !== event.batch || installation !== event.installation)
1311
+ return;
1312
+ const results = event.tickets.map((ticket, index) => {
1313
+ const reject = (code) => ({
1314
+ ticket: ticket.ticket,
1315
+ kind: "reject",
1316
+ code
1317
+ });
1318
+ if (!foreground || tickets.get(ticket.ticket)?.cancelled)
1319
+ return reject("NATIVE_ADMISSION_CANCELLED");
1320
+ const authority = authorities[index];
1321
+ if (authority === false) return reject("NATIVE_ADMISSION_DENIED");
1322
+ if (context.mapId === null) return { ticket: ticket.ticket, kind: "delegate" };
1323
+ const resource = selected.get(ticket.ticket);
1324
+ if (!authority || !authorityAllowsResource(authority, resource, context.mapId))
1325
+ return reject("NATIVE_ADMISSION_DENIED");
1326
+ const validForMs = context.controller.transportBudget(authority);
1327
+ if (!Number.isSafeInteger(validForMs) || validForMs <= nativeAdmissionLimits.validitySafetyMs || validForMs > 9e5) {
1328
+ return reject("NATIVE_ADMISSION_EXPIRED");
1329
+ }
1330
+ return {
1331
+ ticket: ticket.ticket,
1332
+ kind: "grant",
1333
+ validForMs,
1334
+ authority: {
1335
+ grant: authority.grant,
1336
+ mapId: authority.mapId,
1337
+ resourceOrigins: [...authority.resourceOrigins],
1338
+ resourceScopes: [...authority.resourceScopes],
1339
+ tilesetIds: [...authority.tilesetIds]
1340
+ }
1341
+ };
1342
+ });
1343
+ if (JSON.stringify(results).length > nativeAdmissionLimits.bridgeBytes) {
1344
+ failContext(context);
1345
+ return;
1346
+ }
1347
+ context.batch = null;
1348
+ context.tickets = /* @__PURE__ */ new Map();
1349
+ try {
1350
+ await bridge.completeBatch(
1351
+ event.installation,
1352
+ context.id,
1353
+ context.generation,
1354
+ event.batch,
1355
+ results
1356
+ );
1357
+ } catch {
1358
+ if (context.live) failContext(context);
1359
+ }
1360
+ }
1361
+ function onEvent(event) {
1362
+ if (disposed || !event || event.installation !== installation) return;
1363
+ if (event.kind === "ownershipLost") {
1364
+ void dispose().catch(() => void 0);
1365
+ return;
1366
+ }
1367
+ if (event.kind === "lifecycle") {
1368
+ foreground = event.foreground === true;
1369
+ for (const context2 of contexts.values()) {
1370
+ if (foreground) void context2.controller.resume().catch(() => void 0);
1371
+ else {
1372
+ context2.controller.background();
1373
+ for (const ticket of context2.tickets.values()) ticket.cancelled = true;
1374
+ }
1375
+ }
1376
+ return;
1377
+ }
1378
+ const context = contexts.get(event.context);
1379
+ if (!context || !context.live || event.generation !== context.generation) return;
1380
+ if (event.kind === "retired") {
1381
+ failContext(context);
1382
+ return;
1383
+ }
1384
+ if (event.kind === "cancel") {
1385
+ if (!Array.isArray(event.tickets) || event.tickets.length > nativeAdmissionLimits.queueDepth) {
1386
+ failContext(context);
1387
+ return;
1388
+ }
1389
+ for (const id of event.tickets) {
1390
+ const ticket = context.tickets.get(id);
1391
+ if (ticket) ticket.cancelled = true;
1392
+ }
1393
+ return;
1394
+ }
1395
+ if (event.kind === "response") {
1396
+ if (Number.isInteger(event.status) && event.status >= 100 && event.status <= 599) {
1397
+ try {
1398
+ options.onObservation?.(Object.freeze({ context: context.id, status: event.status }));
1399
+ } catch {
1400
+ }
1401
+ }
1402
+ return;
1403
+ }
1404
+ if (event.kind === "batch") void handleBatch(context, event).catch(() => failContext(context));
1405
+ }
1406
+ const unsubscribe = bridge.subscribe((event) => {
1407
+ try {
1408
+ onEvent(event);
1409
+ } catch {
1410
+ void dispose().catch(() => void 0);
1411
+ }
1412
+ });
1413
+ async function openMap(input) {
1414
+ if (disposed) throw cancel();
1415
+ let resources;
1416
+ try {
1417
+ resources = normalizeNativeResources(input.resources);
1418
+ if (input.onRetired !== void 0 && typeof input.onRetired !== "function") throw cancel();
1419
+ } catch {
1420
+ throw cancel("NATIVE_ADMISSION_INVALID");
1421
+ }
1422
+ if (input.fetch && options.sessionFetch) throw cancel("NATIVE_ADMISSION_INVALID");
1423
+ const ack = await install();
1424
+ if (disposed) throw cancel();
1425
+ if (contexts.size + pendingControllers.size >= nativeAdmissionLimits.contexts || contextSequence >= Number.MAX_SAFE_INTEGER) {
1426
+ throw cancel("NATIVE_ADMISSION_UNAVAILABLE");
1427
+ }
1428
+ const identity = `${ack.installation}.${++contextSequence}`;
1429
+ let sessionSequence = 0;
1430
+ const sessionIdFactory = input.sessionIdFactory ?? (() => {
1431
+ if (sessionSequence >= Number.MAX_SAFE_INTEGER) throw cancel("NATIVE_ADMISSION_INVALID");
1432
+ return `${identity}.${++sessionSequence}`;
1433
+ });
1434
+ let boundFetch;
1435
+ const fetch = options.sessionFetch ? (url, init) => {
1436
+ if (!boundFetch) return Promise.reject(cancel("NATIVE_ADMISSION_CANCELLED"));
1437
+ return boundFetch(url, init);
1438
+ } : input.fetch ?? (input.binding.kind === "direct" ? async () => {
1439
+ throw cancel("NATIVE_ADMISSION_INVALID");
1440
+ } : void 0);
1441
+ if (!fetch) throw cancel("NATIVE_ADMISSION_INVALID");
1442
+ let controller;
1443
+ try {
1444
+ controller = factory({ binding: input.binding, fetch, now: input.now, sessionIdFactory });
1445
+ } catch {
1446
+ throw cancel("NATIVE_ADMISSION_INVALID");
1447
+ }
1448
+ pendingControllers.add(controller);
1449
+ const mapId = input.binding.kind === "hosted" ? input.binding.mapId : null;
1450
+ let orphan;
1451
+ try {
1452
+ const registered = await bridge.registerContext(ack.installation, { mapId, resources });
1453
+ if (!registered || !isNativeToken(registered.context) || !Number.isSafeInteger(registered.generation) || registered.generation < 1 || contexts.has(registered.context)) {
1454
+ void dispose().catch(() => void 0);
1455
+ throw cancel("NATIVE_ADMISSION_INVALID");
1456
+ }
1457
+ orphan = registered.context;
1458
+ if (disposed) throw cancel();
1459
+ if (options.sessionFetch) boundFetch = options.sessionFetch(registered.context);
1460
+ let retirement;
1461
+ let retirementNotified = false;
1462
+ const catalog = createNativeAdmissionCatalog(resources, registered.context, (additions) => {
1463
+ if (!bridge.extendContext) return Promise.reject(cancel("NATIVE_ADMISSION_UNAVAILABLE"));
1464
+ return bridge.extendContext(ack.installation, registered.context, additions);
1465
+ });
1466
+ const context = {
1467
+ id: registered.context,
1468
+ generation: registered.generation,
1469
+ mapId,
1470
+ live: true,
1471
+ lastBatch: 0,
1472
+ batch: null,
1473
+ tickets: /* @__PURE__ */ new Map(),
1474
+ catalog,
1475
+ controller,
1476
+ handle: void 0
1477
+ };
1478
+ context.handle = Object.freeze({
1479
+ context: context.id,
1480
+ generation: context.generation,
1481
+ scope: Object.freeze({ installation: ack.installation, context: context.id }),
1482
+ get state() {
1483
+ return Object.freeze({
1484
+ status: context.live ? "active" : "retired",
1485
+ context: context.id
1486
+ });
1487
+ },
1488
+ async prepare() {
1489
+ if (!context.live || disposed) throw cancel();
1490
+ if (context.mapId === null) return null;
1491
+ if (!foreground || !controller.prepare) throw cancel("NATIVE_ADMISSION_UNAVAILABLE");
1492
+ try {
1493
+ const policy = await controller.prepare();
1494
+ if (!context.live || disposed) throw cancel();
1495
+ if (!foreground || !policy || policy.mapId !== context.mapId)
1496
+ throw cancel("NATIVE_ADMISSION_DENIED");
1497
+ return policy;
1498
+ } catch {
1499
+ throw cancel(
1500
+ context.live && !disposed ? "NATIVE_ADMISSION_DENIED" : "NATIVE_ADMISSION_CANCELLED"
1501
+ );
1502
+ }
1503
+ },
1504
+ discriminate: catalog.discriminate,
1505
+ discriminateForTest: catalog.discriminate,
1506
+ extendResources: catalog.extend,
1507
+ retire() {
1508
+ if (retirement) return retirement;
1509
+ context.live = false;
1510
+ context.batch = null;
1511
+ context.tickets.clear();
1512
+ catalog.retire();
1513
+ contexts.delete(context.id);
1514
+ controller.dispose();
1515
+ boundFetch = void 0;
1516
+ const attempt = Promise.resolve().then(async () => {
1517
+ try {
1518
+ const retired = await bridge.retireContext(ack.installation, context.id);
1519
+ if (retired?.retired !== true) throw cancel("NATIVE_ADMISSION_UNAVAILABLE");
1520
+ return Object.freeze({ retired: true });
1521
+ } catch {
1522
+ throw cancel("NATIVE_ADMISSION_UNAVAILABLE");
1523
+ }
1524
+ });
1525
+ retirement = attempt;
1526
+ void attempt.catch(() => {
1527
+ if (retirement === attempt) retirement = void 0;
1528
+ });
1529
+ if (!retirementNotified) {
1530
+ retirementNotified = true;
1531
+ try {
1532
+ input.onRetired?.();
1533
+ } catch {
1534
+ }
1535
+ }
1536
+ return attempt;
1537
+ }
1538
+ });
1539
+ if (!foreground) controller.background();
1540
+ contexts.set(context.id, context);
1541
+ orphan = void 0;
1542
+ return context.handle;
1543
+ } catch (error) {
1544
+ controller.dispose();
1545
+ boundFetch = void 0;
1546
+ if (orphan) {
1547
+ try {
1548
+ await bridge.retireContext(ack.installation, orphan);
1549
+ } catch {
1550
+ void dispose().catch(() => void 0);
1551
+ }
1552
+ }
1553
+ throw error instanceof NativeAdmissionError ? error : cancel("NATIVE_ADMISSION_UNAVAILABLE");
1554
+ } finally {
1555
+ pendingControllers.delete(controller);
1556
+ }
1557
+ }
1558
+ function dispose() {
1559
+ if (disposal) return disposal;
1560
+ disposed = true;
1561
+ status = "disposed";
1562
+ const retiring = [...contexts.values()].map(
1563
+ (context) => retire(context).catch(() => void 0)
1564
+ );
1565
+ for (const controller of pendingControllers) controller.dispose();
1566
+ const attempt = (async () => {
1567
+ try {
1568
+ let id;
1569
+ try {
1570
+ id = await installed;
1571
+ } catch {
1572
+ }
1573
+ await Promise.all(retiring);
1574
+ if (!id) return Object.freeze({ removed: false, ownershipLost: false });
1575
+ try {
1576
+ const ack = await bridge.remove(id);
1577
+ if (!ack || typeof ack.removed !== "boolean" || typeof ack.ownershipLost !== "boolean")
1578
+ throw cancel("NATIVE_ADMISSION_INVALID");
1579
+ return Object.freeze({ removed: ack.removed, ownershipLost: ack.ownershipLost });
1580
+ } catch {
1581
+ throw cancel("NATIVE_ADMISSION_UNAVAILABLE");
1582
+ }
1583
+ } finally {
1584
+ unsubscribe();
1585
+ }
1586
+ })();
1587
+ disposal = attempt;
1588
+ void attempt.catch(() => {
1589
+ if (disposal === attempt) disposal = void 0;
1590
+ });
1591
+ return attempt;
1592
+ }
1593
+ return Object.freeze({
1594
+ install,
1595
+ openMap,
1596
+ async replaceMap(previous, input) {
1597
+ if (disposed) throw cancel();
1598
+ await previous.retire();
1599
+ return openMap(input);
1600
+ },
1601
+ dispose,
1602
+ get state() {
1603
+ return Object.freeze({
1604
+ status,
1605
+ contexts: contexts.size,
1606
+ pendingRegistrations: pendingControllers.size
1607
+ });
1608
+ }
1609
+ });
1610
+ }
1611
+ export {
1612
+ NativeAdmissionError,
1613
+ createNativeAdmissionOwner
1614
+ };