@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.
- package/README.md +216 -4
- package/TileflowNativeAdmission.podspec +34 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrap.kt +144 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionBootstrapOkHttpNetwork.kt +89 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionCatalog.kt +137 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionDocumentScope.kt +22 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionEngine.kt +335 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionModuleProvider.kt +68 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionOkHttpNetwork.kt +86 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionProviderLease.kt +56 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionTypes.kt +85 -0
- package/android/src/main/java/dev/tileflow/reactnative/AdmissionUrl.kt +100 -0
- package/android/src/main/java/dev/tileflow/reactnative/HandlerAdmissionScheduler.kt +16 -0
- package/android/src/main/java/dev/tileflow/reactnative/MobileConfiguration.kt +95 -0
- package/android/src/main/java/dev/tileflow/reactnative/MobileConfigurationResources.kt +43 -0
- package/android/src/main/java/dev/tileflow/reactnative/NativeDocumentOkHttpNetwork.kt +101 -0
- package/android/src/main/java/dev/tileflow/reactnative/NativeDocumentRegistry.kt +136 -0
- package/android/src/main/java/dev/tileflow/reactnative/NativeSurfaceState.kt +115 -0
- package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeAdmissionModule.kt +234 -0
- package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeAdmissionPackage.kt +16 -0
- package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeConfigurationModule.kt +43 -0
- package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeDocumentsModule.kt +80 -0
- package/android/src/main/java/dev/tileflow/reactnative/TileflowNativeSurfaceModule.kt +277 -0
- package/android/src/main/res/raw/tileflow_configuration_keep.xml +3 -0
- package/dist/contract-DWs-lDPo.d.ts +144 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +6989 -0
- package/dist/internal/camera.d.ts +65 -0
- package/dist/internal/camera.js +325 -0
- package/dist/internal/hosted-binding.d.ts +18 -0
- package/dist/internal/hosted-binding.js +277 -0
- package/dist/internal/initial-view.d.ts +11 -0
- package/dist/internal/initial-view.js +8 -0
- package/dist/internal/interactions.d.ts +76 -0
- package/dist/internal/interactions.js +794 -0
- package/dist/internal/native-admission-bridge.d.ts +29 -0
- package/dist/internal/native-admission-bridge.js +351 -0
- package/dist/internal/native-admission.d.ts +78 -0
- package/dist/internal/native-admission.js +1614 -0
- package/dist/internal/native-appearance.d.ts +21 -0
- package/dist/internal/native-appearance.js +110 -0
- package/dist/internal/native-configuration-bridge.d.ts +9 -0
- package/dist/internal/native-configuration-bridge.js +343 -0
- package/dist/internal/session.d.ts +125 -0
- package/dist/internal/session.js +887 -0
- package/dist/internal/source-state.d.ts +11 -0
- package/dist/internal/source-state.js +26 -0
- package/dist/native-admission-contract-k7LKE1vW.d.ts +89 -0
- package/docs/native-admission.md +108 -0
- package/docs/native-configuration.md +210 -0
- package/docs/native-interactions.md +161 -0
- package/docs/native-location.md +148 -0
- package/ios/TFAdmissionBootstrap.h +17 -0
- package/ios/TFAdmissionBootstrap.mm +168 -0
- package/ios/TFAdmissionCatalog.h +13 -0
- package/ios/TFAdmissionCatalog.mm +193 -0
- package/ios/TFAdmissionDocumentScope.h +7 -0
- package/ios/TFAdmissionDocumentScope.mm +45 -0
- package/ios/TFAdmissionEngine.h +57 -0
- package/ios/TFAdmissionEngine.mm +371 -0
- package/ios/TFAdmissionInstallation.h +27 -0
- package/ios/TFAdmissionInstallation.mm +117 -0
- package/ios/TFAdmissionNetwork.h +16 -0
- package/ios/TFAdmissionNetwork.mm +268 -0
- package/ios/TFAdmissionURL.mm +116 -0
- package/ios/TFDocumentBounds.h +9 -0
- package/ios/TFDocumentBounds.mm +19 -0
- package/ios/TFMobileConfiguration.h +23 -0
- package/ios/TFMobileConfiguration.mm +139 -0
- package/ios/TFNativeAdmissionURLProtocol.mm +69 -0
- package/ios/TFNativeDocumentRegistry.h +28 -0
- package/ios/TFNativeDocumentRegistry.mm +193 -0
- package/ios/TFNativeDocumentURL.h +6 -0
- package/ios/TFNativeSurfaceState.h +28 -0
- package/ios/TFNativeSurfaceState.mm +128 -0
- package/ios/TileflowNativeAdmission.h +9 -0
- package/ios/TileflowNativeAdmission.mm +184 -0
- package/ios/TileflowNativeConfiguration.mm +41 -0
- package/ios/TileflowNativeDocuments.h +12 -0
- package/ios/TileflowNativeDocuments.mm +105 -0
- package/ios/TileflowNativeSurface.mm +403 -0
- package/ios/tileflow_native_admission.rb +45 -0
- package/package.json +51 -7
- package/react-native.config.cjs +13 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { M as MapView, a as MapViewChangeEvent, b as MapInitialViewInputs, c as MapCameraProps } from '../contract-DWs-lDPo.js';
|
|
2
|
+
import '@maplibre/maplibre-react-native';
|
|
3
|
+
import 'react';
|
|
4
|
+
import 'react-native';
|
|
5
|
+
import '@tileflow/core/native';
|
|
6
|
+
import '@tileflow/interactions';
|
|
7
|
+
|
|
8
|
+
declare const messages: {
|
|
9
|
+
readonly CAMERA_INPUT_INVALID: "Expected valid camera ownership and a canonical view.";
|
|
10
|
+
readonly CAMERA_MODE_CHANGE: "Camera ownership cannot change on a mounted instance.";
|
|
11
|
+
readonly CAMERA_NOT_MOUNTED: "The camera instance has not been mounted.";
|
|
12
|
+
readonly CAMERA_DISPOSED: "The camera instance has been disposed.";
|
|
13
|
+
readonly CAMERA_COMMAND_FAILED: "The camera command could not be applied.";
|
|
14
|
+
};
|
|
15
|
+
/** Internal owner diagnostic, never a forwarded native exception or public renderer event. */
|
|
16
|
+
declare class CameraControllerError extends Error {
|
|
17
|
+
readonly code: keyof typeof messages;
|
|
18
|
+
constructor(code: keyof typeof messages);
|
|
19
|
+
}
|
|
20
|
+
type CameraFallbacks = Pick<MapInitialViewInputs, 'mapOptionsView' | 'manifestView'>;
|
|
21
|
+
type CameraPropsSnapshot = Readonly<{
|
|
22
|
+
mode: 'controlled' | 'initial';
|
|
23
|
+
view: MapView;
|
|
24
|
+
onViewChange?: (event: MapViewChangeEvent) => void;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
/** Identity, not just sequence equality, binds callbacks to one controller instance. */
|
|
28
|
+
type CameraToken = Readonly<{
|
|
29
|
+
sequence: number;
|
|
30
|
+
}>;
|
|
31
|
+
type CameraCommand = Readonly<{
|
|
32
|
+
token: CameraToken;
|
|
33
|
+
view: MapView;
|
|
34
|
+
}>;
|
|
35
|
+
type CameraOperation = {
|
|
36
|
+
/** Resolve only after this target has been applied. Retired completions are ignored. */
|
|
37
|
+
finished: Promise<void>;
|
|
38
|
+
cancel(): void | Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
type CameraPort = {
|
|
41
|
+
/** The adapter must retire/cancel old commands and classify their observations by token. */
|
|
42
|
+
apply(command: CameraCommand): CameraOperation;
|
|
43
|
+
};
|
|
44
|
+
/** One pure owner per real native instance. No renderer, source, appearance or scheduling API. */
|
|
45
|
+
declare function createMapCameraController(port: CameraPort, onError?: (error: CameraControllerError) => void): {
|
|
46
|
+
/** Last canonical observed/acknowledged view; not renderer readiness. */
|
|
47
|
+
readonly view: MapView | undefined;
|
|
48
|
+
readonly mode: CameraPropsSnapshot["mode"] | undefined;
|
|
49
|
+
mount: (input: MapCameraProps, fallbacks?: CameraFallbacks) => void;
|
|
50
|
+
update: (input: MapCameraProps) => void;
|
|
51
|
+
startGesture(): CameraToken | undefined;
|
|
52
|
+
changeGesture(identity: CameraToken, input: MapView): void;
|
|
53
|
+
endGesture(identity: CameraToken, input: MapView): void;
|
|
54
|
+
/** Confirm the post-gesture prop commit, after update() has delivered its current props. */
|
|
55
|
+
settleGesture(identity: CameraToken): void;
|
|
56
|
+
/** Retire renderer-bound camera epochs without guessing a completion or issuing a replacement. */
|
|
57
|
+
interrupt(): void;
|
|
58
|
+
/** Programmatic observations are never public changes, even when their values differ. */
|
|
59
|
+
observeCommand(identity: CameraToken, input: MapView): void;
|
|
60
|
+
/** Called explicitly by the renderer owner after replacing style/source on this instance. */
|
|
61
|
+
restoreAfterStyleChange(): void;
|
|
62
|
+
dispose(): void;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export { type CameraCommand, CameraControllerError, type CameraOperation, type CameraPort, type CameraToken, createMapCameraController };
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/initial-view.ts
|
|
6
|
+
import { resolveTileflowNativeInitialView } from "@tileflow/core/native";
|
|
7
|
+
function resolveMapInitialView(inputs) {
|
|
8
|
+
return resolveTileflowNativeInitialView(inputs);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/camera-input.ts
|
|
12
|
+
var messages = {
|
|
13
|
+
CAMERA_INPUT_INVALID: "Expected valid camera ownership and a canonical view.",
|
|
14
|
+
CAMERA_MODE_CHANGE: "Camera ownership cannot change on a mounted instance.",
|
|
15
|
+
CAMERA_NOT_MOUNTED: "The camera instance has not been mounted.",
|
|
16
|
+
CAMERA_DISPOSED: "The camera instance has been disposed.",
|
|
17
|
+
CAMERA_COMMAND_FAILED: "The camera command could not be applied."
|
|
18
|
+
};
|
|
19
|
+
var CameraControllerError = class extends Error {
|
|
20
|
+
constructor(code) {
|
|
21
|
+
super(messages[code]);
|
|
22
|
+
__publicField(this, "code");
|
|
23
|
+
this.name = "CameraControllerError";
|
|
24
|
+
this.code = code;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var fail = () => {
|
|
28
|
+
throw new CameraControllerError("CAMERA_INPUT_INVALID");
|
|
29
|
+
};
|
|
30
|
+
function ownRecord(value, allowed) {
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail();
|
|
32
|
+
const prototype = Object.getPrototypeOf(value);
|
|
33
|
+
if (prototype !== null && prototype !== Object.prototype) return fail();
|
|
34
|
+
const keys = Reflect.ownKeys(value);
|
|
35
|
+
if (keys.length > allowed.length) return fail();
|
|
36
|
+
const copy = /* @__PURE__ */ Object.create(null);
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
if (typeof key !== "string" || !allowed.includes(key)) return fail();
|
|
39
|
+
const property = Object.getOwnPropertyDescriptor(value, key);
|
|
40
|
+
if (!property?.enumerable || !("value" in property)) return fail();
|
|
41
|
+
copy[key] = property.value;
|
|
42
|
+
}
|
|
43
|
+
return copy;
|
|
44
|
+
}
|
|
45
|
+
function requireCompleteView(value) {
|
|
46
|
+
if (!value || typeof value !== "object") return fail();
|
|
47
|
+
for (const key of ["center", "zoom", "bearing", "pitch"]) {
|
|
48
|
+
const property = Object.getOwnPropertyDescriptor(value, key);
|
|
49
|
+
if (!property || !("value" in property) || property.value === void 0) return fail();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function snapshotCameraProps(props, fallbacks = {}) {
|
|
53
|
+
try {
|
|
54
|
+
const input = ownRecord(props, ["view", "initialView", "onViewChange"]);
|
|
55
|
+
const fallback = ownRecord(fallbacks, ["mapOptionsView", "manifestView"]);
|
|
56
|
+
const controlled = input.view !== void 0;
|
|
57
|
+
if (controlled && input.initialView !== void 0) return fail();
|
|
58
|
+
if (input.onViewChange !== void 0 && typeof input.onViewChange !== "function") return fail();
|
|
59
|
+
if (controlled) {
|
|
60
|
+
if (typeof input.onViewChange !== "function") return fail();
|
|
61
|
+
requireCompleteView(input.view);
|
|
62
|
+
}
|
|
63
|
+
const view = resolveMapInitialView({
|
|
64
|
+
view: controlled ? input.view : input.initialView,
|
|
65
|
+
mapOptionsView: fallback.mapOptionsView,
|
|
66
|
+
manifestView: fallback.manifestView
|
|
67
|
+
});
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
mode: controlled ? "controlled" : "initial",
|
|
70
|
+
view,
|
|
71
|
+
onViewChange: input.onViewChange
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
return fail();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function snapshotCameraView(view) {
|
|
78
|
+
try {
|
|
79
|
+
requireCompleteView(view);
|
|
80
|
+
return resolveMapInitialView({ view });
|
|
81
|
+
} catch {
|
|
82
|
+
return fail();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function sameCameraView(left, right) {
|
|
86
|
+
return left.zoom === right.zoom && left.bearing === right.bearing && left.pitch === right.pitch && left.center[0] === right.center[0] && left.center[1] === right.center[1];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/camera-controller.ts
|
|
90
|
+
function createMapCameraController(port, onError = () => void 0) {
|
|
91
|
+
let apply;
|
|
92
|
+
try {
|
|
93
|
+
if (!port || typeof port.apply !== "function" || typeof onError !== "function") {
|
|
94
|
+
throw new CameraControllerError("CAMERA_INPUT_INVALID");
|
|
95
|
+
}
|
|
96
|
+
apply = port.apply.bind(port);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new CameraControllerError("CAMERA_INPUT_INVALID");
|
|
99
|
+
}
|
|
100
|
+
let report = onError;
|
|
101
|
+
let mounted = false;
|
|
102
|
+
let disposed = false;
|
|
103
|
+
let mode;
|
|
104
|
+
let props;
|
|
105
|
+
let live;
|
|
106
|
+
let gesture;
|
|
107
|
+
let settlement;
|
|
108
|
+
let pending;
|
|
109
|
+
let sequence = 0;
|
|
110
|
+
let revision = 0;
|
|
111
|
+
function advance() {
|
|
112
|
+
if (revision >= Number.MAX_SAFE_INTEGER)
|
|
113
|
+
throw new CameraControllerError("CAMERA_INPUT_INVALID");
|
|
114
|
+
return ++revision;
|
|
115
|
+
}
|
|
116
|
+
function token() {
|
|
117
|
+
if (sequence >= Number.MAX_SAFE_INTEGER)
|
|
118
|
+
throw new CameraControllerError("CAMERA_INPUT_INVALID");
|
|
119
|
+
return Object.freeze({ sequence: ++sequence });
|
|
120
|
+
}
|
|
121
|
+
function assertUsable() {
|
|
122
|
+
if (disposed) throw new CameraControllerError("CAMERA_DISPOSED");
|
|
123
|
+
}
|
|
124
|
+
function notify(event) {
|
|
125
|
+
const listener = props?.onViewChange;
|
|
126
|
+
try {
|
|
127
|
+
void Promise.resolve(listener?.(event)).catch(() => void 0);
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function retire(run) {
|
|
132
|
+
if (!run) return;
|
|
133
|
+
run.retired = true;
|
|
134
|
+
if (!run.operation || run.cancelled) return;
|
|
135
|
+
run.cancelled = true;
|
|
136
|
+
try {
|
|
137
|
+
void Promise.resolve(run.operation.cancel()).catch(() => void 0);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function current(run) {
|
|
142
|
+
return mounted && !disposed && pending === run && !run.retired;
|
|
143
|
+
}
|
|
144
|
+
function failed(run) {
|
|
145
|
+
if (!current(run)) return;
|
|
146
|
+
const own = revision;
|
|
147
|
+
pending = void 0;
|
|
148
|
+
retire(run);
|
|
149
|
+
if (disposed || revision !== own) return;
|
|
150
|
+
try {
|
|
151
|
+
void Promise.resolve(report?.(new CameraControllerError("CAMERA_COMMAND_FAILED"))).catch(
|
|
152
|
+
() => void 0
|
|
153
|
+
);
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function issue(view, force = false) {
|
|
158
|
+
if (!mounted || disposed) return;
|
|
159
|
+
if (!force && pending && sameCameraView(pending.command.view, view)) return;
|
|
160
|
+
if (!force && !pending && live && sameCameraView(live, view)) return;
|
|
161
|
+
const run = {
|
|
162
|
+
command: Object.freeze({ token: token(), view }),
|
|
163
|
+
retired: false,
|
|
164
|
+
cancelled: false
|
|
165
|
+
};
|
|
166
|
+
const previous = pending;
|
|
167
|
+
pending = run;
|
|
168
|
+
retire(previous);
|
|
169
|
+
if (!current(run)) return;
|
|
170
|
+
try {
|
|
171
|
+
const operation = apply(run.command);
|
|
172
|
+
if (!operation || typeof operation.cancel !== "function") {
|
|
173
|
+
throw new CameraControllerError("CAMERA_COMMAND_FAILED");
|
|
174
|
+
}
|
|
175
|
+
run.operation = operation;
|
|
176
|
+
const finished = operation.finished;
|
|
177
|
+
if (!finished || typeof finished.then !== "function") {
|
|
178
|
+
throw new CameraControllerError("CAMERA_COMMAND_FAILED");
|
|
179
|
+
}
|
|
180
|
+
void Promise.resolve(finished).then(
|
|
181
|
+
() => {
|
|
182
|
+
if (!current(run)) return;
|
|
183
|
+
live = run.command.view;
|
|
184
|
+
pending = void 0;
|
|
185
|
+
},
|
|
186
|
+
() => failed(run)
|
|
187
|
+
);
|
|
188
|
+
if (!current(run)) retire(run);
|
|
189
|
+
} catch {
|
|
190
|
+
if (current(run)) failed(run);
|
|
191
|
+
else retire(run);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function reconcile() {
|
|
195
|
+
if (!disposed && !gesture && !settlement && props?.mode === "controlled") issue(props.view);
|
|
196
|
+
}
|
|
197
|
+
function update(input) {
|
|
198
|
+
assertUsable();
|
|
199
|
+
if (!mounted) throw new CameraControllerError("CAMERA_NOT_MOUNTED");
|
|
200
|
+
const own = advance();
|
|
201
|
+
const next = snapshotCameraProps(input);
|
|
202
|
+
if (disposed || revision !== own) return;
|
|
203
|
+
if (next.mode !== mode) throw new CameraControllerError("CAMERA_MODE_CHANGE");
|
|
204
|
+
props = next;
|
|
205
|
+
reconcile();
|
|
206
|
+
}
|
|
207
|
+
function mount(input, fallbacks = {}) {
|
|
208
|
+
assertUsable();
|
|
209
|
+
if (mounted) {
|
|
210
|
+
update(input);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const own = advance();
|
|
214
|
+
const next = snapshotCameraProps(input, fallbacks);
|
|
215
|
+
if (disposed || revision !== own) return;
|
|
216
|
+
mode = next.mode;
|
|
217
|
+
props = next;
|
|
218
|
+
live = next.view;
|
|
219
|
+
mounted = true;
|
|
220
|
+
issue(next.view, true);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
/** Last canonical observed/acknowledged view; not renderer readiness. */
|
|
224
|
+
get view() {
|
|
225
|
+
return live;
|
|
226
|
+
},
|
|
227
|
+
get mode() {
|
|
228
|
+
return mode;
|
|
229
|
+
},
|
|
230
|
+
mount,
|
|
231
|
+
update,
|
|
232
|
+
startGesture() {
|
|
233
|
+
if (!mounted || disposed || !live) return void 0;
|
|
234
|
+
if (gesture) return gesture.token;
|
|
235
|
+
advance();
|
|
236
|
+
const next = { token: token(), lastEmitted: live };
|
|
237
|
+
gesture = next;
|
|
238
|
+
settlement = void 0;
|
|
239
|
+
const previous = pending;
|
|
240
|
+
pending = void 0;
|
|
241
|
+
retire(previous);
|
|
242
|
+
return gesture === next && !disposed ? next.token : void 0;
|
|
243
|
+
},
|
|
244
|
+
changeGesture(identity, input) {
|
|
245
|
+
const active = gesture;
|
|
246
|
+
if (disposed || !active || active.token !== identity) return;
|
|
247
|
+
const own = advance();
|
|
248
|
+
const view = snapshotCameraView(input);
|
|
249
|
+
if (disposed || gesture !== active || revision !== own) return;
|
|
250
|
+
live = view;
|
|
251
|
+
if (sameCameraView(active.lastEmitted, view)) return;
|
|
252
|
+
active.lastEmitted = view;
|
|
253
|
+
notify(Object.freeze({ type: "view-change", view }));
|
|
254
|
+
},
|
|
255
|
+
endGesture(identity, input) {
|
|
256
|
+
const active = gesture;
|
|
257
|
+
if (disposed || !active || active.token !== identity) return;
|
|
258
|
+
const own = advance();
|
|
259
|
+
const view = snapshotCameraView(input);
|
|
260
|
+
if (disposed || gesture !== active || revision !== own) return;
|
|
261
|
+
live = view;
|
|
262
|
+
gesture = void 0;
|
|
263
|
+
const waiting = { token: identity, notifying: true };
|
|
264
|
+
settlement = waiting;
|
|
265
|
+
try {
|
|
266
|
+
if (!sameCameraView(active.lastEmitted, view)) {
|
|
267
|
+
notify(Object.freeze({ type: "view-change", view }));
|
|
268
|
+
}
|
|
269
|
+
} finally {
|
|
270
|
+
waiting.notifying = false;
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
/** Confirm the post-gesture prop commit, after update() has delivered its current props. */
|
|
274
|
+
settleGesture(identity) {
|
|
275
|
+
const waiting = settlement;
|
|
276
|
+
if (disposed || !waiting || waiting.token !== identity || waiting.notifying) return;
|
|
277
|
+
advance();
|
|
278
|
+
settlement = void 0;
|
|
279
|
+
reconcile();
|
|
280
|
+
},
|
|
281
|
+
/** Retire renderer-bound camera epochs without guessing a completion or issuing a replacement. */
|
|
282
|
+
interrupt() {
|
|
283
|
+
if (!mounted || disposed) return;
|
|
284
|
+
advance();
|
|
285
|
+
gesture = void 0;
|
|
286
|
+
settlement = void 0;
|
|
287
|
+
const previous = pending;
|
|
288
|
+
pending = void 0;
|
|
289
|
+
retire(previous);
|
|
290
|
+
},
|
|
291
|
+
/** Programmatic observations are never public changes, even when their values differ. */
|
|
292
|
+
observeCommand(identity, input) {
|
|
293
|
+
const run = pending;
|
|
294
|
+
if (!run || !current(run) || run.command.token !== identity) return;
|
|
295
|
+
const own = advance();
|
|
296
|
+
const view = snapshotCameraView(input);
|
|
297
|
+
if (!current(run) || revision !== own) return;
|
|
298
|
+
live = view;
|
|
299
|
+
},
|
|
300
|
+
/** Called explicitly by the renderer owner after replacing style/source on this instance. */
|
|
301
|
+
restoreAfterStyleChange() {
|
|
302
|
+
if (!mounted || disposed || !live || !props) return;
|
|
303
|
+
advance();
|
|
304
|
+
const view = props.mode === "controlled" ? props.view : live;
|
|
305
|
+
gesture = void 0;
|
|
306
|
+
settlement = void 0;
|
|
307
|
+
issue(view, true);
|
|
308
|
+
},
|
|
309
|
+
dispose() {
|
|
310
|
+
if (disposed) return;
|
|
311
|
+
disposed = true;
|
|
312
|
+
gesture = void 0;
|
|
313
|
+
settlement = void 0;
|
|
314
|
+
props = void 0;
|
|
315
|
+
report = void 0;
|
|
316
|
+
const previous = pending;
|
|
317
|
+
pending = void 0;
|
|
318
|
+
retire(previous);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
export {
|
|
323
|
+
CameraControllerError,
|
|
324
|
+
createMapCameraController
|
|
325
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TileflowNativeSourceState } from '@tileflow/core/native';
|
|
2
|
+
import { HostedNativeSessionBinding } from './session.js';
|
|
3
|
+
|
|
4
|
+
type MobileConfiguration = Readonly<{
|
|
5
|
+
apiOrigin: string;
|
|
6
|
+
credential: string;
|
|
7
|
+
}>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Private source-to-session boundary, with no network or renderer capability.
|
|
11
|
+
* Its caller owns the separate session controller and retires it with the real Map.
|
|
12
|
+
*/
|
|
13
|
+
declare function createHostedNativeBindingResolver(readConfiguration: () => Promise<MobileConfiguration>): Readonly<{
|
|
14
|
+
replace(source: TileflowNativeSourceState): Promise<HostedNativeSessionBinding>;
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export { createHostedNativeBindingResolver };
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/mobile-configuration.ts
|
|
6
|
+
var messages = {
|
|
7
|
+
NATIVE_CONFIGURATION_INVALID: "Native application configuration is invalid.",
|
|
8
|
+
NATIVE_CONFIGURATION_UNAVAILABLE: "Native application configuration is unavailable.",
|
|
9
|
+
NATIVE_CONFIGURATION_SOURCE_INVALID: "Hosted source metadata is invalid.",
|
|
10
|
+
NATIVE_CONFIGURATION_ORIGIN_MISMATCH: "Hosted source origin is not approved by the application.",
|
|
11
|
+
NATIVE_CONFIGURATION_REPLACED: "Hosted binding resolution was replaced.",
|
|
12
|
+
NATIVE_CONFIGURATION_DISPOSED: "Hosted binding resolution is disposed."
|
|
13
|
+
};
|
|
14
|
+
var originPattern = /^([Hh][Tt][Tt][Pp][Ss]):\/\/([A-Za-z0-9.-]+)(?::([1-9][0-9]{0,4}))?\/?$/u;
|
|
15
|
+
var NativeConfigurationError = class extends Error {
|
|
16
|
+
constructor(code) {
|
|
17
|
+
super(messages[code]);
|
|
18
|
+
__publicField(this, "code");
|
|
19
|
+
this.name = "NativeConfigurationError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function invalid() {
|
|
24
|
+
throw new NativeConfigurationError("NATIVE_CONFIGURATION_INVALID");
|
|
25
|
+
}
|
|
26
|
+
function canonicalMobileApiOrigin(value) {
|
|
27
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 2048) return invalid();
|
|
28
|
+
const match = originPattern.exec(value);
|
|
29
|
+
if (!match || match[0] !== value) return invalid();
|
|
30
|
+
const host = match[2].toLowerCase();
|
|
31
|
+
const labels = host.split(".");
|
|
32
|
+
if (host.length > 253 || labels.some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(label))) {
|
|
33
|
+
return invalid();
|
|
34
|
+
}
|
|
35
|
+
const last = labels[labels.length - 1];
|
|
36
|
+
if (!/^[a-z]/u.test(last)) {
|
|
37
|
+
if (labels.length !== 4 || labels.some((label) => !/^(?:0|[1-9][0-9]{0,2})$/u.test(label) || Number(label) > 255)) {
|
|
38
|
+
return invalid();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const port = match[3] === void 0 ? 443 : Number(match[3]);
|
|
42
|
+
if (port > 65535) return invalid();
|
|
43
|
+
return `https://${host}${port === 443 ? "" : `:${port}`}`;
|
|
44
|
+
}
|
|
45
|
+
function configurationRecord(value) {
|
|
46
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return invalid();
|
|
47
|
+
const prototype = Object.getPrototypeOf(value);
|
|
48
|
+
if (prototype !== Object.prototype && prototype !== null) return invalid();
|
|
49
|
+
const keys = Reflect.ownKeys(value);
|
|
50
|
+
if (keys.length > 32) return invalid();
|
|
51
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
52
|
+
for (const key of keys) {
|
|
53
|
+
if (typeof key !== "string") return invalid();
|
|
54
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
55
|
+
if (!descriptor || !("value" in descriptor)) return invalid();
|
|
56
|
+
result[key] = descriptor.value;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function snapshotMobileConfiguration(value) {
|
|
61
|
+
try {
|
|
62
|
+
const data = configurationRecord(value);
|
|
63
|
+
if (Object.keys(data).length !== 2 || !Object.hasOwn(data, "apiOrigin") || !Object.hasOwn(data, "credential") || typeof data.credential !== "string" || data.credential.length !== 58 || !/^tf_public_[0-9a-f]{48}$/u.test(data.credential)) {
|
|
64
|
+
return invalid();
|
|
65
|
+
}
|
|
66
|
+
const apiOrigin = canonicalMobileApiOrigin(data.apiOrigin);
|
|
67
|
+
const snapshot = Object.defineProperties(
|
|
68
|
+
{},
|
|
69
|
+
{
|
|
70
|
+
apiOrigin: { value: apiOrigin },
|
|
71
|
+
credential: { value: data.credential }
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
return Object.freeze(snapshot);
|
|
75
|
+
} catch {
|
|
76
|
+
return invalid();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/hosted-manifest-identity.ts
|
|
81
|
+
var mapPattern = /^map_[A-Za-z0-9_-]{16}$/u;
|
|
82
|
+
var themePattern = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/u;
|
|
83
|
+
var revisionPattern = /^[a-f0-9]{64}$/u;
|
|
84
|
+
function record(value) {
|
|
85
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
|
|
86
|
+
const prototype = Object.getPrototypeOf(value);
|
|
87
|
+
if (prototype !== Object.prototype && prototype !== null) throw new Error();
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function field(value, name) {
|
|
91
|
+
const descriptor = Object.getOwnPropertyDescriptor(record(value), name);
|
|
92
|
+
if (!descriptor) return void 0;
|
|
93
|
+
if (!descriptor.enumerable || !("value" in descriptor)) throw new Error();
|
|
94
|
+
return descriptor.value;
|
|
95
|
+
}
|
|
96
|
+
function names(value, maximum) {
|
|
97
|
+
const keys = Reflect.ownKeys(record(value));
|
|
98
|
+
if (!keys.length || keys.length > maximum || keys.some((key) => typeof key !== "string"))
|
|
99
|
+
throw new Error();
|
|
100
|
+
return keys;
|
|
101
|
+
}
|
|
102
|
+
function reservedManifest(value) {
|
|
103
|
+
if (typeof value !== "string" || value.length > 2048) throw new Error();
|
|
104
|
+
return /^https:\/\/[^/?#]+\/maps\/[^/?#]+\/native\//iu.test(decodeURIComponent(value));
|
|
105
|
+
}
|
|
106
|
+
function exactApiOrigin(value) {
|
|
107
|
+
if (typeof value !== "string" || canonicalMobileApiOrigin(value) !== value) throw new Error();
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
function styleVersion(url, origin, mapId, theme) {
|
|
111
|
+
if (typeof url !== "string" || url.length > 2048) throw new Error();
|
|
112
|
+
const prefix = `${origin}/maps/${mapId}/native/v`;
|
|
113
|
+
const suffix = `/${theme}.json`;
|
|
114
|
+
if (!url.startsWith(prefix) || !url.endsWith(suffix)) throw new Error();
|
|
115
|
+
const raw = url.slice(prefix.length, -suffix.length);
|
|
116
|
+
if (!/^[1-9][0-9]{0,15}$/u.test(raw)) throw new Error();
|
|
117
|
+
const version = Number(raw);
|
|
118
|
+
if (!Number.isSafeInteger(version)) throw new Error();
|
|
119
|
+
return version;
|
|
120
|
+
}
|
|
121
|
+
function snapshotHostedNativeManifestIdentity(source) {
|
|
122
|
+
try {
|
|
123
|
+
if (field(source, "status") !== "ready") throw new Error();
|
|
124
|
+
const map = field(source, "map");
|
|
125
|
+
const mode = field(map, "usageMode");
|
|
126
|
+
const input = field(source, "source");
|
|
127
|
+
const sourceUrl = field(input, "manifestUrl");
|
|
128
|
+
const finalUrl = field(source, "manifestUrl");
|
|
129
|
+
if (mode === void 0) {
|
|
130
|
+
if (reservedManifest(sourceUrl) || reservedManifest(finalUrl)) throw new Error();
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
if (mode !== "session") throw new Error();
|
|
134
|
+
const mapId = field(map, "mapId");
|
|
135
|
+
const name = field(map, "name");
|
|
136
|
+
if (typeof mapId !== "string" || !mapPattern.test(mapId) || typeof name !== "string" || !themePattern.test(name) || name !== field(input, "map"))
|
|
137
|
+
throw new Error();
|
|
138
|
+
const apiOrigin = exactApiOrigin(field(map, "apiUrl"));
|
|
139
|
+
const expected = `${apiOrigin}/maps/${mapId}/native/manifest.json`;
|
|
140
|
+
if (sourceUrl !== expected || finalUrl !== expected) throw new Error();
|
|
141
|
+
const manifest = field(source, "manifest");
|
|
142
|
+
const maps = field(manifest, "maps");
|
|
143
|
+
if (field(manifest, "version") !== 1 || names(maps, 1)[0] !== name) throw new Error();
|
|
144
|
+
const declared = field(maps, name);
|
|
145
|
+
if (field(declared, "mapId") !== mapId || field(declared, "usageMode") !== "session")
|
|
146
|
+
throw new Error();
|
|
147
|
+
const rootApi = field(manifest, "apiUrl");
|
|
148
|
+
const declaredApi = field(declared, "apiUrl") ?? rootApi;
|
|
149
|
+
if (declaredApi !== apiOrigin || rootApi !== void 0 && rootApi !== apiOrigin)
|
|
150
|
+
throw new Error();
|
|
151
|
+
const themes = field(map, "themes");
|
|
152
|
+
const declaredThemes = field(declared, "themes");
|
|
153
|
+
const themeNames = names(themes, 64).sort();
|
|
154
|
+
if (themeNames.join("\0") !== names(declaredThemes, 64).sort().join("\0") || field(map, "defaultTheme") !== field(declared, "defaultTheme"))
|
|
155
|
+
throw new Error();
|
|
156
|
+
let deploymentVersion;
|
|
157
|
+
for (const themeName of themeNames) {
|
|
158
|
+
if (!themePattern.test(themeName)) throw new Error();
|
|
159
|
+
const theme2 = field(themes, themeName);
|
|
160
|
+
const original = field(declaredThemes, themeName);
|
|
161
|
+
const revision = field(theme2, "revision");
|
|
162
|
+
const styleUrl = field(theme2, "styleUrl");
|
|
163
|
+
if (typeof revision !== "string" || !revisionPattern.test(revision) || revision !== field(original, "revision") || styleUrl !== field(original, "styleUrl") || field(theme2, "colorScheme") !== field(original, "colorScheme"))
|
|
164
|
+
throw new Error();
|
|
165
|
+
const version = styleVersion(styleUrl, apiOrigin, mapId, themeName);
|
|
166
|
+
if (deploymentVersion !== void 0 && deploymentVersion !== version) throw new Error();
|
|
167
|
+
deploymentVersion = version;
|
|
168
|
+
}
|
|
169
|
+
const selected = field(source, "theme");
|
|
170
|
+
const selectedName = field(selected, "name");
|
|
171
|
+
if (typeof selectedName !== "string" || !themeNames.includes(selectedName)) throw new Error();
|
|
172
|
+
const theme = field(themes, selectedName);
|
|
173
|
+
if (field(selected, "styleUrl") !== field(theme, "styleUrl") || field(selected, "revision") !== field(theme, "revision"))
|
|
174
|
+
throw new Error();
|
|
175
|
+
return Object.freeze({ mapId, apiOrigin, deploymentVersion });
|
|
176
|
+
} catch {
|
|
177
|
+
throw new NativeConfigurationError("NATIVE_CONFIGURATION_SOURCE_INVALID");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/hosted-binding.ts
|
|
182
|
+
function bind(metadata, configuration) {
|
|
183
|
+
const snapshot = snapshotMobileConfiguration(configuration);
|
|
184
|
+
if (snapshot.apiOrigin !== metadata.apiOrigin) {
|
|
185
|
+
throw new NativeConfigurationError("NATIVE_CONFIGURATION_ORIGIN_MISMATCH");
|
|
186
|
+
}
|
|
187
|
+
const binding = Object.defineProperties(
|
|
188
|
+
{ kind: "hosted", mapId: metadata.mapId },
|
|
189
|
+
{
|
|
190
|
+
apiOrigin: { value: snapshot.apiOrigin },
|
|
191
|
+
credential: { value: snapshot.credential }
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
return Object.freeze(binding);
|
|
195
|
+
}
|
|
196
|
+
function createHostedNativeBindingResolver(readConfiguration) {
|
|
197
|
+
let disposed = false;
|
|
198
|
+
let generation = 0;
|
|
199
|
+
let active;
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
replace(source) {
|
|
202
|
+
if (disposed) {
|
|
203
|
+
return Promise.reject(new NativeConfigurationError("NATIVE_CONFIGURATION_DISPOSED"));
|
|
204
|
+
}
|
|
205
|
+
if (generation >= Number.MAX_SAFE_INTEGER) {
|
|
206
|
+
return Promise.reject(new NativeConfigurationError("NATIVE_CONFIGURATION_SOURCE_INVALID"));
|
|
207
|
+
}
|
|
208
|
+
const sequence = ++generation;
|
|
209
|
+
let resolve;
|
|
210
|
+
let reject;
|
|
211
|
+
const result = new Promise((yes, no) => {
|
|
212
|
+
resolve = yes;
|
|
213
|
+
reject = no;
|
|
214
|
+
});
|
|
215
|
+
void result.catch(() => void 0);
|
|
216
|
+
const previous = active;
|
|
217
|
+
const own = { reject };
|
|
218
|
+
active = own;
|
|
219
|
+
const current = () => !disposed && generation === sequence && active === own;
|
|
220
|
+
previous?.reject(new NativeConfigurationError("NATIVE_CONFIGURATION_REPLACED"));
|
|
221
|
+
let metadata;
|
|
222
|
+
try {
|
|
223
|
+
metadata = snapshotHostedNativeManifestIdentity(source);
|
|
224
|
+
} catch {
|
|
225
|
+
if (current()) {
|
|
226
|
+
active = void 0;
|
|
227
|
+
reject(new NativeConfigurationError("NATIVE_CONFIGURATION_SOURCE_INVALID"));
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
if (!current()) return result;
|
|
232
|
+
if (!metadata) {
|
|
233
|
+
active = void 0;
|
|
234
|
+
resolve(Object.freeze({ kind: "direct" }));
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
const selected = metadata;
|
|
238
|
+
void Promise.resolve().then(() => {
|
|
239
|
+
if (!current()) return void 0;
|
|
240
|
+
return readConfiguration();
|
|
241
|
+
}).then((configuration) => {
|
|
242
|
+
if (!current()) return;
|
|
243
|
+
const binding = bind(selected, configuration);
|
|
244
|
+
if (!current()) return;
|
|
245
|
+
active = void 0;
|
|
246
|
+
resolve(binding);
|
|
247
|
+
}).catch((error) => {
|
|
248
|
+
if (!current()) return;
|
|
249
|
+
let code;
|
|
250
|
+
try {
|
|
251
|
+
if (error && typeof error === "object") {
|
|
252
|
+
code = Object.getOwnPropertyDescriptor(error, "code")?.value;
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
if (!current()) return;
|
|
257
|
+
active = void 0;
|
|
258
|
+
reject(
|
|
259
|
+
new NativeConfigurationError(
|
|
260
|
+
code === "NATIVE_CONFIGURATION_INVALID" || code === "NATIVE_CONFIGURATION_ORIGIN_MISMATCH" ? code : "NATIVE_CONFIGURATION_UNAVAILABLE"
|
|
261
|
+
)
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
return result;
|
|
265
|
+
},
|
|
266
|
+
dispose() {
|
|
267
|
+
if (disposed) return;
|
|
268
|
+
disposed = true;
|
|
269
|
+
const previous = active;
|
|
270
|
+
active = void 0;
|
|
271
|
+
previous?.reject(new NativeConfigurationError("NATIVE_CONFIGURATION_DISPOSED"));
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
export {
|
|
276
|
+
createHostedNativeBindingResolver
|
|
277
|
+
};
|