From 96b5e8f40ff59971b99200528656abab988d9ffa Mon Sep 17 00:00:00 2001 From: Andriy Oblivantsev Date: Mon, 2 Mar 2026 21:32:21 +0000 Subject: [PATCH] Improve TypeScript integration doc with concrete 3D upload flow. This updates the example to compute SHA-256 from a selected GLB/GLTF file, create/link asset metadata, upload with signed URL, and use share links plus visibility toggling. Made-with: Cursor --- docs/typescript-frontend-integration.md | 47 +++++++++++++++++++------ 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/docs/typescript-frontend-integration.md b/docs/typescript-frontend-integration.md index d46f397..4965dc5 100644 --- a/docs/typescript-frontend-integration.md +++ b/docs/typescript-frontend-integration.md @@ -114,7 +114,7 @@ When `SERVICE_PUBLIC_KEY` (or `ADMIN_PUBLIC_KEY`) is set, users can register wit Server keys are generated with `./bin/gen-server-keys.sh` and stored in `etc/`. -## Example (TypeScript app) +## Example: place and upload a 3D object (`.glb`) ```ts import { GeoApiClient } from "../libs/geo-api-client/dist/index.js"; @@ -140,28 +140,53 @@ await client.loginWithSignature(keys.publicKey, keys.privateKey); const created = await client.createCollection("My Places"); const feature = await client.createPointFeature(created.id, -16.6291, 28.4636, { name: "Santa Cruz" }); -// 3D/image asset flow +// Assume this comes from: +const fileInput = document.getElementById("modelFile") as HTMLInputElement; +const file = fileInput.files?.[0]; +if (!file) throw new Error("Select a .glb/.gltf file first"); + +const toSha256Hex = async (f: File): Promise => { + const buffer = await f.arrayBuffer(); + const digest = await crypto.subtle.digest("SHA-256", buffer); + return Array.from(new Uint8Array(digest)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +}; + +const checksum = await toSha256Hex(file); +const ext = file.name.toLowerCase().endsWith(".gltf") ? "gltf" : "glb"; + +// Create metadata (or reuse existing by checksum+ext) and link to feature const asset = await client.createOrLinkAsset({ featureId: feature.id, - checksum: "sha256hex...", - ext: "glb", + checksum, + ext, kind: "3d", - mimeType: "model/gltf-binary", - sizeBytes: 1024, - name: "Palm Tree", - description: "Low-poly model", + mimeType: file.type || (ext === "gltf" ? "model/gltf+json" : "model/gltf-binary"), + sizeBytes: file.size, + name: "Palm Tree Model", + description: "3D object placed on map", isPublic: true, }); -const signed = await client.getAssetSignedUploadUrl(asset.asset.id, "model/gltf-binary"); -// fetch(signed.url, { method: signed.method, body: file }) +// Upload binary to object storage through signed URL +const signed = await client.getAssetSignedUploadUrl(asset.asset.id, asset.asset.mimeType); +await fetch(signed.url, { + method: signed.method, + headers: asset.asset.mimeType ? { "Content-Type": asset.asset.mimeType } : undefined, + body: file, +}); + +// Read shareable relative link from feature payload const features = await client.listFeatures(created.id); const firstAsset = features.features[0]?.properties?.assets?.[0]; if (firstAsset) { const shareUrl = client.resolveRelativeLink(firstAsset.link); console.log("Share URL:", shareUrl); } -console.log(features); + +// Optional: owner can disable public access later +await client.setAssetVisibility(asset.asset.id, false); ``` ## Security notes