@tileflow/svelte 0.1.0-alpha.16 → 0.1.0-alpha.18

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 CHANGED
@@ -26,6 +26,10 @@ Tileflow resolves from the map name, manifest, or explicit style props. Direct
26
26
  Tileflow props such as `center`, `zoom`, and `interactive` take priority when
27
27
  provided.
28
28
 
29
+ Hosted maps automatically preflight a short-lived commercial session grant before eligible
30
+ resources. Setting `analytics={{enabled: false}}` disables the optional beacon only; it does not
31
+ remove hosted authorization or override a user `mapOptions.transformRequest` callback.
32
+
29
33
  ## Headless capture readiness
30
34
 
31
35
  Use `captureId` to disambiguate repeated named maps:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/svelte",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.18",
4
4
  "description": "Svelte component for rendering Tileflow maps with MapLibre.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -42,7 +42,7 @@
42
42
  "access": "public"
43
43
  },
44
44
  "dependencies": {
45
- "@tileflow/core": "0.1.0-alpha.16"
45
+ "@tileflow/core": ">=0.1.0-alpha.18 <0.1.0-beta.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "maplibre-gl": ">=5",
@@ -3,25 +3,21 @@
3
3
  type LngLatLike,
4
4
  type Map as MapLibreMap,
5
5
  type MapOptions as MapLibreMapOptions,
6
- type RequestParameters,
7
- type RequestTransformFunction,
8
6
  type StyleSpecification,
9
7
  } from 'maplibre-gl';
10
8
  import {createEventDispatcher, onMount, tick} from 'svelte';
11
9
  import {
12
- createTileflowSessionId,
10
+ createTileflowSessionController,
13
11
  defaultTileflowManifestUrl,
14
12
  loadTileflowManifest,
15
13
  mergeTileflowAnalytics,
16
14
  normalizeTileflowCaptureId,
17
15
  normalizeTileflowStaticImageSize,
18
- resolveTileflowAnalyticsRequestUrl,
19
16
  resolveTileflowManifestMap,
20
17
  resolveTileflowMapMode,
21
18
  resolveTileflowRuntimeStyle,
22
19
  resolveTileflowStaticImageUrl,
23
20
  shouldLoadTileflowManifest,
24
- startTileflowSession,
25
21
  type MapLibreStyle,
26
22
  type TileflowAnalytics,
27
23
  type TileflowConfig,
@@ -29,6 +25,13 @@
29
25
  type TileflowProjectThemes,
30
26
  type TileflowRuntimeManifestMap,
31
27
  } from '@tileflow/core';
28
+ import {
29
+ attachTileflowMapLifecycle,
30
+ createTileflowMarkerController,
31
+ createTileflowSessionStarter,
32
+ createTileflowTransformRequest,
33
+ type TileflowMapLifecycleAttachment,
34
+ } from '@tileflow/core/browser';
32
35
 
33
36
  export let alt = '';
34
37
  export let analytics: TileflowAnalytics | undefined = undefined;
@@ -63,7 +66,7 @@
63
66
  let imageSize: {height: number; width: number} | null = null;
64
67
  let imageResizeObserver: ResizeObserver | null = null;
65
68
  let mapInstance: MapLibreMap | null = null;
66
- let markerInstances: maplibregl.Marker[] = [];
69
+ let mapLifecycleAttachment: TileflowMapLifecycleAttachment | null = null;
67
70
  let mapResizeObserver: ResizeObserver | null = null;
68
71
  let manifestMap: TileflowRuntimeManifestMap | null = null;
69
72
  let manifestLoadId = 0;
@@ -72,8 +75,21 @@
72
75
  let readinessRunId = 0;
73
76
 
74
77
  const dispatch = createEventDispatcher<{load: MapLibreMap}>();
75
- const sessionId = createTileflowSessionId();
76
- const sessionStarts = new Set<string>();
78
+ const markerController = createTileflowMarkerController<
79
+ MapLibreMap,
80
+ TileflowMapMarker,
81
+ maplibregl.Marker
82
+ >({
83
+ attach(markerInstance, targetMap, definition) {
84
+ markerInstance.setLngLat(definition.coordinates).addTo(targetMap);
85
+ markerInstance.getElement().title = definition.label ?? definition.id;
86
+ },
87
+ create: (definition) =>
88
+ new maplibregl.Marker({
89
+ color: definition.color ?? '#C6A15B',
90
+ }),
91
+ remove: (markerInstance) => markerInstance.remove(),
92
+ });
77
93
 
