@tileflow/react-native 0.1.0-alpha.6 → 0.1.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -14
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrap.kt +7 -3
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrapOkHttpNetwork.kt +3 -1
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionEngine.kt +4 -2
- package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeAdmissionModule.kt +1 -1
- package/dist/{contract-By3URNxS.d.ts → contract-BWT1jxqg.d.ts} +44 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +503 -55
- package/dist/internal/camera.d.ts +1 -1
- package/dist/internal/initial-view.d.ts +1 -1
- package/dist/internal/native-admission-bridge.js +8 -3
- package/dist/internal/native-admission.d.ts +15 -1
- package/dist/internal/native-admission.js +208 -21
- package/dist/internal/native-appearance.d.ts +1 -1
- package/dist/internal/session.d.ts +9 -2
- package/dist/internal/session.js +178 -21
- package/dist/internal/source-state.d.ts +1 -1
- package/ios/TFAdmissionBootstrap.mm +12 -6
- package/ios/TFAdmissionEngine.mm +4 -2
- package/package.json +3 -4
- package/docs/native-admission.md +0 -108
- package/docs/native-configuration.md +0 -210
- package/docs/native-interactions.md +0 -161
- package/docs/native-location.md +0 -148
package/dist/index.js
CHANGED
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
Fragment,
|
|
19
19
|
useImperativeHandle,
|
|
20
|
-
useLayoutEffect as
|
|
21
|
-
useMemo as
|
|
20
|
+
useLayoutEffect as useLayoutEffect3,
|
|
21
|
+
useMemo as useMemo3,
|
|
22
22
|
useRef,
|
|
23
|
-
useState,
|
|
23
|
+
useState as useState2,
|
|
24
24
|
useSyncExternalStore
|
|
25
25
|
} from "react";
|
|
26
26
|
import { AppState, findNodeHandle, View as View2 } from "react-native";
|
|
@@ -1268,6 +1268,9 @@ function createMapLifecycle(create, retire) {
|
|
|
1268
1268
|
getSourceState() {
|
|
1269
1269
|
return active?.owner.getSourceState();
|
|
1270
1270
|
},
|
|
1271
|
+
retry() {
|
|
1272
|
+
active?.owner.retry();
|
|
1273
|
+
},
|
|
1271
1274
|
subscribe(listener) {
|
|
1272
1275
|
listeners.add(listener);
|
|
1273
1276
|
return () => listeners.delete(listener);
|
|
@@ -1350,9 +1353,157 @@ function createMapLifecycle(create, retire) {
|
|
|
1350
1353
|
});
|
|
1351
1354
|
}
|
|
1352
1355
|
|
|
1356
|
+
// src/native-boundary-feature-state.tsx
|
|
1357
|
+
import { VectorSource } from "@maplibre/maplibre-react-native";
|
|
1358
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
1359
|
+
import {
|
|
1360
|
+
createBoundaryFeatureState,
|
|
1361
|
+
tileflowBoundaryFamilies
|
|
1362
|
+
} from "@tileflow/core/native";
|
|
1363
|
+
|
|
1364
|
+
// src/native-boundary-feature-state-owner.ts
|
|
1365
|
+
var retryDelaysMs = [16, 50, 150, 400, 1e3];
|
|
1366
|
+
var timer = (run, delayMs) => {
|
|
1367
|
+
const handle = setTimeout(run, delayMs);
|
|
1368
|
+
return () => clearTimeout(handle);
|
|
1369
|
+
};
|
|
1370
|
+
function createNativeBoundaryFeatureStateOwner(sourceId, schedule = timer) {
|
|
1371
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1372
|
+
let source = null;
|
|
1373
|
+
let style = {};
|
|
1374
|
+
let loaded = false;
|
|
1375
|
+
let replacing = false;
|
|
1376
|
+
const present = () => !replacing && loaded && source !== null && declaredVectorSource(style, sourceId);
|
|
1377
|
+
let notified = false;
|
|
1378
|
+
const notify = () => {
|
|
1379
|
+
const next = present();
|
|
1380
|
+
if (next === notified) return;
|
|
1381
|
+
notified = next;
|
|
1382
|
+
for (const listener of [...listeners]) listener();
|
|
1383
|
+
};
|
|
1384
|
+
const absentThenNotify = () => {
|
|
1385
|
+
replacing = true;
|
|
1386
|
+
notify();
|
|
1387
|
+
replacing = false;
|
|
1388
|
+
notify();
|
|
1389
|
+
};
|
|
1390
|
+
let attempt = 0;
|
|
1391
|
+
let cancelRetry;
|
|
1392
|
+
const reset = () => {
|
|
1393
|
+
cancelRetry?.();
|
|
1394
|
+
cancelRetry = void 0;
|
|
1395
|
+
attempt = 0;
|
|
1396
|
+
};
|
|
1397
|
+
const rejected = (target) => {
|
|
1398
|
+
if (cancelRetry || target !== source || !present() || attempt >= retryDelaysMs.length) return;
|
|
1399
|
+
cancelRetry = schedule(() => {
|
|
1400
|
+
cancelRetry = void 0;
|
|
1401
|
+
if (target === source && present()) absentThenNotify();
|
|
1402
|
+
}, retryDelaysMs[attempt++]);
|
|
1403
|
+
};
|
|
1404
|
+
const write = (operation) => {
|
|
1405
|
+
const target = source;
|
|
1406
|
+
if (!target) return;
|
|
1407
|
+
void operation(target).then(
|
|
1408
|
+
() => {
|
|
1409
|
+
if (target === source && !cancelRetry) attempt = 0;
|
|
1410
|
+
},
|
|
1411
|
+
() => rejected(target)
|
|
1412
|
+
);
|
|
1413
|
+
};
|
|
1414
|
+
const map = {
|
|
1415
|
+
getSource: (id) => id === sourceId && present() ? {} : void 0,
|
|
1416
|
+
getStyle: () => style,
|
|
1417
|
+
on: (_type, listener) => listeners.add(listener),
|
|
1418
|
+
off: (_type, listener) => listeners.delete(listener),
|
|
1419
|
+
setFeatureState({ id, sourceLayer }, state) {
|
|
1420
|
+
if (!sourceLayer) return;
|
|
1421
|
+
write((target) => target.setFeatureState({ id, sourceLayer }, state));
|
|
1422
|
+
},
|
|
1423
|
+
removeFeatureState({ id, sourceLayer }, key2) {
|
|
1424
|
+
if (!sourceLayer || id === void 0) return;
|
|
1425
|
+
write((target) => target.removeFeatureState({ id, sourceLayer }, key2));
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
return Object.freeze({
|
|
1429
|
+
map,
|
|
1430
|
+
/** A newly committed style is loading until MapLibre reports it loaded. */
|
|
1431
|
+
setStyle(next, nextLoaded) {
|
|
1432
|
+
if (next !== style) {
|
|
1433
|
+
reset();
|
|
1434
|
+
replacing = true;
|
|
1435
|
+
notify();
|
|
1436
|
+
replacing = false;
|
|
1437
|
+
}
|
|
1438
|
+
style = next;
|
|
1439
|
+
loaded = nextLoaded;
|
|
1440
|
+
notify();
|
|
1441
|
+
},
|
|
1442
|
+
setSource(next) {
|
|
1443
|
+
if (next !== source) reset();
|
|
1444
|
+
source = next;
|
|
1445
|
+
notify();
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
function declaredVectorSource(style, id) {
|
|
1450
|
+
const value = style.sources?.[id];
|
|
1451
|
+
return value?.type === "vector";
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/native-boundary-feature-state.tsx
|
|
1455
|
+
import { jsx } from "react/jsx-runtime";
|
|
1456
|
+
function NativeBoundaryFeatureState({
|
|
1457
|
+
input,
|
|
1458
|
+
style,
|
|
1459
|
+
loaded
|
|
1460
|
+
}) {
|
|
1461
|
+
const family = input.family ?? "boundaries";
|
|
1462
|
+
const sourceId = Object.hasOwn(tileflowBoundaryFamilies, family) ? tileflowBoundaryFamilies[family].sourceId : family;
|
|
1463
|
+
const owner = useMemo(() => createNativeBoundaryFeatureStateOwner(sourceId), [sourceId]);
|
|
1464
|
+
const [controller, setController] = useState(null);
|
|
1465
|
+
const [adopted, setAdopted] = useState(false);
|
|
1466
|
+
const bind2 = useCallback(
|
|
1467
|
+
(node) => {
|
|
1468
|
+
owner.setSource(node);
|
|
1469
|
+
setAdopted(node !== null);
|
|
1470
|
+
},
|
|
1471
|
+
[owner]
|
|
1472
|
+
);
|
|
1473
|
+
useLayoutEffect(() => owner.setStyle(style, loaded), [owner, style, loaded]);
|
|
1474
|
+
const { labels, state, values } = input;
|
|
1475
|
+
useEffect(() => {
|
|
1476
|
+
const created = createBoundaryFeatureState(owner.map, {
|
|
1477
|
+
family,
|
|
1478
|
+
...labels === void 0 ? {} : { labels },
|
|
1479
|
+
...state === void 0 ? {} : { state }
|
|
1480
|
+
});
|
|
1481
|
+
setController(created);
|
|
1482
|
+
return () => {
|
|
1483
|
+
setController(null);
|
|
1484
|
+
created.dispose();
|
|
1485
|
+
};
|
|
1486
|
+
}, [owner, family, labels, state]);
|
|
1487
|
+
useEffect(() => {
|
|
1488
|
+
if (!controller) return;
|
|
1489
|
+
if (values) controller.set(values);
|
|
1490
|
+
else controller.clear();
|
|
1491
|
+
}, [controller, values]);
|
|
1492
|
+
if (!declaredVectorSource(style, sourceId) || !loaded && !adopted) return null;
|
|
1493
|
+
const tiles = (style.sources?.[sourceId]).tiles;
|
|
1494
|
+
return /* @__PURE__ */ jsx(
|
|
1495
|
+
VectorSource,
|
|
1496
|
+
{
|
|
1497
|
+
id: sourceId,
|
|
1498
|
+
ref: bind2,
|
|
1499
|
+
...Array.isArray(tiles) ? { tiles } : {}
|
|
1500
|
+
}
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1353
1504
|
// src/native-marker.tsx
|
|
1354
1505
|
import { Marker as NativeMarker } from "@maplibre/maplibre-react-native";
|
|
1355
|
-
import { useLayoutEffect, useMemo } from "react";
|
|
1506
|
+
import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo2 } from "react";
|
|
1356
1507
|
import { Pressable, processColor, View } from "react-native";
|
|
1357
1508
|
|
|
1358
1509
|
// src/native-marker-model.ts
|
|
@@ -1399,7 +1550,7 @@ function resolveNativeMarkerContent(context, render) {
|
|
|
1399
1550
|
}
|
|
1400
1551
|
|
|
1401
1552
|
// src/native-marker.tsx
|
|
1402
|
-
import { jsx } from "react/jsx-runtime";
|
|
1553
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1403
1554
|
var button = Object.freeze({
|
|
1404
1555
|
minWidth: 44,
|
|
1405
1556
|
minHeight: 44,
|
|
@@ -1421,7 +1572,7 @@ function NativeAnnotationMarker({
|
|
|
1421
1572
|
lifecycle,
|
|
1422
1573
|
renderMarker
|
|
1423
1574
|
}) {
|
|
1424
|
-
const model =
|
|
1575
|
+
const model = useMemo2(() => nativeMarkerModel(annotation, enabled), [annotation, enabled]);
|
|
1425
1576
|
const rendered = resolveNativeMarkerContent(model.context, renderMarker);
|
|
1426
1577
|
let color = defaultColor;
|
|
1427
1578
|
let invalidColor = false;
|
|
@@ -1435,14 +1586,14 @@ function NativeAnnotationMarker({
|
|
|
1435
1586
|
}
|
|
1436
1587
|
}
|
|
1437
1588
|
const diagnostic = rendered.diagnostic ?? (!rendered.content && invalidColor ? "INVALID_FIELD" : void 0);
|
|
1438
|
-
|
|
1589
|
+
useLayoutEffect2(() => {
|
|
1439
1590
|
if (diagnostic) lifecycle.interactionDiagnostic(sceneKey, diagnostic);
|
|
1440
1591
|
}, [annotation, diagnostic, lifecycle, sceneKey]);
|
|
1441
1592
|
const press = (event) => {
|
|
1442
1593
|
event?.stopPropagation();
|
|
1443
1594
|
lifecycle.markerPress(sceneKey, annotation);
|
|
1444
1595
|
};
|
|
1445
|
-
return /* @__PURE__ */
|
|
1596
|
+
return /* @__PURE__ */ jsx2(
|
|
1446
1597
|
NativeMarker,
|
|
1447
1598
|
{
|
|
1448
1599
|
id: `tileflow-annotation-${model.key}`,
|
|
@@ -1453,7 +1604,7 @@ function NativeAnnotationMarker({
|
|
|
1453
1604
|
lifecycle.beginTouch(sceneKey);
|
|
1454
1605
|
press();
|
|
1455
1606
|
},
|
|
1456
|
-
children: /* @__PURE__ */
|
|
1607
|
+
children: /* @__PURE__ */ jsx2(
|
|
1457
1608
|
Pressable,
|
|
1458
1609
|
{
|
|
1459
1610
|
accessible: false,
|
|
@@ -1464,14 +1615,14 @@ function NativeAnnotationMarker({
|
|
|
1464
1615
|
event.stopPropagation();
|
|
1465
1616
|
},
|
|
1466
1617
|
onPress: press,
|
|
1467
|
-
children: /* @__PURE__ */
|
|
1618
|
+
children: /* @__PURE__ */ jsx2(
|
|
1468
1619
|
View,
|
|
1469
1620
|
{
|
|
1470
1621
|
pointerEvents: "none",
|
|
1471
1622
|
accessible: false,
|
|
1472
1623
|
accessibilityElementsHidden: true,
|
|
1473
1624
|
importantForAccessibility: "no-hide-descendants",
|
|
1474
|
-
children: rendered.content ?? /* @__PURE__ */
|
|
1625
|
+
children: rendered.content ?? /* @__PURE__ */ jsx2(View, { style: [dot, { backgroundColor: color }] })
|
|
1475
1626
|
}
|
|
1476
1627
|
)
|
|
1477
1628
|
}
|
|
@@ -1809,6 +1960,13 @@ var REFRESH_MARGIN_MS = 3e4;
|
|
|
1809
1960
|
var ROTATION_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
1810
1961
|
var ROTATION_REQUEST_LIMIT = 1e4;
|
|
1811
1962
|
var FUTURE_ISSUE_SKEW_MS = 3e4;
|
|
1963
|
+
var RETRY_INITIAL_MS = 1e3;
|
|
1964
|
+
var RETRY_LIMIT_MS = 6e4;
|
|
1965
|
+
var RETRY_AFTER_LIMIT_MS = 15 * 60 * 1e3;
|
|
1966
|
+
var RETRY_AFTER_SECONDS = /^\d{1,10}$/u;
|
|
1967
|
+
var QUOTA_CODES = /* @__PURE__ */ new Set(["COMMERCIAL_MAP_LIMIT_REACHED", "COMMERCIAL_SPENDING_LIMIT_REACHED"]);
|
|
1968
|
+
var SERVICE_CODE = /^[A-Z][A-Z0-9_]{0,63}$/u;
|
|
1969
|
+
var SERVICE_CODE_BODY_LIMIT = 4096;
|
|
1812
1970
|
var MOBILE_CREDENTIAL = /^tf_public_[0-9a-f]{48}$/u;
|
|
1813
1971
|
var MAP_ID = /^map_[A-Za-z0-9_-]{16}$/u;
|
|
1814
1972
|
var CREDENTIAL_ID = /^key_[A-Za-z0-9_-]{8,80}$/u;
|
|
@@ -1847,6 +2005,14 @@ var SUCCESS_KEYS = /* @__PURE__ */ new Set([
|
|
|
1847
2005
|
"tilesetIds"
|
|
1848
2006
|
]);
|
|
1849
2007
|
var RESTART_KEYS = /* @__PURE__ */ new Set(["code", "error", "retryWithNewSession", "sessionId"]);
|
|
2008
|
+
var TERMINAL_CODES = /* @__PURE__ */ new Set([
|
|
2009
|
+
"NATIVE_SESSION_INPUT_INVALID",
|
|
2010
|
+
"NATIVE_SESSION_CREDENTIAL_REJECTED",
|
|
2011
|
+
"NATIVE_SESSION_MAP_MISMATCH",
|
|
2012
|
+
"NATIVE_SESSION_MAP_NOT_FOUND",
|
|
2013
|
+
"NATIVE_SESSION_QUOTA_EXCEEDED",
|
|
2014
|
+
"NATIVE_SESSION_REJECTED"
|
|
2015
|
+
]);
|
|
1850
2016
|
var ERROR_MESSAGES = {
|
|
1851
2017
|
NATIVE_SESSION_INPUT_INVALID: "Native session configuration is invalid.",
|
|
1852
2018
|
NATIVE_SESSION_RESPONSE_TOO_LARGE: "Native session response exceeded its byte limit.",
|
|
@@ -1858,18 +2024,22 @@ var ERROR_MESSAGES = {
|
|
|
1858
2024
|
NATIVE_SESSION_MAP_NOT_FOUND: "Native session Map was not found.",
|
|
1859
2025
|
NATIVE_SESSION_QUOTA_EXCEEDED: "Native session quota was exceeded.",
|
|
1860
2026
|
NATIVE_SESSION_REJECTED: "Native session request was rejected.",
|
|
2027
|
+
NATIVE_SESSION_RATE_LIMITED: "Native session requests are rate limited.",
|
|
1861
2028
|
NATIVE_SESSION_UNAVAILABLE: "Native session service is unavailable.",
|
|
1862
2029
|
NATIVE_SESSION_REPLACED: "Native session binding was replaced.",
|
|
1863
2030
|
NATIVE_SESSION_DISPOSED: "Native session controller is disposed."
|
|
1864
2031
|
};
|
|
1865
2032
|
var HostedNativeSessionError = class extends Error {
|
|
1866
|
-
constructor(code) {
|
|
2033
|
+
constructor(code, reason) {
|
|
1867
2034
|
super(ERROR_MESSAGES[code]);
|
|
1868
2035
|
__publicField(this, "code");
|
|
1869
2036
|
__publicField(this, "kind");
|
|
1870
2037
|
this.name = "HostedNativeSessionError";
|
|
1871
2038
|
this.code = code;
|
|
1872
2039
|
this.kind = errorKind(code);
|
|
2040
|
+
if (code === "NATIVE_SESSION_REJECTED" && typeof reason === "string" && SERVICE_CODE.test(reason)) {
|
|
2041
|
+
this.reason = reason;
|
|
2042
|
+
}
|
|
1873
2043
|
}
|
|
1874
2044
|
};
|
|
1875
2045
|
function createHostedNativeSessionController(input) {
|
|
@@ -1979,7 +2149,29 @@ function createHostedNativeSessionController(input) {
|
|
|
1979
2149
|
})
|
|
1980
2150
|
);
|
|
1981
2151
|
}
|
|
2152
|
+
let terminal = null;
|
|
2153
|
+
let failures = 0;
|
|
2154
|
+
let retryAt = null;
|
|
2155
|
+
let retryError = null;
|
|
2156
|
+
function clearRetry() {
|
|
2157
|
+
failures = 0;
|
|
2158
|
+
retryAt = null;
|
|
2159
|
+
retryError = null;
|
|
2160
|
+
}
|
|
2161
|
+
function delayRetry(error) {
|
|
2162
|
+
let at;
|
|
2163
|
+
try {
|
|
2164
|
+
at = readClock();
|
|
2165
|
+
} catch {
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
if (retryAt === null || at >= retryAt) failures = Math.min(failures + 1, 16);
|
|
2169
|
+
const backoff = Math.min(RETRY_INITIAL_MS * 2 ** (failures - 1), RETRY_LIMIT_MS);
|
|
2170
|
+
retryAt = Math.max(retryAt ?? at, at + Math.max(backoff, retryAfterDelays.get(error) ?? 0));
|
|
2171
|
+
retryError = safeError(error);
|
|
2172
|
+
}
|
|
1982
2173
|
function rotateIfRequired(at) {
|
|
2174
|
+
if (terminal) throw errorFrom(terminal);
|
|
1983
2175
|
if (!activeSession || binding.kind !== "hosted") return;
|
|
1984
2176
|
if (at - activeSession.startedAt < ROTATION_AGE_MS && activeSession.requestCount < ROTATION_REQUEST_LIMIT) {
|
|
1985
2177
|
return;
|
|
@@ -2013,6 +2205,7 @@ function createHostedNativeSessionController(input) {
|
|
|
2013
2205
|
return current;
|
|
2014
2206
|
}
|
|
2015
2207
|
async function ensureAuthority(session, at) {
|
|
2208
|
+
if (terminal) throw errorFrom(terminal);
|
|
2016
2209
|
if (session.authority && session.authority.clockEpoch === clock.epoch && session.authority.validUntil - at > REFRESH_MARGIN_MS) {
|
|
2017
2210
|
return { authority: session.authority, session };
|
|
2018
2211
|
}
|
|
@@ -2020,20 +2213,28 @@ function createHostedNativeSessionController(input) {
|
|
|
2020
2213
|
if (binding.kind !== "hosted") {
|
|
2021
2214
|
throw new HostedNativeSessionError("NATIVE_SESSION_REPLACED");
|
|
2022
2215
|
}
|
|
2216
|
+
if (retryAt !== null && retryError && at < retryAt) {
|
|
2217
|
+
if (session.authority && session.authority.clockEpoch === clock.epoch && session.authority.validUntil > at) {
|
|
2218
|
+
return { authority: session.authority, session };
|
|
2219
|
+
}
|
|
2220
|
+
throw errorFrom(retryError);
|
|
2221
|
+
}
|
|
2023
2222
|
const operationBinding = binding;
|
|
2024
2223
|
const abort = new SessionAbortController();
|
|
2025
2224
|
operations.add(abort);
|
|
2026
2225
|
const operation = {
|
|
2027
2226
|
abort,
|
|
2028
2227
|
promise: Promise.resolve().then(async () => {
|
|
2029
|
-
session.error = null;
|
|
2030
|
-
publishSession(session, "loading");
|
|
2031
|
-
if (abort.signal.aborted) throw abort.error();
|
|
2032
2228
|
try {
|
|
2229
|
+
if (abort.signal.aborted) throw abort.error();
|
|
2230
|
+
session.error = null;
|
|
2231
|
+
publishSession(session, "loading");
|
|
2232
|
+
if (abort.signal.aborted) throw abort.error();
|
|
2033
2233
|
const result = await bootstrap(session, operationBinding, abort, true, true);
|
|
2034
2234
|
if (abort.signal.aborted) throw abort.error();
|
|
2035
2235
|
result.session.authority = result.authority;
|
|
2036
2236
|
result.session.error = null;
|
|
2237
|
+
if (binding === operationBinding) clearRetry();
|
|
2037
2238
|
if (activeSession === result.session) {
|
|
2038
2239
|
publishSession(result.session, "ready");
|
|
2039
2240
|
if (abort.signal.aborted) throw abort.error();
|
|
@@ -2042,8 +2243,26 @@ function createHostedNativeSessionController(input) {
|
|
|
2042
2243
|
} catch (error) {
|
|
2043
2244
|
const normalized = abort.signal.aborted ? abort.error() : normalizeError(error);
|
|
2044
2245
|
const target = latestSession(session);
|
|
2246
|
+
if (normalized.kind === "terminal" && binding === operationBinding && !terminal) {
|
|
2247
|
+
const failure3 = safeError(normalized);
|
|
2248
|
+
terminal = failure3;
|
|
2249
|
+
const rejectedSession = activeSession;
|
|
2250
|
+
if (rejectedSession) {
|
|
2251
|
+
rejectedSession.authority = null;
|
|
2252
|
+
rejectedSession.error = failure3;
|
|
2253
|
+
}
|
|
2254
|
+
for (const pending of [...operations]) {
|
|
2255
|
+
pending.abort(failure3.code, failure3.reason);
|
|
2256
|
+
}
|
|
2257
|
+
if (binding === operationBinding && rejectedSession) {
|
|
2258
|
+
publishSession(rejectedSession, "error", failure3);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
if (normalized.kind === "recoverable" && binding === operationBinding && !terminal) {
|
|
2262
|
+
delayRetry(normalized);
|
|
2263
|
+
}
|
|
2045
2264
|
if (normalized.code !== "NATIVE_SESSION_REPLACED" && normalized.code !== "NATIVE_SESSION_DISPOSED") {
|
|
2046
|
-
target.error =
|
|
2265
|
+
target.error = safeError(normalized);
|
|
2047
2266
|
publishSession(target, "error", target.error);
|
|
2048
2267
|
}
|
|
2049
2268
|
throw normalized;
|
|
@@ -2069,20 +2288,29 @@ function createHostedNativeSessionController(input) {
|
|
|
2069
2288
|
if (abort.signal.aborted) throw abort.error();
|
|
2070
2289
|
const expectedSurfaceId = session.authority?.public.surfaceId ?? session.requestedSurfaceId;
|
|
2071
2290
|
const response = await fetchResponse(hostedBinding, session, expectedSurfaceId, abort);
|
|
2072
|
-
const source = await readBoundedResponse(response, abort);
|
|
2291
|
+
const { source, size } = await readBoundedResponse(response, abort);
|
|
2073
2292
|
let body;
|
|
2074
2293
|
try {
|
|
2075
2294
|
body = source.length ? JSON.parse(source) : null;
|
|
2076
2295
|
} catch {
|
|
2077
|
-
|
|
2296
|
+
if (response.status === 201) {
|
|
2297
|
+
throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_JSON_INVALID");
|
|
2298
|
+
}
|
|
2299
|
+
body = null;
|
|
2078
2300
|
}
|
|
2079
|
-
if (response.status === 409 &&
|
|
2301
|
+
if (response.status === 409 && isExactRestart(body, session.sessionId)) {
|
|
2302
|
+
if (!allowRestart) throw new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE");
|
|
2080
2303
|
assertCurrentSession(session, hostedBinding);
|
|
2081
2304
|
const replacement = redirectSession(session, readClock());
|
|
2082
2305
|
if (abort.signal.aborted) throw abort.error();
|
|
2083
2306
|
return bootstrap(replacement, hostedBinding, abort, false, false);
|
|
2084
2307
|
}
|
|
2085
|
-
if (response.status !== 201)
|
|
2308
|
+
if (response.status !== 201) {
|
|
2309
|
+
const failure3 = statusError(response.status, body, size);
|
|
2310
|
+
const delay = retryAfterMs(response.headers, now);
|
|
2311
|
+
if (delay !== null) retryAfterDelays.set(failure3, delay);
|
|
2312
|
+
throw failure3;
|
|
2313
|
+
}
|
|
2086
2314
|
if (!hasNoStore(response.headers.get("cache-control"))) {
|
|
2087
2315
|
throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
|
|
2088
2316
|
}
|
|
@@ -2155,6 +2383,16 @@ function createHostedNativeSessionController(input) {
|
|
|
2155
2383
|
if (binding.kind === "direct" || !activeSession) {
|
|
2156
2384
|
return Object.freeze({ status: "idle", source: "direct", lifecycle });
|
|
2157
2385
|
}
|
|
2386
|
+
if (terminal) {
|
|
2387
|
+
return Object.freeze({
|
|
2388
|
+
status: "error",
|
|
2389
|
+
source: "hosted",
|
|
2390
|
+
lifecycle,
|
|
2391
|
+
mapId: binding.mapId,
|
|
2392
|
+
sessionId: activeSession.sessionId,
|
|
2393
|
+
error: terminal
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2158
2396
|
if (activeSession.operation) {
|
|
2159
2397
|
return Object.freeze({
|
|
2160
2398
|
status: "loading",
|
|
@@ -2206,6 +2444,7 @@ function createHostedNativeSessionController(input) {
|
|
|
2206
2444
|
if (bindingGeneration !== expectedGeneration) {
|
|
2207
2445
|
throw new HostedNativeSessionError("NATIVE_SESSION_REPLACED");
|
|
2208
2446
|
}
|
|
2447
|
+
if (terminal) throw errorFrom(terminal);
|
|
2209
2448
|
checkedAt = readClock();
|
|
2210
2449
|
if (resolved.authority.clockEpoch === clock.epoch && resolved.authority.validUntil > checkedAt) {
|
|
2211
2450
|
admittedAuthorities.set(resolved.authority.public, resolved.authority);
|
|
@@ -2260,7 +2499,7 @@ function createHostedNativeSessionController(input) {
|
|
|
2260
2499
|
}
|
|
2261
2500
|
},
|
|
2262
2501
|
transportBudget(authority) {
|
|
2263
|
-
if (disposed || lifecycle !== "foreground") return 0;
|
|
2502
|
+
if (disposed || terminal || lifecycle !== "foreground") return 0;
|
|
2264
2503
|
const stored = admittedAuthorities.get(authority);
|
|
2265
2504
|
if (!stored || stored.bindingGeneration !== bindingGeneration) return 0;
|
|
2266
2505
|
try {
|
|
@@ -2279,6 +2518,11 @@ function createHostedNativeSessionController(input) {
|
|
|
2279
2518
|
async resume() {
|
|
2280
2519
|
if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
|
|
2281
2520
|
lifecycle = "foreground";
|
|
2521
|
+
if (terminal) {
|
|
2522
|
+
const error = errorFrom(terminal);
|
|
2523
|
+
publish(currentSnapshot());
|
|
2524
|
+
throw error;
|
|
2525
|
+
}
|
|
2282
2526
|
if (binding.kind === "direct" || !activeSession) {
|
|
2283
2527
|
publish(currentSnapshot());
|
|
2284
2528
|
return;
|
|
@@ -2301,9 +2545,27 @@ function createHostedNativeSessionController(input) {
|
|
|
2301
2545
|
bindingGeneration += 1;
|
|
2302
2546
|
for (const operation of operations) operation.abort("NATIVE_SESSION_REPLACED");
|
|
2303
2547
|
binding = next;
|
|
2548
|
+
terminal = null;
|
|
2549
|
+
clearRetry();
|
|
2304
2550
|
activeSession = next.kind === "hosted" ? createSession(next.surfaceId, readClock()) : null;
|
|
2305
2551
|
publish(snapshotIdle(binding, activeSession, lifecycle));
|
|
2306
2552
|
},
|
|
2553
|
+
retry() {
|
|
2554
|
+
if (disposed) throw new HostedNativeSessionError("NATIVE_SESSION_DISPOSED");
|
|
2555
|
+
if (!terminal && retryAt === null) return;
|
|
2556
|
+
terminal = null;
|
|
2557
|
+
clearRetry();
|
|
2558
|
+
if (activeSession && !activeSession.operation) activeSession.error = null;
|
|
2559
|
+
publish(currentSnapshot());
|
|
2560
|
+
},
|
|
2561
|
+
retryDelay() {
|
|
2562
|
+
if (disposed || terminal || retryAt === null) return null;
|
|
2563
|
+
try {
|
|
2564
|
+
return Math.max(0, Math.ceil(retryAt - readClock()));
|
|
2565
|
+
} catch {
|
|
2566
|
+
return null;
|
|
2567
|
+
}
|
|
2568
|
+
},
|
|
2307
2569
|
subscribe(listener) {
|
|
2308
2570
|
if (typeof listener !== "function") {
|
|
2309
2571
|
throw new HostedNativeSessionError("NATIVE_SESSION_INPUT_INVALID");
|
|
@@ -2398,7 +2660,8 @@ function parseSuccess(value, binding, expectedSessionId, expectedSurfaceId) {
|
|
|
2398
2660
|
const issuedAtMs = parseExactIso(value.issuedAt);
|
|
2399
2661
|
const serverTimeMs = parseExactIso(value.serverTime);
|
|
2400
2662
|
const expiresAtMs = parseExactIso(value.expiresAt);
|
|
2401
|
-
if (issuedAtMs > serverTimeMs + FUTURE_ISSUE_SKEW_MS || expiresAtMs <= serverTimeMs || expiresAtMs <= issuedAtMs || expiresAtMs - issuedAtMs > MAX_GRANT_LIFETIME_MS ||
|
|
2663
|
+
if (issuedAtMs > serverTimeMs + FUTURE_ISSUE_SKEW_MS || expiresAtMs <= serverTimeMs || expiresAtMs <= issuedAtMs || expiresAtMs - issuedAtMs > MAX_GRANT_LIFETIME_MS || // The local deadline is derived from serverTime, so this also bounds the transport budget.
|
|
2664
|
+
expiresAtMs - serverTimeMs > MAX_GRANT_LIFETIME_MS) {
|
|
2402
2665
|
throw new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
|
|
2403
2666
|
}
|
|
2404
2667
|
const authority = {
|
|
@@ -2529,7 +2792,7 @@ async function readBoundedResponse(response, abort) {
|
|
|
2529
2792
|
bytes.set(chunk, offset);
|
|
2530
2793
|
offset += chunk.byteLength;
|
|
2531
2794
|
}
|
|
2532
|
-
return decodeUtf8(bytes);
|
|
2795
|
+
return { source: decodeUtf8(bytes), size };
|
|
2533
2796
|
}
|
|
2534
2797
|
function decodeUtf8(bytes) {
|
|
2535
2798
|
let result = "";
|
|
@@ -2577,18 +2840,60 @@ function decodeUtf8(bytes) {
|
|
|
2577
2840
|
function hasNoStore(value) {
|
|
2578
2841
|
return typeof value === "string" && value.split(",").some((part) => part.trim().toLowerCase() === "no-store");
|
|
2579
2842
|
}
|
|
2580
|
-
function statusError(status) {
|
|
2843
|
+
function statusError(status, body, size) {
|
|
2581
2844
|
if (status === 401) return new HostedNativeSessionError("NATIVE_SESSION_CREDENTIAL_REJECTED");
|
|
2582
2845
|
if (status === 403) return new HostedNativeSessionError("NATIVE_SESSION_MAP_MISMATCH");
|
|
2583
2846
|
if (status === 404) return new HostedNativeSessionError("NATIVE_SESSION_MAP_NOT_FOUND");
|
|
2584
|
-
if (status === 429)
|
|
2585
|
-
|
|
2586
|
-
|
|
2847
|
+
if (status === 429) {
|
|
2848
|
+
return new HostedNativeSessionError(
|
|
2849
|
+
isRecord(body) && typeof body.code === "string" && QUOTA_CODES.has(body.code) ? "NATIVE_SESSION_QUOTA_EXCEEDED" : "NATIVE_SESSION_RATE_LIMITED"
|
|
2850
|
+
);
|
|
2851
|
+
}
|
|
2852
|
+
if (status === 408 || status >= 500 && status <= 599) {
|
|
2853
|
+
return new HostedNativeSessionError("NATIVE_SESSION_UNAVAILABLE");
|
|
2854
|
+
}
|
|
2855
|
+
if (status >= 400 && status <= 499) {
|
|
2856
|
+
return new HostedNativeSessionError(
|
|
2857
|
+
"NATIVE_SESSION_REJECTED",
|
|
2858
|
+
size <= SERVICE_CODE_BODY_LIMIT && isRecord(body) && typeof body.code === "string" ? body.code : void 0
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2861
|
+
return new HostedNativeSessionError("NATIVE_SESSION_RESPONSE_INVALID");
|
|
2862
|
+
}
|
|
2863
|
+
function safeError(error) {
|
|
2864
|
+
return Object.freeze(
|
|
2865
|
+
error.reason === void 0 ? { code: error.code, kind: error.kind } : { code: error.code, kind: error.kind, reason: error.reason }
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
function errorFrom(error) {
|
|
2869
|
+
return new HostedNativeSessionError(error.code, error.reason);
|
|
2870
|
+
}
|
|
2871
|
+
var retryAfterDelays = /* @__PURE__ */ new WeakMap();
|
|
2872
|
+
function retryAfterMs(headers, now) {
|
|
2873
|
+
let value;
|
|
2874
|
+
try {
|
|
2875
|
+
value = headers.get("retry-after");
|
|
2876
|
+
} catch {
|
|
2877
|
+
return null;
|
|
2878
|
+
}
|
|
2879
|
+
if (typeof value !== "string" || value.length > 64) return null;
|
|
2880
|
+
const text = value.trim();
|
|
2881
|
+
let delay;
|
|
2882
|
+
if (RETRY_AFTER_SECONDS.test(text)) delay = Number(text) * 1e3;
|
|
2883
|
+
else {
|
|
2884
|
+
const date = Date.parse(text);
|
|
2885
|
+
if (!Number.isFinite(date)) return null;
|
|
2886
|
+
try {
|
|
2887
|
+
delay = date - readWallClock(now);
|
|
2888
|
+
} catch {
|
|
2889
|
+
return null;
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
return Math.min(Math.max(0, delay), RETRY_AFTER_LIMIT_MS);
|
|
2587
2893
|
}
|
|
2588
2894
|
function errorKind(code) {
|
|
2589
2895
|
if (code === "NATIVE_SESSION_DISPOSED" || code === "NATIVE_SESSION_REPLACED") return "cancelled";
|
|
2590
|
-
|
|
2591
|
-
return "terminal";
|
|
2896
|
+
return TERMINAL_CODES.has(code) ? "terminal" : "recoverable";
|
|
2592
2897
|
}
|
|
2593
2898
|
function normalizeError(error, fallback = "NATIVE_SESSION_RESPONSE_INVALID") {
|
|
2594
2899
|
return error instanceof HostedNativeSessionError ? error : new HostedNativeSessionError(fallback);
|
|
@@ -2611,12 +2916,13 @@ async function cancelResponse(response) {
|
|
|
2611
2916
|
} catch {
|
|
2612
2917
|
}
|
|
2613
2918
|
}
|
|
2614
|
-
var _aborted, _code, _listeners;
|
|
2919
|
+
var _aborted, _code, _reason, _listeners;
|
|
2615
2920
|
var SessionAbortController = class {
|
|
2616
2921
|
constructor() {
|
|
2617
2922
|
__publicField(this, "signal");
|
|
2618
2923
|
__privateAdd(this, _aborted, false);
|
|
2619
2924
|
__privateAdd(this, _code, "NATIVE_SESSION_REPLACED");
|
|
2925
|
+
__privateAdd(this, _reason);
|
|
2620
2926
|
__privateAdd(this, _listeners, /* @__PURE__ */ new Set());
|
|
2621
2927
|
const isAborted = () => __privateGet(this, _aborted);
|
|
2622
2928
|
this.signal = Object.freeze({
|
|
@@ -2631,9 +2937,10 @@ var SessionAbortController = class {
|
|
|
2631
2937
|
}
|
|
2632
2938
|
});
|
|
2633
2939
|
}
|
|
2634
|
-
abort(code) {
|
|
2940
|
+
abort(code, reason) {
|
|
2635
2941
|
if (__privateGet(this, _aborted)) return;
|
|
2636
2942
|
__privateSet(this, _code, code);
|
|
2943
|
+
__privateSet(this, _reason, reason);
|
|
2637
2944
|
__privateSet(this, _aborted, true);
|
|
2638
2945
|
for (const listener of [...__privateGet(this, _listeners)]) {
|
|
2639
2946
|
try {
|
|
@@ -2644,11 +2951,12 @@ var SessionAbortController = class {
|
|
|
2644
2951
|
__privateGet(this, _listeners).clear();
|
|
2645
2952
|
}
|
|
2646
2953
|
error() {
|
|
2647
|
-
return new HostedNativeSessionError(__privateGet(this, _code));
|
|
2954
|
+
return new HostedNativeSessionError(__privateGet(this, _code), __privateGet(this, _reason));
|
|
2648
2955
|
}
|
|
2649
2956
|
};
|
|
2650
2957
|
_aborted = new WeakMap();
|
|
2651
2958
|
_code = new WeakMap();
|
|
2959
|
+
_reason = new WeakMap();
|
|
2652
2960
|
_listeners = new WeakMap();
|
|
2653
2961
|
function raceWithAbort(promise, controller) {
|
|
2654
2962
|
if (controller.signal.aborted) return Promise.reject(controller.error());
|
|
@@ -2887,6 +3195,8 @@ function createNativeAdmissionOwner(options) {
|
|
|
2887
3195
|
try {
|
|
2888
3196
|
resources = normalizeNativeResources(input.resources);
|
|
2889
3197
|
if (input.onRetired !== void 0 && typeof input.onRetired !== "function") throw cancel();
|
|
3198
|
+
if (input.onSessionError !== void 0 && typeof input.onSessionError !== "function")
|
|
3199
|
+
throw cancel();
|
|
2890
3200
|
} catch {
|
|
2891
3201
|
throw cancel("NATIVE_ADMISSION_INVALID");
|
|
2892
3202
|
}
|
|
@@ -2947,6 +3257,17 @@ function createNativeAdmissionOwner(options) {
|
|
|
2947
3257
|
controller,
|
|
2948
3258
|
handle: void 0
|
|
2949
3259
|
};
|
|
3260
|
+
let reported = null;
|
|
3261
|
+
const releaseSession = controller.subscribe((state) => {
|
|
3262
|
+
const code = state.status === "error" ? state.error.code : null;
|
|
3263
|
+
if (!context.live || code === reported) return;
|
|
3264
|
+
reported = code;
|
|
3265
|
+
if (state.status !== "error") return;
|
|
3266
|
+
try {
|
|
3267
|
+
input.onSessionError?.(Object.freeze({ ...state.error }));
|
|
3268
|
+
} catch {
|
|
3269
|
+
}
|
|
3270
|
+
});
|
|
2950
3271
|
context.handle = Object.freeze({
|
|
2951
3272
|
context: context.id,
|
|
2952
3273
|
generation: context.generation,
|
|
@@ -2976,9 +3297,26 @@ function createNativeAdmissionOwner(options) {
|
|
|
2976
3297
|
discriminate: catalog.discriminate,
|
|
2977
3298
|
discriminateForTest: catalog.discriminate,
|
|
2978
3299
|
extendResources: catalog.extend,
|
|
3300
|
+
retry() {
|
|
3301
|
+
if (!context.live || disposed) return;
|
|
3302
|
+
try {
|
|
3303
|
+
controller.retry?.();
|
|
3304
|
+
} catch {
|
|
3305
|
+
}
|
|
3306
|
+
},
|
|
3307
|
+
retryDelay() {
|
|
3308
|
+
if (!context.live || disposed) return null;
|
|
3309
|
+
try {
|
|
3310
|
+
const delay = controller.retryDelay?.() ?? null;
|
|
3311
|
+
return delay !== null && Number.isSafeInteger(delay) && delay >= 0 ? delay : null;
|
|
3312
|
+
} catch {
|
|
3313
|
+
return null;
|
|
3314
|
+
}
|
|
3315
|
+
},
|
|
2979
3316
|
retire() {
|
|
2980
3317
|
if (retirement) return retirement;
|
|
2981
3318
|
context.live = false;
|
|
3319
|
+
releaseSession();
|
|
2982
3320
|
context.batch = null;
|
|
2983
3321
|
context.tickets.clear();
|
|
2984
3322
|
catalog.retire();
|
|
@@ -5502,6 +5840,20 @@ function themeBlendKey(blend) {
|
|
|
5502
5840
|
}
|
|
5503
5841
|
|
|
5504
5842
|
// src/mounted-map-owner.ts
|
|
5843
|
+
var timer2 = (run, delayMs) => {
|
|
5844
|
+
const handle = setTimeout(run, delayMs);
|
|
5845
|
+
return () => clearTimeout(handle);
|
|
5846
|
+
};
|
|
5847
|
+
var REOPEN_LIMIT = 3;
|
|
5848
|
+
var sessionErrorCodes = /* @__PURE__ */ new Set([
|
|
5849
|
+
"NATIVE_SESSION_CREDENTIAL_REJECTED",
|
|
5850
|
+
"NATIVE_SESSION_MAP_MISMATCH",
|
|
5851
|
+
"NATIVE_SESSION_MAP_NOT_FOUND",
|
|
5852
|
+
"NATIVE_SESSION_QUOTA_EXCEEDED",
|
|
5853
|
+
"NATIVE_SESSION_REJECTED",
|
|
5854
|
+
"NATIVE_SESSION_INPUT_INVALID"
|
|
5855
|
+
]);
|
|
5856
|
+
var SERVICE_CODE2 = /^[A-Z][A-Z0-9_]{0,63}$/u;
|
|
5505
5857
|
function themeSource(source, name3) {
|
|
5506
5858
|
const theme = Object.hasOwn(source.map.themes, name3) ? source.map.themes[name3] : void 0;
|
|
5507
5859
|
if (!theme) throw new NativePreparationError();
|
|
@@ -5584,7 +5936,9 @@ function createMountedMapOwner(ports2) {
|
|
|
5584
5936
|
let sourceState;
|
|
5585
5937
|
let current;
|
|
5586
5938
|
let lastReadiness = "";
|
|
5939
|
+
let reopenings = 0;
|
|
5587
5940
|
let disposal;
|
|
5941
|
+
const schedule = ports2.schedule ?? timer2;
|
|
5588
5942
|
const retired = /* @__PURE__ */ new Set();
|
|
5589
5943
|
const tasks = /* @__PURE__ */ new Set();
|
|
5590
5944
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -5619,10 +5973,11 @@ function createMountedMapOwner(ports2) {
|
|
|
5619
5973
|
if (lastReadiness === identity) return;
|
|
5620
5974
|
lastReadiness = identity;
|
|
5621
5975
|
}
|
|
5976
|
+
if (event.type === "load") reopenings = 0;
|
|
5622
5977
|
try {
|
|
5623
5978
|
let result;
|
|
5624
5979
|
if (event.type === "load") result = props.onLoad?.(event);
|
|
5625
|
-
else if (event.type === "renderer-error" || event.type === "source-error")
|
|
5980
|
+
else if (event.type === "renderer-error" || event.type === "source-error" || event.type === "session-error")
|
|
5626
5981
|
result = props.onError?.(event);
|
|
5627
5982
|
else if (event.type === "readiness-change") result = props.onReadinessChange?.(event);
|
|
5628
5983
|
else result = props.onThemeChange?.(event);
|
|
@@ -5699,11 +6054,29 @@ function createMountedMapOwner(ports2) {
|
|
|
5699
6054
|
}
|
|
5700
6055
|
}
|
|
5701
6056
|
}
|
|
6057
|
+
function cancelRetry(epoch) {
|
|
6058
|
+
const cancel = epoch.retry;
|
|
6059
|
+
epoch.retry = void 0;
|
|
6060
|
+
cancel?.();
|
|
6061
|
+
}
|
|
6062
|
+
function retryLater(epoch, source) {
|
|
6063
|
+
const delay = epoch.map?.retryDelay();
|
|
6064
|
+
if (typeof delay !== "number") return;
|
|
6065
|
+
cancelRetry(epoch);
|
|
6066
|
+
const cancel = schedule(() => {
|
|
6067
|
+
if (epoch.retry !== cancel) return;
|
|
6068
|
+
epoch.retry = void 0;
|
|
6069
|
+
if (!disposed && foreground && current === epoch && epoch.live && core.state === source)
|
|
6070
|
+
prepare(epoch, source);
|
|
6071
|
+
}, delay);
|
|
6072
|
+
epoch.retry = cancel;
|
|
6073
|
+
}
|
|
5702
6074
|
function retire(epoch) {
|
|
5703
6075
|
if (epoch.retirement) return epoch.retirement;
|
|
5704
6076
|
epoch.live = false;
|
|
5705
6077
|
retired.add(epoch);
|
|
5706
6078
|
cancelJob(epoch);
|
|
6079
|
+
cancelRetry(epoch);
|
|
5707
6080
|
epoch.cache.clear();
|
|
5708
6081
|
epoch.binding.dispose();
|
|
5709
6082
|
const context2 = epoch.map?.retire();
|
|
@@ -5727,6 +6100,29 @@ function createMountedMapOwner(ports2) {
|
|
|
5727
6100
|
});
|
|
5728
6101
|
return track(attempt);
|
|
5729
6102
|
}
|
|
6103
|
+
function restart(epoch) {
|
|
6104
|
+
current = void 0;
|
|
6105
|
+
void retire(epoch).catch(() => void 0);
|
|
6106
|
+
current = createEpoch(epoch.developmentOrigin);
|
|
6107
|
+
select();
|
|
6108
|
+
}
|
|
6109
|
+
function reopen(epoch) {
|
|
6110
|
+
if (disposed || current !== epoch || !epoch.live || !props) return;
|
|
6111
|
+
rendererError();
|
|
6112
|
+
if (reopenings >= REOPEN_LIMIT) return;
|
|
6113
|
+
reopenings += 1;
|
|
6114
|
+
restart(epoch);
|
|
6115
|
+
}
|
|
6116
|
+
function sessionError(epoch, code, reason) {
|
|
6117
|
+
if (disposed || current !== epoch || !epoch.live || !sessionErrorCodes.has(code)) return;
|
|
6118
|
+
const detail = code === "NATIVE_SESSION_REJECTED" && typeof reason === "string" && SERVICE_CODE2.test(reason) ? { reason } : {};
|
|
6119
|
+
const error = Object.freeze({
|
|
6120
|
+
code,
|
|
6121
|
+
kind: "terminal",
|
|
6122
|
+
...detail
|
|
6123
|
+
});
|
|
6124
|
+
emit({ type: "session-error", generation: generation(), error });
|
|
6125
|
+
}
|
|
5730
6126
|
function createEpoch(developmentOrigin) {
|
|
5731
6127
|
const epoch = {
|
|
5732
6128
|
live: true,
|
|
@@ -5754,7 +6150,17 @@ function createMountedMapOwner(ports2) {
|
|
|
5754
6150
|
if (!epoch.live || current !== epoch) throw new NativePreparationError();
|
|
5755
6151
|
const binding = await epoch.binding.replace(source);
|
|
5756
6152
|
if (!epoch.live || current !== epoch) throw new NativePreparationError();
|
|
5757
|
-
epoch.lease = ports2.installation.open({
|
|
6153
|
+
epoch.lease = ports2.installation.open({
|
|
6154
|
+
binding,
|
|
6155
|
+
resources: [],
|
|
6156
|
+
now: ports2.now,
|
|
6157
|
+
// Admission reports from inside its own cleanup and session publication; defer.
|
|
6158
|
+
onRetired: () => void Promise.resolve().then(() => reopen(epoch)),
|
|
6159
|
+
onSessionError: (error) => {
|
|
6160
|
+
if (error.kind !== "terminal") return;
|
|
6161
|
+
void Promise.resolve().then(() => sessionError(epoch, error.code, error.reason));
|
|
6162
|
+
}
|
|
6163
|
+
});
|
|
5758
6164
|
const map = await epoch.lease.ready;
|
|
5759
6165
|
epoch.map = map;
|
|
5760
6166
|
if (!epoch.live || current !== epoch || map.state.status !== "active") {
|
|
@@ -5763,7 +6169,9 @@ function createMountedMapOwner(ports2) {
|
|
|
5763
6169
|
}
|
|
5764
6170
|
return map;
|
|
5765
6171
|
});
|
|
5766
|
-
void epoch.context.catch(() =>
|
|
6172
|
+
void epoch.context.catch(() => {
|
|
6173
|
+
epoch.contextFailed = true;
|
|
6174
|
+
});
|
|
5767
6175
|
}
|
|
5768
6176
|
return epoch.context;
|
|
5769
6177
|
}
|
|
@@ -5905,6 +6313,7 @@ function createMountedMapOwner(ports2) {
|
|
|
5905
6313
|
}
|
|
5906
6314
|
rendererError();
|
|
5907
6315
|
epoch.renderer?.preparationFailed(source.generation);
|
|
6316
|
+
retryLater(epoch, source);
|
|
5908
6317
|
}).finally(() => {
|
|
5909
6318
|
if (epoch.job === job) epoch.job = void 0;
|
|
5910
6319
|
});
|
|
@@ -5962,6 +6371,7 @@ function createMountedMapOwner(ports2) {
|
|
|
5962
6371
|
foreground = false;
|
|
5963
6372
|
if (current) {
|
|
5964
6373
|
cancelJob(current);
|
|
6374
|
+
cancelRetry(current);
|
|
5965
6375
|
current.renderer?.background();
|
|
5966
6376
|
}
|
|
5967
6377
|
return;
|
|
@@ -6060,6 +6470,22 @@ function createMountedMapOwner(ports2) {
|
|
|
6060
6470
|
if (state?.status !== "ready") select();
|
|
6061
6471
|
},
|
|
6062
6472
|
nativeLifecycle,
|
|
6473
|
+
/** Loads again after a stopped session, a failed source or a failed context. */
|
|
6474
|
+
retry() {
|
|
6475
|
+
const epoch = current;
|
|
6476
|
+
if (disposed || !props || !epoch?.live) return;
|
|
6477
|
+
reopenings = 0;
|
|
6478
|
+
const map = epoch.map;
|
|
6479
|
+
if (epoch.contextFailed || map?.state.status === "retired") {
|
|
6480
|
+
restart(epoch);
|
|
6481
|
+
return;
|
|
6482
|
+
}
|
|
6483
|
+
map?.retry();
|
|
6484
|
+
cancelRetry(epoch);
|
|
6485
|
+
const state = core.state;
|
|
6486
|
+
if (state?.status === "error") select();
|
|
6487
|
+
else if (state?.status === "ready" && map && foreground) prepare(epoch, state);
|
|
6488
|
+
},
|
|
6063
6489
|
async whenIdle() {
|
|
6064
6490
|
while (tasks.size) await Promise.allSettled([...tasks]);
|
|
6065
6491
|
await current?.renderer?.whenIdle();
|
|
@@ -6097,7 +6523,9 @@ var nativeBootstrapLimits = Object.freeze({
|
|
|
6097
6523
|
queueDepth: 32,
|
|
6098
6524
|
requestBytes: 2048,
|
|
6099
6525
|
responseBytes: 65536,
|
|
6100
|
-
|
|
6526
|
+
retryAfterCharacters: 64,
|
|
6527
|
+
// Well inside the 30 s ticket and batch deadlines.
|
|
6528
|
+
timeoutMs: 1e4
|
|
6101
6529
|
});
|
|
6102
6530
|
|
|
6103
6531
|
// src/native-admission-wire.ts
|
|
@@ -6128,10 +6556,11 @@ function decodeBoundedBase64(value) {
|
|
|
6128
6556
|
return bytes;
|
|
6129
6557
|
}
|
|
6130
6558
|
function responseFromWire(reply, isAborted, release) {
|
|
6131
|
-
if (!reply || !Number.isInteger(reply.status) || reply.status < 100 || reply.status > 599 || typeof reply.cacheControl !== "string" || reply.cacheControl.length > 1024)
|
|
6559
|
+
if (!reply || !Number.isInteger(reply.status) || reply.status < 100 || reply.status > 599 || typeof reply.cacheControl !== "string" || reply.cacheControl.length > 1024 || reply.retryAfter !== void 0 && (typeof reply.retryAfter !== "string" || reply.retryAfter.length > nativeBootstrapLimits.retryAfterCharacters))
|
|
6132
6560
|
throw invalid4();
|
|
6133
6561
|
const status = reply.status;
|
|
6134
6562
|
const cacheControl = reply.cacheControl;
|
|
6563
|
+
const retryAfter = reply.retryAfter || null;
|
|
6135
6564
|
let bytes = decodeBoundedBase64(reply.bodyBase64);
|
|
6136
6565
|
let readerTaken = false;
|
|
6137
6566
|
let done = false;
|
|
@@ -6145,7 +6574,9 @@ function responseFromWire(reply, isAborted, release) {
|
|
|
6145
6574
|
status,
|
|
6146
6575
|
headers: Object.freeze({
|
|
6147
6576
|
get(name3) {
|
|
6148
|
-
|
|
6577
|
+
const header = name3.toLowerCase();
|
|
6578
|
+
if (header === "cache-control") return cacheControl;
|
|
6579
|
+
return header === "retry-after" ? retryAfter : null;
|
|
6149
6580
|
}
|
|
6150
6581
|
}),
|
|
6151
6582
|
body: Object.freeze({
|
|
@@ -7272,7 +7703,7 @@ function createNativeMapOwner() {
|
|
|
7272
7703
|
}
|
|
7273
7704
|
|
|
7274
7705
|
// src/map.tsx
|
|
7275
|
-
import { jsx as
|
|
7706
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
7276
7707
|
var fill = Object.freeze({ flex: 1 });
|
|
7277
7708
|
function NativeScene({
|
|
7278
7709
|
scene,
|
|
@@ -7281,7 +7712,8 @@ function NativeScene({
|
|
|
7281
7712
|
children,
|
|
7282
7713
|
interactions,
|
|
7283
7714
|
interactionEnabled,
|
|
7284
|
-
renderMarker
|
|
7715
|
+
renderMarker,
|
|
7716
|
+
boundaryFeatureState
|
|
7285
7717
|
}) {
|
|
7286
7718
|
const view = useRef(null);
|
|
7287
7719
|
const camera = useRef(null);
|
|
@@ -7291,7 +7723,10 @@ function NativeScene({
|
|
|
7291
7723
|
const laidOut = useRef(false);
|
|
7292
7724
|
const styleLoaded = useRef(false);
|
|
7293
7725
|
const attachRequested = useRef(false);
|
|
7294
|
-
const
|
|
7726
|
+
const latestStyle = useRef(scene.style);
|
|
7727
|
+
latestStyle.current = scene.style;
|
|
7728
|
+
const [loadedStyle, setLoadedStyle] = useState2(void 0);
|
|
7729
|
+
const host = useMemo3(() => {
|
|
7295
7730
|
const value = Object.freeze({
|
|
7296
7731
|
key: scene.key,
|
|
7297
7732
|
style: scene.style,
|
|
@@ -7305,7 +7740,7 @@ function NativeScene({
|
|
|
7305
7740
|
});
|
|
7306
7741
|
return value;
|
|
7307
7742
|
}, [scene.key, scene.style]);
|
|
7308
|
-
|
|
7743
|
+
useLayoutEffect3(() => {
|
|
7309
7744
|
committedHost.current = host;
|
|
7310
7745
|
lifecycle.bindInteractionHost(host);
|
|
7311
7746
|
return () => {
|
|
@@ -7313,11 +7748,11 @@ function NativeScene({
|
|
|
7313
7748
|
lifecycle.unbindInteractionHost(host);
|
|
7314
7749
|
};
|
|
7315
7750
|
}, [host, lifecycle]);
|
|
7316
|
-
const mapStyle =
|
|
7751
|
+
const mapStyle = useMemo3(
|
|
7317
7752
|
() => JSON.stringify(scene.style),
|
|
7318
7753
|
[scene.style]
|
|
7319
7754
|
);
|
|
7320
|
-
const [initialViewState] =
|
|
7755
|
+
const [initialViewState] = useState2(() => {
|
|
7321
7756
|
const center = [scene.initialView.center[0], scene.initialView.center[1]];
|
|
7322
7757
|
Object.freeze(center);
|
|
7323
7758
|
return Object.freeze({
|
|
@@ -7343,7 +7778,7 @@ function NativeScene({
|
|
|
7343
7778
|
};
|
|
7344
7779
|
const annotations = interactions?.annotations;
|
|
7345
7780
|
const byId = new globalThis.Map(annotations?.map((annotation) => [annotation.id, annotation]));
|
|
7346
|
-
return /* @__PURE__ */
|
|
7781
|
+
return /* @__PURE__ */ jsx3(
|
|
7347
7782
|
View2,
|
|
7348
7783
|
{
|
|
7349
7784
|
ref: bind2,
|
|
@@ -7381,10 +7816,11 @@ function NativeScene({
|
|
|
7381
7816
|
},
|
|
7382
7817
|
onDidFinishLoadingStyle: () => {
|
|
7383
7818
|
styleLoaded.current = true;
|
|
7819
|
+
setLoadedStyle(latestStyle.current);
|
|
7384
7820
|
attach();
|
|
7385
7821
|
},
|
|
7386
7822
|
children: [
|
|
7387
|
-
/* @__PURE__ */
|
|
7823
|
+
/* @__PURE__ */ jsx3(
|
|
7388
7824
|
NativeCamera,
|
|
7389
7825
|
{
|
|
7390
7826
|
ref: camera,
|
|
@@ -7394,9 +7830,9 @@ function NativeScene({
|
|
|
7394
7830
|
},
|
|
7395
7831
|
"tileflow-camera"
|
|
7396
7832
|
),
|
|
7397
|
-
/* @__PURE__ */
|
|
7833
|
+
/* @__PURE__ */ jsx3(Fragment, { children: interactions?.plan.order.map((id) => {
|
|
7398
7834
|
const annotation = byId.get(id);
|
|
7399
|
-
return annotation ? /* @__PURE__ */
|
|
7835
|
+
return annotation ? /* @__PURE__ */ jsx3(
|
|
7400
7836
|
NativeAnnotationMarker,
|
|
7401
7837
|
{
|
|
7402
7838
|
annotation,
|
|
@@ -7408,6 +7844,15 @@ function NativeScene({
|
|
|
7408
7844
|
annotation.id
|
|
7409
7845
|
) : null;
|
|
7410
7846
|
}) }, "tileflow-annotations"),
|
|
7847
|
+
boundaryFeatureState ? /* @__PURE__ */ jsx3(
|
|
7848
|
+
NativeBoundaryFeatureState,
|
|
7849
|
+
{
|
|
7850
|
+
input: boundaryFeatureState,
|
|
7851
|
+
style: scene.style,
|
|
7852
|
+
loaded: loadedStyle === scene.style
|
|
7853
|
+
},
|
|
7854
|
+
"tileflow-boundary-feature-state"
|
|
7855
|
+
) : null,
|
|
7411
7856
|
children
|
|
7412
7857
|
]
|
|
7413
7858
|
}
|
|
@@ -7416,7 +7861,7 @@ function NativeScene({
|
|
|
7416
7861
|
);
|
|
7417
7862
|
}
|
|
7418
7863
|
function Map2(props) {
|
|
7419
|
-
const [lifecycle] =
|
|
7864
|
+
const [lifecycle] = useState2(
|
|
7420
7865
|
() => createMapLifecycle(createNativeMapOwner, retireNativeMapOwner)
|
|
7421
7866
|
);
|
|
7422
7867
|
const snapshot = useSyncExternalStore(
|
|
@@ -7424,10 +7869,12 @@ function Map2(props) {
|
|
|
7424
7869
|
lifecycle.getSnapshot,
|
|
7425
7870
|
lifecycle.getSnapshot
|
|
7426
7871
|
);
|
|
7427
|
-
useImperativeHandle(
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7872
|
+
useImperativeHandle(
|
|
7873
|
+
props.ref,
|
|
7874
|
+
() => Object.freeze({ getSourceState: lifecycle.getSourceState, retry: lifecycle.retry }),
|
|
7875
|
+
[lifecycle]
|
|
7876
|
+
);
|
|
7877
|
+
useLayoutEffect3(() => {
|
|
7431
7878
|
const unmount = lifecycle.mount();
|
|
7432
7879
|
if (AppState.currentState !== "active") lifecycle.background();
|
|
7433
7880
|
const subscription = AppState.addEventListener("change", (state) => {
|
|
@@ -7439,10 +7886,10 @@ function Map2(props) {
|
|
|
7439
7886
|
unmount();
|
|
7440
7887
|
};
|
|
7441
7888
|
}, [lifecycle]);
|
|
7442
|
-
|
|
7889
|
+
useLayoutEffect3(() => {
|
|
7443
7890
|
lifecycle.update(props);
|
|
7444
7891
|
});
|
|
7445
|
-
return /* @__PURE__ */
|
|
7892
|
+
return /* @__PURE__ */ jsx3(View2, { style: props.style, testID: props.testID, children: snapshot.renderer ? /* @__PURE__ */ jsx3(
|
|
7446
7893
|
NativeScene,
|
|
7447
7894
|
{
|
|
7448
7895
|
scene: snapshot.renderer,
|
|
@@ -7451,6 +7898,7 @@ function Map2(props) {
|
|
|
7451
7898
|
interactions: snapshot.interactions,
|
|
7452
7899
|
interactionEnabled: snapshot.interactionEnabled === true,
|
|
7453
7900
|
renderMarker: props.renderMarker,
|
|
7901
|
+
boundaryFeatureState: props.boundaryFeatureState,
|
|
7454
7902
|
children: props.children
|
|
7455
7903
|
},
|
|
7456
7904
|
snapshot.renderer.key
|