# TypeScript Frontend Integration Guide This document explains how frontend developers should integrate with the backend through the reusable TypeScript client at `libs/geo-api-client`. > **See also:** [Frontend Development](frontend-development.md) — demo app (`web/`), local dev, build steps. Primary backend URL for integration: - `https://momswap.produktor.duckdns.org/` Deployment: API is proxied via reverse proxy from `https://momswap.produktor.duckdns.org` to backend at `172.17.0.1:8122`. Docker Compose maps port 8122 for the reverse proxy. ## Goals - Keep cryptographic signing logic in one place. - Avoid duplicating API request code in frontend apps. - Use a consistent local key storage format across projects. ## Client package location - Source: `libs/geo-api-client/src` - Entry point: `libs/geo-api-client/src/index.ts` - Build output (browser ESM): `libs/geo-api-client/dist/index.js` ## Build and test the client ```bash cd libs/geo-api-client bun install bun test # unit + integration tests (docs flow) bun run build ``` Integration tests in `test/integration.test.ts` cover the recommended flow: register, login, create collection, create point feature, list features. ## Public API (current) ### Class: `GeoApiClient` Source: [GeoApiClient.ts](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts) Constructor: - [`new GeoApiClient(baseUrl, storage, storageKey?)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L14) Key methods: **Key storage** - [`ensureKeysInStorage()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L20) — Ensure a keypair exists; if none found, generate and save. Use on app init. - [`getStoredKeys()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L29) — Get stored keypair without generating. Returns null if none. - [`derivePublicKey(privateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L33) — Derive public key from private key (Ed25519). Use when importing pk from backup/QR. - [`importKeys(keys)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L37) — Overwrite stored keypair (e.g. after import or restore from QR). - [`exportKeys()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L41) — Read stored keypair for export/backup. - [`setAccessToken(token)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L45) — Set bearer token for authenticated requests. Call after login. **Auth** - [`getServicePublicKey()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L69) — Fetch API service public key (for register-by-signature). - [`createChallenge(publicKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L73) — Request login challenge; returns nonce and messageToSign. - [`loginWithSignature(publicKey, privateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L86) — Login via challenge-response. Returns bearer token; stores it internally. - [`registerBySigningServiceKey(publicKey, privateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L76) — Register without invitation by signing the API service key. 409 if already registered. - [`createInvitation(payload, inviterPrivateKey)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L101) — Create invitation for new users. Inviter signs the payload. - [`registerWithInvitation(...)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L114) — Register using an invitation. Proves key ownership and redeems invite. **Collections** - [`listCollections()`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L133) — List collections for the authenticated user. - [`createCollection(name)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L137) — Create a new collection. Returns id and name. - [`updateCollection(collectionId, name)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L140) — Rename a collection. - [`deleteCollection(collectionId)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L148) — Delete a collection and its features. **Features** - [`listFeatures(collectionId)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L153) — List GeoJSON features in a collection. - [`createPointFeature(collectionId, lon, lat, properties)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L156) — Add a Point. lon ∈ [-180,180], lat ∈ [-90,90]. Returns feature id. - [`deleteFeature(featureId)`](https://git.produktor.io/momswap/backend/src/branch/main/libs/geo-api-client/src/GeoApiClient.ts#L172) — Delete a feature. ## Recommended integration flow 1. Create one `GeoApiClient` instance per backend base URL. 2. Call `ensureKeysInStorage()` when app initializes. 3. If not yet registered: call `registerBySigningServiceKey(publicKey, privateKey)` (signs the API service key and publishes your public key). 4. Use `loginWithSignature()` to obtain and set a bearer token. 5. Call collection/feature methods after authentication. 6. Use `importKeys`/`exportKeys` in profile settings UX. ## Registration by signing service key When `SERVICE_PUBLIC_KEY` (or `ADMIN_PUBLIC_KEY`) is set, users can register without an invitation: 1. `GET /v1/service-key` — fetch the server public key (clients use this for registration and further signed communication). 2. Sign that key with your private key. 3. `POST /v1/auth/register-by-signature` with `{ publicKey, signature }`. Server keys are generated with `./bin/gen-server-keys.sh` and stored in `etc/`. ## Example (TypeScript app) ```ts import { GeoApiClient } from "../libs/geo-api-client/dist/index.js"; const storage = window.localStorage; const storageLike = { getItem: (key: string) => storage.getItem(key), setItem: (key: string, value: string) => storage.setItem(key, value), removeItem: (key: string) => storage.removeItem(key), }; const client = new GeoApiClient("https://momswap.produktor.duckdns.org", storageLike); const keys = await client.ensureKeysInStorage(); // Register (ignored if already registered); then login try { await client.registerBySigningServiceKey(keys.publicKey, keys.privateKey); } catch (_) { // Already registered or registration disabled } await client.loginWithSignature(keys.publicKey, keys.privateKey); const created = await client.createCollection("My Places"); await client.createPointFeature(created.id, -16.6291, 28.4636, { name: "Santa Cruz" }); const features = await client.listFeatures(created.id); console.log(features); ``` ## Security notes - Private keys are currently stored in browser storage via the selected storage adapter. - If your frontend has stronger security requirements, wrap the storage adapter with your own encryption/decryption layer before calling `setItem`/`getItem`. - Never send private keys to the backend. ## No-build frontend compatibility For no-bundler apps, import the built ESM file: ```html ``` The backend itself serves static UI at `/web/`, but this library can be consumed by any frontend runtime that supports `fetch`, `TextEncoder`, and ES modules. For local development you can switch the client base URL to `http://localhost:8122`.