@expofp/config 3.19.0 → 3.20.1
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/legacy-url.d.ts +15 -0
- package/dist/lib/legacy-url.js +49 -2
- package/dist/lib/load-config.d.ts +3 -2
- package/dist/lib/load-config.js +30 -13
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { applyIntents } from './lib/apply-intents.js';
|
|
2
2
|
export { getConfig, setConfig } from './lib/config-store.js';
|
|
3
3
|
export { type DebugSettingDescriptor, toDebugSettings } from './lib/debug-settings.js';
|
|
4
|
-
export { type LegacyOneShot, type LegacyPrimary, type LegacyUrlQuery, parseLegacyQuery, serializeSelection, type UrlSelection, } from './lib/legacy-url.js';
|
|
4
|
+
export { type LegacyOneShot, type LegacyPrimary, type LegacyUrlQuery, parseLegacyQuery, serializeSelection, splitPlatformParams, type UrlSelection, } from './lib/legacy-url.js';
|
|
5
5
|
export { loadConfig } from './lib/load-config.js';
|
|
6
6
|
export { type StorageLike } from './lib/local-storage-codec.js';
|
|
7
7
|
export { type ConfigResourceStore, serializeConfigResources, } from './lib/serialize-config-resources.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { applyIntents } from './lib/apply-intents.js';
|
|
2
2
|
export { getConfig, setConfig } from './lib/config-store.js';
|
|
3
3
|
export { toDebugSettings } from './lib/debug-settings.js';
|
|
4
|
-
export { parseLegacyQuery, serializeSelection, } from './lib/legacy-url.js';
|
|
4
|
+
export { parseLegacyQuery, serializeSelection, splitPlatformParams, } from './lib/legacy-url.js';
|
|
5
5
|
export { loadConfig } from './lib/load-config.js';
|
|
6
6
|
export { serializeConfigResources, } from './lib/serialize-config-resources.js';
|
|
7
7
|
export { parseFromUrlTolerant } from './lib/url-codec.js';
|
package/dist/lib/legacy-url.d.ts
CHANGED
|
@@ -157,6 +157,21 @@ export type UrlSelection = {
|
|
|
157
157
|
value: string;
|
|
158
158
|
};
|
|
159
159
|
export declare function parseLegacyQuery(search: string): LegacyUrlQuery;
|
|
160
|
+
/**
|
|
161
|
+
* Split a raw `location.search` into the floor plan's own query and the
|
|
162
|
+
* platform's routing params (`?expo=…` — {@link platformParamKeys}).
|
|
163
|
+
* Raw-segment level (split on `&`, no decoding): platform params travel
|
|
164
|
+
* outside the legacy grammar's single-`encodeURIComponent`-unit payload, and
|
|
165
|
+
* that payload must pass through untouched. The floor plan's single URL
|
|
166
|
+
* writer (`url-sync`) hides the platform half from what the app reads back
|
|
167
|
+
* and re-attaches it verbatim to every URL it writes — a preview URL
|
|
168
|
+
* `/s/<sha>/?expo=…` must survive reload and sharing after any rewrite.
|
|
169
|
+
* When no platform param is present, `search` is returned byte-identical.
|
|
170
|
+
*/
|
|
171
|
+
export declare function splitPlatformParams(search: string): {
|
|
172
|
+
search: string;
|
|
173
|
+
platform: string;
|
|
174
|
+
};
|
|
160
175
|
/**
|
|
161
176
|
* The selection's canonical query — `''` for none, else `?<payload>` with the
|
|
162
177
|
* whole payload as one `encodeURIComponent` unit (the legacy contract). This is
|
package/dist/lib/legacy-url.js
CHANGED
|
@@ -44,6 +44,23 @@ const oneShotKeys = new Set(['blue-dot', 'viewermode', 'previewMode', 'uiscale',
|
|
|
44
44
|
* one of these words (a booth named "center") still resolves via the catalog.
|
|
45
45
|
*/
|
|
46
46
|
const legacyCameraKeys = new Set(['centerxy', 'center', 'z', 'bearing', 'zoomtime', 'roll']);
|
|
47
|
+
/**
|
|
48
|
+
* Query keys owned by the DELIVERY PLATFORM, not the floor plan: `?expo=` is
|
|
49
|
+
* the routing param the sha-delivery platform materializes for expo-aware
|
|
50
|
+
* origins, and preview URLs (`/s/<sha>/?expo=…`) carry it literally in the
|
|
51
|
+
* page URL (design note 2026-08-05-sha-delivery-iac.md § 2). Not a grammar
|
|
52
|
+
* extension — a third partition of the query alongside config-owned and
|
|
53
|
+
* one-shot keys: the parser strips these from the residual slug (in their
|
|
54
|
+
* `key=value` form only — a bare slug that merely spells `expo` still
|
|
55
|
+
* resolves via the catalog), and the floor plan's URL writer carries them
|
|
56
|
+
* verbatim across query rewrites ({@link splitPlatformParams}).
|
|
57
|
+
*
|
|
58
|
+
* Unlike config-owned keys, platform params do NOT classify a query as
|
|
59
|
+
* `ownedByConfig`: `?expo=…` alone DESCRIBES the empty selection — browser
|
|
60
|
+
* back onto it must clear the selection like any genuinely empty query,
|
|
61
|
+
* whereas config keys are instructions riding alongside the selection state.
|
|
62
|
+
*/
|
|
63
|
+
const platformParamKeys = new Set(['expo']);
|
|
47
64
|
export function parseLegacyQuery(search) {
|
|
48
65
|
const rawQuery = search.startsWith('?') ? search.slice(1) : search;
|
|
49
66
|
// the legacy contract: the payload is one encodeURIComponent unit
|
|
@@ -246,8 +263,9 @@ function finiteNumber(value) {
|
|
|
246
263
|
}
|
|
247
264
|
/**
|
|
248
265
|
* The slug is the decoded query minus every key another consumer owns: the
|
|
249
|
-
* config layer's keys and intent shortcuts (read by `loadConfig`),
|
|
250
|
-
* one-shot keys consumed above
|
|
266
|
+
* config layer's keys and intent shortcuts (read by `loadConfig`), the
|
|
267
|
+
* one-shot keys consumed above, and the platform's routing params.
|
|
268
|
+
* `heatmap`/`type`/`subtype` stay in the slug —
|
|
251
269
|
* heatmap mode historically froze the URL with them in place (its data loader
|
|
252
270
|
* re-reads them), and the select fallback knows not to treat a query carrying
|
|
253
271
|
* `heatmap=true` as search text.
|
|
@@ -270,6 +288,8 @@ function residualSlug(decoded) {
|
|
|
270
288
|
const key = part.split('=')[0].split('[')[0];
|
|
271
289
|
if (legacyCameraKeys.has(key) && part.includes('='))
|
|
272
290
|
return false;
|
|
291
|
+
if (platformParamKeys.has(key) && part.includes('='))
|
|
292
|
+
return false;
|
|
273
293
|
// Carve-out (retirement plan §4.8): `heatmap` is a UrlConfigSchema key
|
|
274
294
|
// now, so the canonical bracketed `heatmap[…]=` form is config-owned and
|
|
275
295
|
// strips like any other — but the legacy scalar `?heatmap=true` must
|
|
@@ -282,6 +302,33 @@ function residualSlug(decoded) {
|
|
|
282
302
|
})
|
|
283
303
|
.join('&');
|
|
284
304
|
}
|
|
305
|
+
/* ── Platform routing params: the writer-side partition ────────────────────── */
|
|
306
|
+
/**
|
|
307
|
+
* Split a raw `location.search` into the floor plan's own query and the
|
|
308
|
+
* platform's routing params (`?expo=…` — {@link platformParamKeys}).
|
|
309
|
+
* Raw-segment level (split on `&`, no decoding): platform params travel
|
|
310
|
+
* outside the legacy grammar's single-`encodeURIComponent`-unit payload, and
|
|
311
|
+
* that payload must pass through untouched. The floor plan's single URL
|
|
312
|
+
* writer (`url-sync`) hides the platform half from what the app reads back
|
|
313
|
+
* and re-attaches it verbatim to every URL it writes — a preview URL
|
|
314
|
+
* `/s/<sha>/?expo=…` must survive reload and sharing after any rewrite.
|
|
315
|
+
* When no platform param is present, `search` is returned byte-identical.
|
|
316
|
+
*/
|
|
317
|
+
export function splitPlatformParams(search) {
|
|
318
|
+
const raw = search.startsWith('?') ? search.slice(1) : search;
|
|
319
|
+
if (!raw)
|
|
320
|
+
return { search, platform: '' };
|
|
321
|
+
const segments = raw.split('&');
|
|
322
|
+
const platform = segments.filter(isPlatformParam);
|
|
323
|
+
if (!platform.length)
|
|
324
|
+
return { search, platform: '' };
|
|
325
|
+
const app = segments.filter((segment) => !isPlatformParam(segment));
|
|
326
|
+
return { search: app.length ? '?' + app.join('&') : '', platform: platform.join('&') };
|
|
327
|
+
}
|
|
328
|
+
function isPlatformParam(segment) {
|
|
329
|
+
const eq = segment.indexOf('=');
|
|
330
|
+
return eq > 0 && platformParamKeys.has(segment.slice(0, eq));
|
|
331
|
+
}
|
|
285
332
|
/* ── Serialize: selection → location.search ────────────────────────────────── */
|
|
286
333
|
/**
|
|
287
334
|
* The selection's canonical query — `''` for none, else `?<payload>` with the
|
|
@@ -6,8 +6,9 @@ import { type StorageLike } from './local-storage-codec.js';
|
|
|
6
6
|
* expo data — by layering, lowest to highest precedence:
|
|
7
7
|
* manifest (+ its legacy data.js) → options → localStorage → URL.
|
|
8
8
|
*
|
|
9
|
-
* With a `legacyDataUrlBase` (live events), the
|
|
10
|
-
*
|
|
9
|
+
* With a `legacyDataUrlBase` (live events), the manifest's `legacyDataVersion`
|
|
10
|
+
* pin — or, absent one, the version probe — settles the `?v=` cache-buster,
|
|
11
|
+
* refs to the sibling drawing / wayfinding files are
|
|
11
12
|
* derived from it (`data.js` never carries them), and all three legacy files —
|
|
12
13
|
* `data.js` evaluated to its `__data` document like every legacy `.js` file,
|
|
13
14
|
* asset-resolved and merged in — are fetched in parallel. A manifest can
|
package/dist/lib/load-config.js
CHANGED
|
@@ -17,8 +17,9 @@ const log = debug('efp:config');
|
|
|
17
17
|
* expo data — by layering, lowest to highest precedence:
|
|
18
18
|
* manifest (+ its legacy data.js) → options → localStorage → URL.
|
|
19
19
|
*
|
|
20
|
-
* With a `legacyDataUrlBase` (live events), the
|
|
21
|
-
*
|
|
20
|
+
* With a `legacyDataUrlBase` (live events), the manifest's `legacyDataVersion`
|
|
21
|
+
* pin — or, absent one, the version probe — settles the `?v=` cache-buster,
|
|
22
|
+
* refs to the sibling drawing / wayfinding files are
|
|
22
23
|
* derived from it (`data.js` never carries them), and all three legacy files —
|
|
23
24
|
* `data.js` evaluated to its `__data` document like every legacy `.js` file,
|
|
24
25
|
* asset-resolved and merged in — are fetched in parallel. A manifest can
|
|
@@ -45,28 +46,44 @@ export async function loadConfig(manifest, options, url, storage) {
|
|
|
45
46
|
// manifest carries the full event data (booths, exhibitors, …), and
|
|
46
47
|
// `normalizeLegacyData` below mutates those nested objects in place.
|
|
47
48
|
manifest = validateOrClone(ManifestSchema, await resolve(manifest));
|
|
48
|
-
// The legacy event-data directory
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const { legacyDataUrlBase, legacyDataVersion: _manifestVersion, ...manifestFields } = manifest;
|
|
49
|
+
// The legacy event-data directory and the data-revision pin are INPUTS,
|
|
50
|
+
// consumed right here — the effective config carries only the refs derived
|
|
51
|
+
// from them, so neither is merged.
|
|
52
|
+
const { legacyDataUrlBase, legacyDataVersion: manifestPin, ...manifestFields } = manifest;
|
|
53
53
|
applyConsentAlias(manifestFields);
|
|
54
54
|
assignDefined(config, manifestFields);
|
|
55
55
|
// don't trust, parse
|
|
56
56
|
options = OptionsSchema.parse(options);
|
|
57
57
|
applyConsentAlias(options);
|
|
58
|
-
// every legacy file URL is ?v= cache-busted; a timestamp busts when
|
|
59
|
-
// version.json
|
|
58
|
+
// every legacy file URL is ?v= cache-busted; a timestamp busts when neither
|
|
59
|
+
// the manifest nor version.json pins a revision (TODO: drop once the legacy
|
|
60
|
+
// data directory is gone)
|
|
60
61
|
let legacyDataVersion = String(Date.now());
|
|
61
|
-
// live legacy directory:
|
|
62
|
-
//
|
|
62
|
+
// live legacy directory: settle the version (manifest pin, else the
|
|
63
|
+
// version.json probe), then the sibling file refs — data.js never carries
|
|
64
|
+
// the payload refs, so both are known before it lands
|
|
63
65
|
if (legacyDataUrlBase) {
|
|
64
66
|
if (isFromDesignerReferrer()) {
|
|
65
67
|
// The designer previews in-progress edits — the directory's files change
|
|
66
|
-
// without a version bump, so
|
|
67
|
-
//
|
|
68
|
+
// without a version bump, so a published version (the manifest pin and
|
|
69
|
+
// version.json alike) must not pin `?v=`; the unique timestamp stands
|
|
70
|
+
// and every load fetches fresh files.
|
|
68
71
|
log('loadConfig', 'designer referrer — cache-busting with a timestamp');
|
|
69
72
|
}
|
|
73
|
+
else if (typeof manifestPin === 'string' && manifestPin) {
|
|
74
|
+
// The manifest pins the data revision (get-manifest already probed
|
|
75
|
+
// version.json server-side) — honor it, no client probe: manifest and
|
|
76
|
+
// data stay consistent by construction.
|
|
77
|
+
legacyDataVersion = manifestPin;
|
|
78
|
+
}
|
|
79
|
+
else if (manifestPin === null) {
|
|
80
|
+
// The generator probed and there IS no version.json for this expo — the
|
|
81
|
+
// per-load timestamp stands, and the client must not re-probe (on the
|
|
82
|
+
// cross-origin contour that probe could only fail). Only an ABSENT pin
|
|
83
|
+
// (legacy manifests; the retired `$ref` pointer counts as absent) falls
|
|
84
|
+
// through to the probe below.
|
|
85
|
+
log('loadConfig', 'manifest pins "no version" — cache-busting with a timestamp');
|
|
86
|
+
}
|
|
70
87
|
else {
|
|
71
88
|
try {
|
|
72
89
|
legacyDataVersion = (await resolve({ $ref: `${legacyDataUrlBase}version.json` })).version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/config",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ExpoFP SDK internal: config layer and schemas",
|
|
6
6
|
"homepage": "https://developer.expofp.com/",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"debug": "^4.4.3",
|
|
30
30
|
"tslib": "^2.3.0",
|
|
31
31
|
"zod": "4.4.3",
|
|
32
|
-
"@expofp/schema": "3.
|
|
33
|
-
"@expofp/
|
|
34
|
-
"@expofp/
|
|
32
|
+
"@expofp/schema": "3.20.1",
|
|
33
|
+
"@expofp/utils": "3.20.1",
|
|
34
|
+
"@expofp/resolve": "3.20.1"
|
|
35
35
|
}
|
|
36
36
|
}
|