78
94
  $: resolvedMode = resolveTileflowMapMode({imageUrl, mode, preferLocalDev});
79
95
  $: resolvedCaptureId = normalizeTileflowCaptureId(captureId);
@@ -225,11 +241,22 @@
225
241
 
226
242
  function destroyMap() {
227
243
  readinessRunId += 1;
244
+ const lifecycleAttachment = mapLifecycleAttachment;
245
+ const currentMap = mapInstance;
246
+ mapLifecycleAttachment = null;
247
+ mapInstance = null;
228
248
  mapResizeObserver?.disconnect();
229
249
  mapResizeObserver = null;
230
- clearMarkers();
231
- mapInstance?.remove();
232
- mapInstance = null;
250
+
251
+ try {
252
+ lifecycleAttachment?.dispose();
253
+ } finally {
254
+ try {
255
+ markerController.dispose();
256
+ } finally {
257
+ currentMap?.remove();
258
+ }
259
+ }
233
260
  }
234
261
 
235
262
  function recreateMap() {
@@ -241,6 +268,12 @@
241
268
  }
242
269
 
243
270
  const runtime = runtimeStyle;
271
+ const session = createTileflowSessionController({source: 'svelte'});
272
+ const sessionStarter = createTileflowSessionStarter({
273
+ getSessionId: () => session.sessionId,
274
+ sessionId: session.sessionId,
275
+ source: 'svelte',
276
+ });
244
277
  const maplibreMap = new maplibregl.Map({
245
278
  ...mapOptions,
246
279
  attributionControl: mapOptions?.attributionControl ?? {compact: true},
@@ -248,11 +281,14 @@
248
281
  container,
249
282
  interactive: resolvedInteractive,
250
283
  style: runtime.style as StyleSpecification | string,
251
- transformRequest: createTransformRequest(
252
- () => resolvedAnalytics,
253
- sessionId,
254
- mapOptions?.transformRequest,
255
- ),
284
+ transformRequest: createTileflowTransformRequest({
285
+ always: true,
286
+ asyncAnalyticsTiming: 'request',
287
+ getAnalytics: () => resolvedAnalytics,
288
+ sessionController: session,
289
+ sessionId: session.sessionId,
290
+ transformRequest: mapOptions?.transformRequest ?? undefined,
291
+ }),
256
292
  zoom: resolvedZoom,
257
293
  });
258
294
 
@@ -267,47 +303,29 @@
267
303
  });
268
304
  mapResizeObserver.observe(container);
269
305
 
