fix(map): accept canonical gateway endpoints

This commit is contained in:
Codex
2026-07-29 17:24:46 +03:00
parent c293d748f5
commit bc1c0dd6e6
4 changed files with 132 additions and 44 deletions
+1 -1
View File
@@ -170,7 +170,7 @@ Page/Visual/Application previews не содержат shipped domain fixtures.
Server-side runtime contract:
- `GET /api/map/runtime-config` сообщает версию renderer и readiness возможностей, но не возвращает master token;
- `GET /api/map/ion/assets/:assetId/endpoint` разрешает только allowlisted assets и возвращает public provider URL без credential; private Gateway добавляет asset credential только к своему upstream request;
- `GET /api/map/ion/assets/:assetId/endpoint` разрешает только allowlisted assets, возвращает canonical string `assetId` и public provider URL без credential; private Gateway добавляет asset credential только к своему upstream request;
- `CESIUM_ION_TOKEN` передаётся только private Platform Map Gateway через deployment environment/secret, не в Foundry;
- `CESIUM_ION_ASSET_ALLOWLIST` ограничивает terrain/buildings/Gaussian assets;
- production endpoint переезжает в `platform/services/map-gateway`.
@@ -7,6 +7,10 @@ import type {
MapProviderId,
MapRuntimeState,
} from "./contracts.js";
import {
parseProviderEndpoint,
type ProviderEndpoint,
} from "./providerEndpoint.js";
import {
MapRuntimeError,
applyCacheIntent,
@@ -18,19 +22,6 @@ import {
} from "./runtime.js";
type CesiumNamespace = typeof CesiumModule;
type ProviderEndpoint = {
assetId: number;
type: "IMAGERY" | "TERRAIN" | "3DTILES";
externalType?: "BING";
credentialMode: "gateway";
url?: string;
options?: {
url?: string;
mapStyle?: string;
};
attributions?: readonly unknown[];
};
export function CesiumMapRenderer({
runtimeConfigUrl,
camera,
@@ -239,29 +230,7 @@ async function fetchProviderEndpoint(
signal,
);
const expectedAssetId = configuration.assets[providerId];
const expectedType =
providerId === "imagery"
? "IMAGERY"
: providerId === "terrain"
? "TERRAIN"
: "3DTILES";
if (
document.assetId !== expectedAssetId ||
document.type !== expectedType ||
document.credentialMode !== "gateway"
) {
throw new MapRuntimeError("map_provider_contract_mismatch");
}
if (
(providerId === "imagery" &&
(document.externalType !== "BING" ||
!isEndpointOptions(document.options) ||
typeof document.options.url !== "string")) ||
(providerId !== "imagery" && typeof document.url !== "string")
) {
throw new MapRuntimeError("map_provider_contract_mismatch");
}
return document as ProviderEndpoint;
return parseProviderEndpoint(document, expectedAssetId, providerId);
}
async function attachProvider(
@@ -285,8 +254,12 @@ async function attachProvider(
});
if (providerId === "imagery") {
const provider = await Cesium.BingMapsImageryProvider.fromUrl(resource, {
key: "gateway-proxy",
// Cesium requires a key-shaped value to build Bing metadata and tile
// URLs. This is a public marker, not a credential: Map Gateway strips
// it and injects the server-owned provider key upstream.
key: "nodedc-gateway",
mapStyle: bingMapStyle(Cesium, endpoint.options?.mapStyle),
tileProtocol: "https",
});
const layer = viewer.imageryLayers.addImageryProvider(provider);
if (settings.monochrome_enabled) {
@@ -386,12 +359,6 @@ function safeErrorCode(error: unknown): string {
return error instanceof MapRuntimeError ? error.code : "map_provider_unavailable";
}
function isEndpointOptions(
value: unknown,
): value is NonNullable<ProviderEndpoint["options"]> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function tokenColor(
Cesium: CesiumNamespace,
container: HTMLElement,
@@ -0,0 +1,52 @@
import type { MapProviderId } from "./contracts.js";
import { MapRuntimeError } from "./runtime.js";
export type ProviderEndpoint = {
assetId: string;
type: "IMAGERY" | "TERRAIN" | "3DTILES";
externalType?: "BING";
credentialMode: "gateway";
url?: string;
options?: {
url?: string;
mapStyle?: string;
};
attributions: readonly unknown[];
};
export function parseProviderEndpoint(
document: Record<string, unknown>,
expectedAssetId: number,
providerId: MapProviderId,
): ProviderEndpoint {
const expectedType =
providerId === "imagery"
? "IMAGERY"
: providerId === "terrain"
? "TERRAIN"
: "3DTILES";
if (
document.assetId !== String(expectedAssetId) ||
document.type !== expectedType ||
document.credentialMode !== "gateway" ||
!Array.isArray(document.attributions)
) {
throw new MapRuntimeError("map_provider_contract_mismatch");
}
if (
(providerId === "imagery" &&
(document.externalType !== "BING" ||
!isEndpointOptions(document.options) ||
typeof document.options.url !== "string")) ||
(providerId !== "imagery" && typeof document.url !== "string")
) {
throw new MapRuntimeError("map_provider_contract_mismatch");
}
return document as ProviderEndpoint;
}
function isEndpointOptions(
value: unknown,
): value is NonNullable<ProviderEndpoint["options"]> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
@@ -0,0 +1,69 @@
import assert from "node:assert/strict";
import test from "node:test";
import { parseProviderEndpoint } from "../dist/providerEndpoint.js";
const attributions = [{ html: "provider", collapsible: true }];
test("provider endpoint accepts the canonical string asset identifiers", () => {
const imagery = parseProviderEndpoint({
assetId: "2",
type: "IMAGERY",
externalType: "BING",
credentialMode: "gateway",
options: {
url: "https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial",
mapStyle: "Aerial",
},
attributions,
}, 2, "imagery");
const terrain = parseProviderEndpoint({
assetId: "1",
type: "TERRAIN",
credentialMode: "gateway",
url: "https://assets.ion.cesium.com/terrain/",
attributions,
}, 1, "terrain");
const buildings = parseProviderEndpoint({
assetId: "96188",
type: "3DTILES",
credentialMode: "gateway",
url: "https://assets.ion.cesium.com/buildings/tileset.json",
attributions,
}, 96188, "buildings");
assert.equal(imagery.assetId, "2");
assert.equal(terrain.assetId, "1");
assert.equal(buildings.assetId, "96188");
});
test("provider endpoint rejects numeric, mismatched and credential-bearing shapes", () => {
for (const document of [
{
assetId: 1,
type: "TERRAIN",
credentialMode: "gateway",
url: "https://assets.ion.cesium.com/terrain/",
attributions,
},
{
assetId: "2",
type: "TERRAIN",
credentialMode: "gateway",
url: "https://assets.ion.cesium.com/terrain/",
attributions,
},
{
assetId: "1",
type: "TERRAIN",
credentialMode: "browser",
url: "https://assets.ion.cesium.com/terrain/",
attributions,
},
]) {
assert.throws(
() => parseProviderEndpoint(document, 1, "terrain"),
/map_provider_contract_mismatch/,
);
}
});