Implement geo backend, TS client, frontend, and CI tests.
Add a Go HTTP API with Ed25519 auth and invitation onboarding, user-scoped GeoJSON Point management, a Bun-tested @noble/ed25519 TypeScript client, static Vue/Vuetify frontend integration, and a Gitea CI workflow running both Go and Bun test suites. Made-with: Cursor
This commit is contained in:
Vendored
+617
@@ -0,0 +1,617 @@
|
||||
// node_modules/@noble/ed25519/index.js
|
||||
/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
|
||||
var ed25519_CURVE = {
|
||||
p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
|
||||
n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
|
||||
h: 8n,
|
||||
a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
|
||||
d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
|
||||
Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
|
||||
Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n
|
||||
};
|
||||
var { p: P, n: N, Gx, Gy, a: _a, d: _d, h } = ed25519_CURVE;
|
||||
var L = 32;
|
||||
var L2 = 64;
|
||||
var captureTrace = (...args) => {
|
||||
if ("captureStackTrace" in Error && typeof Error.captureStackTrace === "function") {
|
||||
Error.captureStackTrace(...args);
|
||||
}
|
||||
};
|
||||
var err = (message = "") => {
|
||||
const e = new Error(message);
|
||||
captureTrace(e, err);
|
||||
throw e;
|
||||
};
|
||||
var isBig = (n) => typeof n === "bigint";
|
||||
var isStr = (s) => typeof s === "string";
|
||||
var isBytes = (a) => a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
||||
var abytes = (value, length, title = "") => {
|
||||
const bytes = isBytes(value);
|
||||
const len = value?.length;
|
||||
const needsLen = length !== undefined;
|
||||
if (!bytes || needsLen && len !== length) {
|
||||
const prefix = title && `"${title}" `;
|
||||
const ofLen = needsLen ? ` of length ${length}` : "";
|
||||
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
||||
err(prefix + "expected Uint8Array" + ofLen + ", got " + got);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
var u8n = (len) => new Uint8Array(len);
|
||||
var u8fr = (buf) => Uint8Array.from(buf);
|
||||
var padh = (n, pad) => n.toString(16).padStart(pad, "0");
|
||||
var bytesToHex = (b) => Array.from(abytes(b)).map((e) => padh(e, 2)).join("");
|
||||
var C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
||||
var _ch = (ch) => {
|
||||
if (ch >= C._0 && ch <= C._9)
|
||||
return ch - C._0;
|
||||
if (ch >= C.A && ch <= C.F)
|
||||
return ch - (C.A - 10);
|
||||
if (ch >= C.a && ch <= C.f)
|
||||
return ch - (C.a - 10);
|
||||
return;
|
||||
};
|
||||
var hexToBytes = (hex) => {
|
||||
const e = "hex invalid";
|
||||
if (!isStr(hex))
|
||||
return err(e);
|
||||
const hl = hex.length;
|
||||
const al = hl / 2;
|
||||
if (hl % 2)
|
||||
return err(e);
|
||||
const array = u8n(al);
|
||||
for (let ai = 0, hi = 0;ai < al; ai++, hi += 2) {
|
||||
const n1 = _ch(hex.charCodeAt(hi));
|
||||
const n2 = _ch(hex.charCodeAt(hi + 1));
|
||||
if (n1 === undefined || n2 === undefined)
|
||||
return err(e);
|
||||
array[ai] = n1 * 16 + n2;
|
||||
}
|
||||
return array;
|
||||
};
|
||||
var cr = () => globalThis?.crypto;
|
||||
var subtle = () => cr()?.subtle ?? err("crypto.subtle must be defined, consider polyfill");
|
||||
var concatBytes = (...arrs) => {
|
||||
const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
|
||||
let pad = 0;
|
||||
arrs.forEach((a) => {
|
||||
r.set(a, pad);
|
||||
pad += a.length;
|
||||
});
|
||||
return r;
|
||||
};
|
||||
var big = BigInt;
|
||||
var assertRange = (n, min, max, msg = "bad number: out of range") => isBig(n) && min <= n && n < max ? n : err(msg);
|
||||
var M = (a, b = P) => {
|
||||
const r = a % b;
|
||||
return r >= 0n ? r : b + r;
|
||||
};
|
||||
var modN = (a) => M(a, N);
|
||||
var invert = (num, md) => {
|
||||
if (num === 0n || md <= 0n)
|
||||
err("no inverse n=" + num + " mod=" + md);
|
||||
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
|
||||
while (a !== 0n) {
|
||||
const q = b / a, r = b % a;
|
||||
const m = x - u * q, n = y - v * q;
|
||||
b = a, a = r, x = u, y = v, u = m, v = n;
|
||||
}
|
||||
return b === 1n ? M(x, md) : err("no inverse");
|
||||
};
|
||||
var apoint = (p) => p instanceof Point ? p : err("Point expected");
|
||||
var B256 = 2n ** 256n;
|
||||
|
||||
class Point {
|
||||
static BASE;
|
||||
static ZERO;
|
||||
X;
|
||||
Y;
|
||||
Z;
|
||||
T;
|
||||
constructor(X, Y, Z, T) {
|
||||
const max = B256;
|
||||
this.X = assertRange(X, 0n, max);
|
||||
this.Y = assertRange(Y, 0n, max);
|
||||
this.Z = assertRange(Z, 1n, max);
|
||||
this.T = assertRange(T, 0n, max);
|
||||
Object.freeze(this);
|
||||
}
|
||||
static CURVE() {
|
||||
return ed25519_CURVE;
|
||||
}
|
||||
static fromAffine(p) {
|
||||
return new Point(p.x, p.y, 1n, M(p.x * p.y));
|
||||
}
|
||||
static fromBytes(hex, zip215 = false) {
|
||||
const d = _d;
|
||||
const normed = u8fr(abytes(hex, L));
|
||||
const lastByte = hex[31];
|
||||
normed[31] = lastByte & ~128;
|
||||
const y = bytesToNumLE(normed);
|
||||
const max = zip215 ? B256 : P;
|
||||
assertRange(y, 0n, max);
|
||||
const y2 = M(y * y);
|
||||
const u = M(y2 - 1n);
|
||||
const v = M(d * y2 + 1n);
|
||||
let { isValid, value: x } = uvRatio(u, v);
|
||||
if (!isValid)
|
||||
err("bad point: y not sqrt");
|
||||
const isXOdd = (x & 1n) === 1n;
|
||||
const isLastByteOdd = (lastByte & 128) !== 0;
|
||||
if (!zip215 && x === 0n && isLastByteOdd)
|
||||
err("bad point: x==0, isLastByteOdd");
|
||||
if (isLastByteOdd !== isXOdd)
|
||||
x = M(-x);
|
||||
return new Point(x, y, 1n, M(x * y));
|
||||
}
|
||||
static fromHex(hex, zip215) {
|
||||
return Point.fromBytes(hexToBytes(hex), zip215);
|
||||
}
|
||||
get x() {
|
||||
return this.toAffine().x;
|
||||
}
|
||||
get y() {
|
||||
return this.toAffine().y;
|
||||
}
|
||||
assertValidity() {
|
||||
const a = _a;
|
||||
const d = _d;
|
||||
const p = this;
|
||||
if (p.is0())
|
||||
return err("bad point: ZERO");
|
||||
const { X, Y, Z, T } = p;
|
||||
const X2 = M(X * X);
|
||||
const Y2 = M(Y * Y);
|
||||
const Z2 = M(Z * Z);
|
||||
const Z4 = M(Z2 * Z2);
|
||||
const aX2 = M(X2 * a);
|
||||
const left = M(Z2 * M(aX2 + Y2));
|
||||
const right = M(Z4 + M(d * M(X2 * Y2)));
|
||||
if (left !== right)
|
||||
return err("bad point: equation left != right (1)");
|
||||
const XY = M(X * Y);
|
||||
const ZT = M(Z * T);
|
||||
if (XY !== ZT)
|
||||
return err("bad point: equation left != right (2)");
|
||||
return this;
|
||||
}
|
||||
equals(other) {
|
||||
const { X: X1, Y: Y1, Z: Z1 } = this;
|
||||
const { X: X2, Y: Y2, Z: Z2 } = apoint(other);
|
||||
const X1Z2 = M(X1 * Z2);
|
||||
const X2Z1 = M(X2 * Z1);
|
||||
const Y1Z2 = M(Y1 * Z2);
|
||||
const Y2Z1 = M(Y2 * Z1);
|
||||
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
||||
}
|
||||
is0() {
|
||||
return this.equals(I);
|
||||
}
|
||||
negate() {
|
||||
return new Point(M(-this.X), this.Y, this.Z, M(-this.T));
|
||||
}
|
||||
double() {
|
||||
const { X: X1, Y: Y1, Z: Z1 } = this;
|
||||
const a = _a;
|
||||
const A = M(X1 * X1);
|
||||
const B = M(Y1 * Y1);
|
||||
const C2 = M(2n * M(Z1 * Z1));
|
||||
const D = M(a * A);
|
||||
const x1y1 = X1 + Y1;
|
||||
const E = M(M(x1y1 * x1y1) - A - B);
|
||||
const G = D + B;
|
||||
const F = G - C2;
|
||||
const H = D - B;
|
||||
const X3 = M(E * F);
|
||||
const Y3 = M(G * H);
|
||||
const T3 = M(E * H);
|
||||
const Z3 = M(F * G);
|
||||
return new Point(X3, Y3, Z3, T3);
|
||||
}
|
||||
add(other) {
|
||||
const { X: X1, Y: Y1, Z: Z1, T: T1 } = this;
|
||||
const { X: X2, Y: Y2, Z: Z2, T: T2 } = apoint(other);
|
||||
const a = _a;
|
||||
const d = _d;
|
||||
const A = M(X1 * X2);
|
||||
const B = M(Y1 * Y2);
|
||||
const C2 = M(T1 * d * T2);
|
||||
const D = M(Z1 * Z2);
|
||||
const E = M((X1 + Y1) * (X2 + Y2) - A - B);
|
||||
const F = M(D - C2);
|
||||
const G = M(D + C2);
|
||||
const H = M(B - a * A);
|
||||
const X3 = M(E * F);
|
||||
const Y3 = M(G * H);
|
||||
const T3 = M(E * H);
|
||||
const Z3 = M(F * G);
|
||||
return new Point(X3, Y3, Z3, T3);
|
||||
}
|
||||
subtract(other) {
|
||||
return this.add(apoint(other).negate());
|
||||
}
|
||||
multiply(n, safe = true) {
|
||||
if (!safe && (n === 0n || this.is0()))
|
||||
return I;
|
||||
assertRange(n, 1n, N);
|
||||
if (n === 1n)
|
||||
return this;
|
||||
if (this.equals(G))
|
||||
return wNAF(n).p;
|
||||
let p = I;
|
||||
let f = G;
|
||||
for (let d = this;n > 0n; d = d.double(), n >>= 1n) {
|
||||
if (n & 1n)
|
||||
p = p.add(d);
|
||||
else if (safe)
|
||||
f = f.add(d);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
multiplyUnsafe(scalar) {
|
||||
return this.multiply(scalar, false);
|
||||
}
|
||||
toAffine() {
|
||||
const { X, Y, Z } = this;
|
||||
if (this.equals(I))
|
||||
return { x: 0n, y: 1n };
|
||||
const iz = invert(Z, P);
|
||||
if (M(Z * iz) !== 1n)
|
||||
err("invalid inverse");
|
||||
const x = M(X * iz);
|
||||
const y = M(Y * iz);
|
||||
return { x, y };
|
||||
}
|
||||
toBytes() {
|
||||
const { x, y } = this.assertValidity().toAffine();
|
||||
const b = numTo32bLE(y);
|
||||
b[31] |= x & 1n ? 128 : 0;
|
||||
return b;
|
||||
}
|
||||
toHex() {
|
||||
return bytesToHex(this.toBytes());
|
||||
}
|
||||
clearCofactor() {
|
||||
return this.multiply(big(h), false);
|
||||
}
|
||||
isSmallOrder() {
|
||||
return this.clearCofactor().is0();
|
||||
}
|
||||
isTorsionFree() {
|
||||
let p = this.multiply(N / 2n, false).double();
|
||||
if (N % 2n)
|
||||
p = p.add(this);
|
||||
return p.is0();
|
||||
}
|
||||
}
|
||||
var G = new Point(Gx, Gy, 1n, M(Gx * Gy));
|
||||
var I = new Point(0n, 1n, 1n, 0n);
|
||||
Point.BASE = G;
|
||||
Point.ZERO = I;
|
||||
var numTo32bLE = (num) => hexToBytes(padh(assertRange(num, 0n, B256), L2)).reverse();
|
||||
var bytesToNumLE = (b) => big("0x" + bytesToHex(u8fr(abytes(b)).reverse()));
|
||||
var pow2 = (x, power) => {
|
||||
let r = x;
|
||||
while (power-- > 0n) {
|
||||
r *= r;
|
||||
r %= P;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
var pow_2_252_3 = (x) => {
|
||||
const x2 = x * x % P;
|
||||
const b2 = x2 * x % P;
|
||||
const b4 = pow2(b2, 2n) * b2 % P;
|
||||
const b5 = pow2(b4, 1n) * x % P;
|
||||
const b10 = pow2(b5, 5n) * b5 % P;
|
||||
const b20 = pow2(b10, 10n) * b10 % P;
|
||||
const b40 = pow2(b20, 20n) * b20 % P;
|
||||
const b80 = pow2(b40, 40n) * b40 % P;
|
||||
const b160 = pow2(b80, 80n) * b80 % P;
|
||||
const b240 = pow2(b160, 80n) * b80 % P;
|
||||
const b250 = pow2(b240, 10n) * b10 % P;
|
||||
const pow_p_5_8 = pow2(b250, 2n) * x % P;
|
||||
return { pow_p_5_8, b2 };
|
||||
};
|
||||
var RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
|
||||
var uvRatio = (u, v) => {
|
||||
const v3 = M(v * v * v);
|
||||
const v7 = M(v3 * v3 * v);
|
||||
const pow = pow_2_252_3(u * v7).pow_p_5_8;
|
||||
let x = M(u * v3 * pow);
|
||||
const vx2 = M(v * x * x);
|
||||
const root1 = x;
|
||||
const root2 = M(x * RM1);
|
||||
const useRoot1 = vx2 === u;
|
||||
const useRoot2 = vx2 === M(-u);
|
||||
const noRoot = vx2 === M(-u * RM1);
|
||||
if (useRoot1)
|
||||
x = root1;
|
||||
if (useRoot2 || noRoot)
|
||||
x = root2;
|
||||
if ((M(x) & 1n) === 1n)
|
||||
x = M(-x);
|
||||
return { isValid: useRoot1 || useRoot2, value: x };
|
||||
};
|
||||
var modL_LE = (hash) => modN(bytesToNumLE(hash));
|
||||
var sha512a = (...m) => hashes.sha512Async(concatBytes(...m));
|
||||
var hash2extK = (hashed) => {
|
||||
const head = hashed.slice(0, L);
|
||||
head[0] &= 248;
|
||||
head[31] &= 127;
|
||||
head[31] |= 64;
|
||||
const prefix = hashed.slice(L, L2);
|
||||
const scalar = modL_LE(head);
|
||||
const point = G.multiply(scalar);
|
||||
const pointBytes = point.toBytes();
|
||||
return { head, prefix, scalar, point, pointBytes };
|
||||
};
|
||||
var getExtendedPublicKeyAsync = (secretKey) => sha512a(abytes(secretKey, L)).then(hash2extK);
|
||||
var getPublicKeyAsync = (secretKey) => getExtendedPublicKeyAsync(secretKey).then((p) => p.pointBytes);
|
||||
var hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
|
||||
var _sign = (e, rBytes, msg) => {
|
||||
const { pointBytes: P2, scalar: s } = e;
|
||||
const r = modL_LE(rBytes);
|
||||
const R = G.multiply(r).toBytes();
|
||||
const hashable = concatBytes(R, P2, msg);
|
||||
const finish = (hashed) => {
|
||||
const S = modN(r + modL_LE(hashed) * s);
|
||||
return abytes(concatBytes(R, numTo32bLE(S)), L2);
|
||||
};
|
||||
return { hashable, finish };
|
||||
};
|
||||
var signAsync = async (message, secretKey) => {
|
||||
const m = abytes(message);
|
||||
const e = await getExtendedPublicKeyAsync(secretKey);
|
||||
const rBytes = await sha512a(e.prefix, m);
|
||||
return hashFinishA(_sign(e, rBytes, m));
|
||||
};
|
||||
var hashes = {
|
||||
sha512Async: async (message) => {
|
||||
const s = subtle();
|
||||
const m = concatBytes(message);
|
||||
return u8n(await s.digest("SHA-512", m.buffer));
|
||||
},
|
||||
sha512: undefined
|
||||
};
|
||||
var W = 8;
|
||||
var scalarBits = 256;
|
||||
var pwindows = Math.ceil(scalarBits / W) + 1;
|
||||
var pwindowSize = 2 ** (W - 1);
|
||||
var precompute = () => {
|
||||
const points = [];
|
||||
let p = G;
|
||||
let b = p;
|
||||
for (let w = 0;w < pwindows; w++) {
|
||||
b = p;
|
||||
points.push(b);
|
||||
for (let i = 1;i < pwindowSize; i++) {
|
||||
b = b.add(p);
|
||||
points.push(b);
|
||||
}
|
||||
p = b.double();
|
||||
}
|
||||
return points;
|
||||
};
|
||||
var Gpows = undefined;
|
||||
var ctneg = (cnd, p) => {
|
||||
const n = p.negate();
|
||||
return cnd ? n : p;
|
||||
};
|
||||
var wNAF = (n) => {
|
||||
const comp = Gpows || (Gpows = precompute());
|
||||
let p = I;
|
||||
let f = G;
|
||||
const pow_2_w = 2 ** W;
|
||||
const maxNum = pow_2_w;
|
||||
const mask = big(pow_2_w - 1);
|
||||
const shiftBy = big(W);
|
||||
for (let w = 0;w < pwindows; w++) {
|
||||
let wbits = Number(n & mask);
|
||||
n >>= shiftBy;
|
||||
if (wbits > pwindowSize) {
|
||||
wbits -= maxNum;
|
||||
n += 1n;
|
||||
}
|
||||
const off = w * pwindowSize;
|
||||
const offF = off;
|
||||
const offP = off + Math.abs(wbits) - 1;
|
||||
const isEven = w % 2 !== 0;
|
||||
const isNeg = wbits < 0;
|
||||
if (wbits === 0) {
|
||||
f = f.add(ctneg(isEven, comp[offF]));
|
||||
} else {
|
||||
p = p.add(ctneg(isNeg, comp[offP]));
|
||||
}
|
||||
}
|
||||
if (n !== 0n)
|
||||
err("invalid wnaf");
|
||||
return { p, f };
|
||||
};
|
||||
|
||||
// src/encoding.ts
|
||||
function bytesToBase64Url(bytes) {
|
||||
const bin = Array.from(bytes).map((b) => String.fromCharCode(b)).join("");
|
||||
if (typeof btoa !== "undefined") {
|
||||
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
||||
}
|
||||
const b64 = globalThis.Buffer.from(bytes).toString("base64");
|
||||
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
||||
}
|
||||
function base64UrlToBytes(input) {
|
||||
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const padLen = (4 - normalized.length % 4) % 4;
|
||||
const b64 = normalized + "=".repeat(padLen);
|
||||
if (typeof atob !== "undefined") {
|
||||
const bin = atob(b64);
|
||||
const out = new Uint8Array(bin.length);
|
||||
for (let i = 0;i < bin.length; i++) {
|
||||
out[i] = bin.charCodeAt(i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return new Uint8Array(globalThis.Buffer.from(b64, "base64"));
|
||||
}
|
||||
function textToBytes(value) {
|
||||
return new TextEncoder().encode(value);
|
||||
}
|
||||
|
||||
// src/keys.ts
|
||||
function randomPrivateKey() {
|
||||
const out = new Uint8Array(32);
|
||||
crypto.getRandomValues(out);
|
||||
return out;
|
||||
}
|
||||
async function generateKeyPair() {
|
||||
const privateKey = randomPrivateKey();
|
||||
const publicKey = await getPublicKeyAsync(privateKey);
|
||||
return {
|
||||
publicKey: bytesToBase64Url(publicKey),
|
||||
privateKey: bytesToBase64Url(privateKey)
|
||||
};
|
||||
}
|
||||
async function signMessage(privateKeyBase64, message) {
|
||||
const privateKey = base64UrlToBytes(privateKeyBase64);
|
||||
const signature = await signAsync(textToBytes(message), privateKey);
|
||||
return bytesToBase64Url(signature);
|
||||
}
|
||||
|
||||
// src/storage.ts
|
||||
var DEFAULT_KEYS_STORAGE_KEY = "geo_api_keys_v1";
|
||||
function saveKeys(storage, keys, storageKey = DEFAULT_KEYS_STORAGE_KEY) {
|
||||
storage.setItem(storageKey, JSON.stringify(keys));
|
||||
}
|
||||
function loadKeys(storage, storageKey = DEFAULT_KEYS_STORAGE_KEY) {
|
||||
const raw = storage.getItem(storageKey);
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed.publicKey || !parsed.privateKey) {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
function clearKeys(storage, storageKey = DEFAULT_KEYS_STORAGE_KEY) {
|
||||
storage.removeItem(storageKey);
|
||||
}
|
||||
|
||||
// src/GeoApiClient.ts
|
||||
class GeoApiClient {
|
||||
baseUrl;
|
||||
storage;
|
||||
storageKey;
|
||||
accessToken = null;
|
||||
constructor(baseUrl, storage, storageKey = DEFAULT_KEYS_STORAGE_KEY) {
|
||||
this.baseUrl = baseUrl.replace(/\/+$/g, "");
|
||||
this.storage = storage;
|
||||
this.storageKey = storageKey;
|
||||
}
|
||||
async ensureKeysInStorage() {
|
||||
const existing = loadKeys(this.storage, this.storageKey);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const generated = await generateKeyPair();
|
||||
saveKeys(this.storage, generated, this.storageKey);
|
||||
return generated;
|
||||
}
|
||||
getStoredKeys() {
|
||||
return loadKeys(this.storage, this.storageKey);
|
||||
}
|
||||
importKeys(keys) {
|
||||
saveKeys(this.storage, keys, this.storageKey);
|
||||
}
|
||||
exportKeys() {
|
||||
return loadKeys(this.storage, this.storageKey);
|
||||
}
|
||||
setAccessToken(token) {
|
||||
this.accessToken = token;
|
||||
}
|
||||
async request(path, init = {}) {
|
||||
const headers = new Headers(init.headers ?? {});
|
||||
headers.set("Content-Type", "application/json");
|
||||
if (this.accessToken) {
|
||||
headers.set("Authorization", `Bearer ${this.accessToken}`);
|
||||
}
|
||||
const body = init.body === undefined ? undefined : JSON.stringify(init.body);
|
||||
const res = await fetch(`${this.baseUrl}${path}`, { ...init, headers, body });
|
||||
if (!res.ok) {
|
||||
const maybeJson = await res.json().catch(() => ({}));
|
||||
const msg = maybeJson.error ?? `HTTP ${res.status}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
if (res.status === 204) {
|
||||
return;
|
||||
}
|
||||
return await res.json();
|
||||
}
|
||||
async createChallenge(publicKey) {
|
||||
return this.request("/v1/auth/challenge", { method: "POST", body: { publicKey } });
|
||||
}
|
||||
async loginWithSignature(publicKey, privateKey) {
|
||||
const challenge = await this.createChallenge(publicKey);
|
||||
const signature = await signMessage(privateKey, challenge.messageToSign);
|
||||
const response = await this.request("/v1/auth/login", {
|
||||
method: "POST",
|
||||
body: {
|
||||
publicKey,
|
||||
nonce: challenge.nonce,
|
||||
signature
|
||||
}
|
||||
});
|
||||
this.accessToken = response.accessToken;
|
||||
return response.accessToken;
|
||||
}
|
||||
async createInvitation(payload, inviterPrivateKey) {
|
||||
const payloadStr = JSON.stringify(payload);
|
||||
const payloadB64 = bytesToBase64Url(textToBytes(payloadStr));
|
||||
const inviteSignature = await signMessage(inviterPrivateKey, `invite:${payloadB64}`);
|
||||
await this.request("/v1/invitations", {
|
||||
method: "POST",
|
||||
body: {
|
||||
invitePayloadB64: payloadB64,
|
||||
inviteSignature
|
||||
}
|
||||
});
|
||||
}
|
||||
async registerWithInvitation(input) {
|
||||
const proofSignature = await signMessage(input.privateKey, `register:${input.publicKey}:${input.jti}`);
|
||||
await this.request("/v1/auth/register", {
|
||||
method: "POST",
|
||||
body: {
|
||||
publicKey: input.publicKey,
|
||||
invitePayloadB64: input.invitePayloadB64,
|
||||
inviteSignature: input.inviteSignature,
|
||||
proofSignature
|
||||
}
|
||||
});
|
||||
}
|
||||
async listCollections() {
|
||||
return this.request("/v1/collections", { method: "GET" });
|
||||
}
|
||||
async createCollection(name) {
|
||||
return this.request("/v1/collections", { method: "POST", body: { name } });
|
||||
}
|
||||
async listFeatures(collectionId) {
|
||||
return this.request(`/v1/collections/${collectionId}/features`, { method: "GET" });
|
||||
}
|
||||
async createPointFeature(collectionId, lon, lat, properties) {
|
||||
return this.request(`/v1/collections/${collectionId}/features`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
geometry: { type: "Point", coordinates: [lon, lat] },
|
||||
properties
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export {
|
||||
signMessage,
|
||||
saveKeys,
|
||||
loadKeys,
|
||||
generateKeyPair,
|
||||
clearKeys,
|
||||
GeoApiClient,
|
||||
DEFAULT_KEYS_STORAGE_KEY
|
||||
};
|
||||
Reference in New Issue
Block a user