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