@tileflow/core 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 +34 -2
- package/dist/browser.d.ts +77 -0
- package/dist/browser.js +337 -0
- package/dist/index.d.ts +3 -654
- package/dist/index.js +248 -6
- package/dist/runtime-Bg--GMWM.d.ts +680 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -6821,6 +6821,250 @@ function createTileflowSessionId() {
|
|
|
6821
6821
|
}
|
|
6822
6822
|
return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
6823
6823
|
}
|
|
6824
|
+
function createTileflowSessionController(input) {
|
|
6825
|
+
const fetchGrant = input.fetch ?? globalThis.fetch;
|
|
6826
|
+
const grantTimeoutMs = validateSessionGrantTimeout(input.grantTimeoutMs);
|
|
6827
|
+
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
6828
|
+
const sessionIdFactory = input.sessionIdFactory ?? createTileflowSessionId;
|
|
6829
|
+
const createSession = (startedAt) => ({
|
|
6830
|
+
binding: null,
|
|
6831
|
+
grant: null,
|
|
6832
|
+
grantPromise: null,
|
|
6833
|
+
pendingGrantRequests: 0,
|
|
6834
|
+
requestCount: 0,
|
|
6835
|
+
sessionId: sessionIdFactory(),
|
|
6836
|
+
startedAt
|
|
6837
|
+
});
|
|
6838
|
+
let activeSession = createSession(now().getTime());
|
|
6839
|
+
async function resolveGrant(session, analytics, at) {
|
|
6840
|
+
if (session.grant && session.grant.expiresAt - at > 3e4) {
|
|
6841
|
+
return { grant: session.grant, session };
|
|
6842
|
+
}
|
|
6843
|
+
session.pendingGrantRequests += 1;
|
|
6844
|
+
try {
|
|
6845
|
+
if (!session.grantPromise) {
|
|
6846
|
+
const pending = (async () => {
|
|
6847
|
+
try {
|
|
6848
|
+
const nextGrant = await requestSessionGrant({
|
|
6849
|
+
analytics,
|
|
6850
|
+
at,
|
|
6851
|
+
fetchGrant,
|
|
6852
|
+
timeoutMs: grantTimeoutMs,
|
|
6853
|
+
sessionId: session.sessionId,
|
|
6854
|
+
source: input.source
|
|
6855
|
+
});
|
|
6856
|
+
session.grant = nextGrant;
|
|
6857
|
+
return { grant: nextGrant, session };
|
|
6858
|
+
} catch (error) {
|
|
6859
|
+
if (!(error instanceof TileflowSessionRestartError)) throw error;
|
|
6860
|
+
const replacement = createSession(at);
|
|
6861
|
+
replacement.binding = session.binding;
|
|
6862
|
+
replacement.requestCount = session.pendingGrantRequests;
|
|
6863
|
+
if (activeSession === session) activeSession = replacement;
|
|
6864
|
+
const replacementPending = requestSessionGrant({
|
|
6865
|
+
analytics,
|
|
6866
|
+
at,
|
|
6867
|
+
fetchGrant,
|
|
6868
|
+
timeoutMs: grantTimeoutMs,
|
|
6869
|
+
sessionId: replacement.sessionId,
|
|
6870
|
+
source: input.source
|
|
6871
|
+
}).then((nextGrant) => {
|
|
6872
|
+
replacement.grant = nextGrant;
|
|
6873
|
+
return { grant: nextGrant, session: replacement };
|
|
6874
|
+
});
|
|
6875
|
+
replacement.grantPromise = replacementPending;
|
|
6876
|
+
try {
|
|
6877
|
+
return await replacementPending;
|
|
6878
|
+
} finally {
|
|
6879
|
+
if (replacement.grantPromise === replacementPending) {
|
|
6880
|
+
replacement.grantPromise = null;
|
|
6881
|
+
}
|
|
6882
|
+
}
|
|
6883
|
+
} finally {
|
|
6884
|
+
session.grantPromise = null;
|
|
6885
|
+
}
|
|
6886
|
+
})();
|
|
6887
|
+
session.grantPromise = pending;
|
|
6888
|
+
}
|
|
6889
|
+
return await session.grantPromise;
|
|
6890
|
+
} finally {
|
|
6891
|
+
session.pendingGrantRequests -= 1;
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
return {
|
|
6895
|
+
get sessionId() {
|
|
6896
|
+
return activeSession.sessionId;
|
|
6897
|
+
},
|
|
6898
|
+
async resolveRequestUrl(url, analytics) {
|
|
6899
|
+
if (!analytics || !analytics.mapId) return void 0;
|
|
6900
|
+
const at = now().getTime();
|
|
6901
|
+
if (at - activeSession.startedAt >= 6 * 60 * 60 * 1e3 || activeSession.requestCount >= 1e4) {
|
|
6902
|
+
activeSession = createSession(at);
|
|
6903
|
+
}
|
|
6904
|
+
const apiOrigin = originOf(getTileflowAnalyticsApiUrl(analytics));
|
|
6905
|
+
const binding = JSON.stringify([getTileflowAnalyticsApiUrl(analytics), analytics.mapId]);
|
|
6906
|
+
let session = activeSession;
|
|
6907
|
+
const grantedOrigins = session.binding === binding ? session.grant?.resourceOrigins : void 0;
|
|
6908
|
+
if (!isEligibleResource(url, analytics.mapId, apiOrigin, grantedOrigins)) {
|
|
6909
|
+
return void 0;
|
|
6910
|
+
}
|
|
6911
|
+
if (session.binding !== null && session.binding !== binding) {
|
|
6912
|
+
session = createSession(at);
|
|
6913
|
+
activeSession = session;
|
|
6914
|
+
}
|
|
6915
|
+
session.binding = binding;
|
|
6916
|
+
session.requestCount += 1;
|
|
6917
|
+
const resolved = await resolveGrant(session, analytics, at);
|
|
6918
|
+
const nextUrl = new URL(url);
|
|
6919
|
+
nextUrl.searchParams.set("session", resolved.session.sessionId);
|
|
6920
|
+
nextUrl.searchParams.set("map", analytics.mapId);
|
|
6921
|
+
if (analytics.styleId) nextUrl.searchParams.set("styleId", analytics.styleId);
|
|
6922
|
+
if (resolved.grant.token) nextUrl.searchParams.set("grant", resolved.grant.token);
|
|
6923
|
+
return nextUrl.toString();
|
|
6924
|
+
}
|
|
6925
|
+
};
|
|
6926
|
+
}
|
|
6927
|
+
async function requestSessionGrant(input) {
|
|
6928
|
+
return runWithinSessionGrantTimeout(input.timeoutMs, async (signal) => {
|
|
6929
|
+
const apiUrl = getTileflowAnalyticsApiUrl(input.analytics);
|
|
6930
|
+
const response = await input.fetchGrant(`${apiUrl}/v1/sessions/start`, {
|
|
6931
|
+
body: JSON.stringify({
|
|
6932
|
+
mapId: input.analytics.mapId,
|
|
6933
|
+
sdkVersion: input.analytics.sdkVersion,
|
|
6934
|
+
sessionId: input.sessionId,
|
|
6935
|
+
source: input.analytics.source ?? input.source,
|
|
6936
|
+
styleId: input.analytics.styleId
|
|
6937
|
+
}),
|
|
6938
|
+
credentials: "omit",
|
|
6939
|
+
headers: { "Content-Type": "application/json" },
|
|
6940
|
+
method: "POST",
|
|
6941
|
+
signal
|
|
6942
|
+
});
|
|
6943
|
+
throwIfSessionGrantAborted(signal);
|
|
6944
|
+
const body = await response.json().catch(() => null);
|
|
6945
|
+
throwIfSessionGrantAborted(signal);
|
|
6946
|
+
if (response.status === 409 && isRecord6(body) && body.code === "COMMERCIAL_SESSION_RESTART_REQUIRED" && body.ok === false && body.retryWithNewSession === true && body.sessionId === input.sessionId) {
|
|
6947
|
+
throw new TileflowSessionRestartError();
|
|
6948
|
+
}
|
|
6949
|
+
if (!response.ok) {
|
|
6950
|
+
throw new Error(`Tileflow session grant failed: ${response.status}`);
|
|
6951
|
+
}
|
|
6952
|
+
const parsed = parseSessionGrantResponse(body, input.sessionId);
|
|
6953
|
+
if (!parsed) {
|
|
6954
|
+
throw new Error("Tileflow session grant response was invalid.");
|
|
6955
|
+
}
|
|
6956
|
+
const expiresAt = parsed.grant ? Date.parse(parsed.expiresAt) : input.at + 6e4;
|
|
6957
|
+
if (response.status === 201 && (!parsed.grant || !parsed.expiresAt) || parsed.grant && (!Number.isFinite(expiresAt) || expiresAt <= input.at)) {
|
|
6958
|
+
throw new Error("Tileflow session grant response was invalid.");
|
|
6959
|
+
}
|
|
6960
|
+
return {
|
|
6961
|
+
expiresAt,
|
|
6962
|
+
resourceOrigins: new Set(
|
|
6963
|
+
[originOf(apiUrl), ...(parsed.resourceOrigins ?? []).map(originOf)].filter(
|
|
6964
|
+
(value) => Boolean(value)
|
|
6965
|
+
)
|
|
6966
|
+
),
|
|
6967
|
+
token: parsed.grant ?? null
|
|
6968
|
+
};
|
|
6969
|
+
});
|
|
6970
|
+
}
|
|
6971
|
+
function validateSessionGrantTimeout(value) {
|
|
6972
|
+
const resolved = value ?? 1e4;
|
|
6973
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > 12e4) {
|
|
6974
|
+
throw new Error("Tileflow session grantTimeoutMs must be an integer from 1 to 120000");
|
|
6975
|
+
}
|
|
6976
|
+
return resolved;
|
|
6977
|
+
}
|
|
6978
|
+
async function runWithinSessionGrantTimeout(timeoutMs, operation) {
|
|
6979
|
+
const controller = new AbortController();
|
|
6980
|
+
const timeoutError = new Error(`Tileflow session grant timed out after ${timeoutMs}ms`);
|
|
6981
|
+
const timeout = setTimeout(() => controller.abort(timeoutError), timeoutMs);
|
|
6982
|
+
try {
|
|
6983
|
+
return await raceSessionGrantWithAbort(operation(controller.signal), controller.signal);
|
|
6984
|
+
} finally {
|
|
6985
|
+
clearTimeout(timeout);
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
function raceSessionGrantWithAbort(operation, signal) {
|
|
6989
|
+
if (signal.aborted) return Promise.reject(sessionGrantAbortReason(signal));
|
|
6990
|
+
return new Promise((resolve, reject) => {
|
|
6991
|
+
const onAbort = () => {
|
|
6992
|
+
cleanup();
|
|
6993
|
+
reject(sessionGrantAbortReason(signal));
|
|
6994
|
+
};
|
|
6995
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
6996
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6997
|
+
operation.then(
|
|
6998
|
+
(value) => {
|
|
6999
|
+
cleanup();
|
|
7000
|
+
resolve(value);
|
|
7001
|
+
},
|
|
7002
|
+
(error) => {
|
|
7003
|
+
cleanup();
|
|
7004
|
+
reject(error);
|
|
7005
|
+
}
|
|
7006
|
+
);
|
|
7007
|
+
});
|
|
7008
|
+
}
|
|
7009
|
+
function throwIfSessionGrantAborted(signal) {
|
|
7010
|
+
if (signal.aborted) throw sessionGrantAbortReason(signal);
|
|
7011
|
+
}
|
|
7012
|
+
function sessionGrantAbortReason(signal) {
|
|
7013
|
+
if (signal.reason instanceof Error) return signal.reason;
|
|
7014
|
+
const error = new Error("Tileflow session grant was aborted");
|
|
7015
|
+
error.name = "AbortError";
|
|
7016
|
+
return error;
|
|
7017
|
+
}
|
|
7018
|
+
function parseSessionGrantResponse(value, expectedSessionId) {
|
|
7019
|
+
if (!isRecord6(value) || value.ok !== true || value.sessionId !== expectedSessionId) return null;
|
|
7020
|
+
if (value.usageMode !== void 0 && value.usageMode !== "session") return null;
|
|
7021
|
+
if (value.billingEnabled !== void 0 && value.billingEnabled !== false) return null;
|
|
7022
|
+
if (value.disposition !== void 0 && value.disposition !== "ordinary" && value.disposition !== "unbilled_fail_open") {
|
|
7023
|
+
return null;
|
|
7024
|
+
}
|
|
7025
|
+
if (value.meterMode !== void 0 && value.meterMode !== "disabled" && value.meterMode !== "shadow" && value.meterMode !== "enforced") {
|
|
7026
|
+
return null;
|
|
7027
|
+
}
|
|
7028
|
+
if (value.grant !== void 0 && (typeof value.grant !== "string" || value.grant.length === 0 || value.grant.length > 8192)) {
|
|
7029
|
+
return null;
|
|
7030
|
+
}
|
|
7031
|
+
if (value.expiresAt !== void 0 && typeof value.expiresAt !== "string") return null;
|
|
7032
|
+
if (value.grant !== void 0 && value.expiresAt === void 0) return null;
|
|
7033
|
+
if (value.resourceOrigins !== void 0 && (!Array.isArray(value.resourceOrigins) || value.resourceOrigins.length > 32 || value.resourceOrigins.some((origin) => !originOf(origin)))) {
|
|
7034
|
+
return null;
|
|
7035
|
+
}
|
|
7036
|
+
return value;
|
|
7037
|
+
}
|
|
7038
|
+
function isRecord6(value) {
|
|
7039
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
7040
|
+
}
|
|
7041
|
+
var TileflowSessionRestartError = class extends Error {
|
|
7042
|
+
constructor() {
|
|
7043
|
+
super("Tileflow session must restart");
|
|
7044
|
+
this.name = "TileflowSessionRestartError";
|
|
7045
|
+
}
|
|
7046
|
+
};
|
|
7047
|
+
function isEligibleResource(value, mapId, apiOrigin, grantedOrigins) {
|
|
7048
|
+
try {
|
|
7049
|
+
const url = new URL(value);
|
|
7050
|
+
if (url.username || url.password) return false;
|
|
7051
|
+
const originAllowed = url.origin === apiOrigin || grantedOrigins?.has(url.origin) || apiOrigin === "https://api.tileflow.dev" && url.origin === "https://cdn.tileflow.dev";
|
|
7052
|
+
if (!originAllowed) return false;
|
|
7053
|
+
return url.pathname === `/maps/${encodeURIComponent(mapId)}/style.json` || url.pathname.startsWith("/tiles/") || url.pathname.startsWith("/v1/tiles/") || url.pathname.startsWith("/fonts/") || url.pathname.startsWith("/sprites/");
|
|
7054
|
+
} catch {
|
|
7055
|
+
return false;
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
function originOf(value) {
|
|
7059
|
+
if (typeof value !== "string") return null;
|
|
7060
|
+
try {
|
|
7061
|
+
const url = new URL(value);
|
|
7062
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) return null;
|
|
7063
|
+
return url.origin;
|
|
7064
|
+
} catch {
|
|
7065
|
+
return null;
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
6824
7068
|
function resolveTileflowAnalyticsRequestUrl(url, analytics, sessionId) {
|
|
6825
7069
|
if (!analytics || analytics.enabled === false) {
|
|
6826
7070
|
return void 0;
|
|
@@ -6843,7 +7087,7 @@ function startTileflowSession(analytics, input) {
|
|
|
6843
7087
|
if (!analytics || analytics.enabled === false || !input.mapId || typeof navigator === "undefined") {
|
|
6844
7088
|
return;
|
|
6845
7089
|
}
|
|
6846
|
-
const endpoint = analytics.endpoint ?? `${getTileflowAnalyticsApiUrl(analytics)}/v1/sessions/
|
|
7090
|
+
const endpoint = analytics.endpoint ?? `${getTileflowAnalyticsApiUrl(analytics)}/v1/sessions/analytics`;
|
|
6847
7091
|
const payload = JSON.stringify({
|
|
6848
7092
|
mapId: input.mapId,
|
|
6849
7093
|
metadata: analytics.metadata,
|
|
@@ -6866,7 +7110,7 @@ function startTileflowSession(analytics, input) {
|
|
|
6866
7110
|
},
|
|
6867
7111
|
keepalive: true,
|
|
6868
7112
|
method: "POST"
|
|
6869
|
-
});
|
|
7113
|
+
}).catch(() => void 0);
|
|
6870
7114
|
}
|
|
6871
7115
|
function getTileflowAnalyticsApiUrl(analytics) {
|
|
6872
7116
|
if (analytics.apiUrl) {
|
|
@@ -6883,9 +7127,6 @@ function getTileflowAnalyticsApiUrl(analytics) {
|
|
|
6883
7127
|
return "https://api.tileflow.dev";
|
|
6884
7128
|
}
|
|
6885
7129
|
function mergeTileflowAnalytics(userAnalytics, runtimeAnalytics) {
|
|
6886
|
-
if (userAnalytics?.enabled === false) {
|
|
6887
|
-
return userAnalytics;
|
|
6888
|
-
}
|
|
6889
7130
|
if (!userAnalytics && !runtimeAnalytics?.mapId) {
|
|
6890
7131
|
return void 0;
|
|
6891
7132
|
}
|
|
@@ -6994,7 +7235,7 @@ function normalizeTileflowStaticImageSize(input) {
|
|
|
6994
7235
|
function inferTileflowAnalyticsFromStyleUrl(styleUrl) {
|
|
6995
7236
|
try {
|
|
6996
7237
|
const url = new URL(styleUrl, getTileflowLocationOrigin());
|
|
6997
|
-
const match = url.pathname.match(
|
|
7238
|
+
const match = url.pathname.match(/^\/maps\/([^/]+)\/style\.json$/);
|
|
6998
7239
|
if (!match?.[1]) {
|
|
6999
7240
|
return void 0;
|
|
7000
7241
|
}
|
|
@@ -7037,6 +7278,7 @@ export {
|
|
|
7037
7278
|
createManifest,
|
|
7038
7279
|
createStyle,
|
|
7039
7280
|
createStyleFromProject,
|
|
7281
|
+
createTileflowSessionController,
|
|
7040
7282
|
createTileflowSessionId,
|
|
7041
7283
|
defaultTileflowManifestUrl,
|
|
7042
7284
|
defaultTileflowStyleBaseUrl,
|