270
- maplibreMap.on('load', () => {
271
- dispatch('load', maplibreMap);
272
- const analyticsForLoad = resolvedAnalytics;
273
- const styleId =
274
- analyticsForLoad?.styleId ?? (typeof runtime.style === 'string' ? runtime.style : map);
275
-
276
- if (analyticsForLoad?.mapId) {
277
- const sessionKey = `${sessionId}:${analyticsForLoad.mapId}:${styleId ?? ''}`;
278
-
279
- if (!sessionStarts.has(sessionKey)) {
280
- sessionStarts.add(sessionKey);
281
- startTileflowSession(analyticsForLoad, {
282
- mapId: analyticsForLoad.mapId,
283
- sessionId,
284
- source: 'svelte',
285
- styleId,
286
- });
287
- }
288
- }
289
- });
290
- maplibreMap.on('dataloading', () => {
291
- readinessRunId += 1;
292
- captureState = 'loading';
293
- });
294
- maplibreMap.on('styledataloading', () => {
295
- readinessRunId += 1;
296
- captureState = 'loading';
297
- });
298
- maplibreMap.on('idle', () => {
299
- const runId = ++readinessRunId;
300
- requestAnimationFrame(() => {
301
- requestAnimationFrame(() => {
302
- if (runId === readinessRunId && mapInstance === maplibreMap) {
303
- captureState = 'idle';
304
- }
305
- });
306
- });
307
- });
308
- maplibreMap.on('error', () => {
309
- readinessRunId += 1;
310
- captureState = 'error';
306
+ mapLifecycleAttachment = attachTileflowMapLifecycle<MapLibreMap, number>({
307
+ getSession: () => {
308
+ const analyticsForLoad = resolvedAnalytics;
309
+ return {
310
+ analytics: analyticsForLoad,
311
+ styleId:
312
+ analyticsForLoad?.styleId ?? (typeof runtime.style === 'string' ? runtime.style : map),
313
+ };
314
+ },
315
+ map: maplibreMap,
316
+ onLoad: () => dispatch('load', maplibreMap),
317
+ scheduler: {
318
+ cancelFrame: (frame) => cancelAnimationFrame(frame),
319
+ requestFrame: (callback) => requestAnimationFrame(callback),
320
+ },
321
+ sessionStarter,
322
+ setState: (state) => {
323
+ captureState = state;
324
+ },
325
+ subscribe: (targetMap, event, listener) => {
326
+ const subscription = targetMap.on(event, listener);
327
+ return () => subscription.unsubscribe();
328
+ },
311
329
  });
312
330
 
313
331
  syncMarkers();
@@ -321,30 +339,12 @@
321
339
  }
322
340
 
323
341
  function syncMarkers() {
324
- clearMarkers();
325
-
326
342
  if (!mapInstance) {
343
+ markerController.clear();
327
344
  return;
328
345
  }
329
346
 
330
- markerInstances = markers.map((marker) => {
331
- const markerInstance = new maplibregl.Marker({
332
- color: marker.color ?? '#C6A15B',
333
- })
334
- .setLngLat(marker.coordinates)
335
- .addTo(mapInstance!);
336
-
337
- markerInstance.getElement().title = marker.label ?? marker.id;
338
- return markerInstance;
339
- });
340
- }
341
-
342
- function clearMarkers() {
343
- for (const marker of markerInstances) {
344
- marker.remove();
345
- }
346
-
347
- markerInstances = [];
347
+ markerController.replace(mapInstance, markers);
348
348
  }
349
349
 
350
350
  async function handleImageLoad(event: Event) {
@@ -383,46 +383,6 @@
383
383
  captureState = 'error';
384
384
  }
385
385
 
386
- function createTransformRequest(
387
- getAnalytics: () => TileflowAnalytics | undefined,
388
- session: string,
389
- userTransformRequest?: TileflowMapOptions['transformRequest'],
390
- ): RequestTransformFunction | undefined {
391
- return (url, resourceType) => {
392
- const request = userTransformRequest?.(url, resourceType);
393
- const analyticsInput = getAnalytics();
394
-
395
- if (isPromiseLike(request)) {
396
- return request.then((resolvedRequest) =>
397
- applyTileflowAnalyticsRequest(url, resolvedRequest, analyticsInput, session) ??
398
- resolvedRequest,
399
- );
400
- }
401
-
402
- return applyTileflowAnalyticsRequest(url, request, analyticsInput, session);
403
- };
404
- }
405
-
406
- function applyTileflowAnalyticsRequest(
407
- url: string,
408
- request: RequestParameters | undefined,
409
- analyticsInput: TileflowAnalytics | undefined,
410
- session: string,
411
- ): RequestParameters | undefined {
412
- const requestUrl = request?.url ?? url;
413
- const nextUrl = resolveTileflowAnalyticsRequestUrl(requestUrl, analyticsInput, session);
414
-
415
- if (!nextUrl) {
416
- return request;
417
- }
418
-
419
- return request ? {...request, url: nextUrl} : {url: nextUrl};
420
- }
421
-
422
- function isPromiseLike<T>(value: T | Promise<T> | undefined): value is Promise<T> {
423
- return Boolean(value && typeof (value as Promise<T>).then === 'function');
424
- }
425
-
426
386
  function formatHeight(value: number | string): string {
427
387
  return typeof value === 'number' ? `${value}px` : value;
428
388
  }