@livo-build/runtime 0.2.6 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aes.d.ts +4 -0
- package/dist/aes.js +38 -0
- package/dist/auth.test.d.ts +1 -0
- package/dist/auth.test.js +57 -0
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +27 -0
- package/dist/email.d.ts +74 -0
- package/dist/email.js +100 -0
- package/dist/email.test.d.ts +1 -0
- package/dist/email.test.js +68 -0
- package/dist/http.d.ts +48 -0
- package/dist/http.js +133 -0
- package/dist/hyperliquid.d.ts +269 -0
- package/dist/hyperliquid.js +194 -0
- package/dist/hyperliquid.test.js +17 -1
- package/dist/hyperliquidBridge.d.ts +30 -0
- package/dist/hyperliquidBridge.js +61 -0
- package/dist/index.d.ts +24 -1
- package/dist/index.js +17 -0
- package/dist/nft.d.ts +33 -0
- package/dist/nft.js +72 -0
- package/dist/nft.test.d.ts +1 -0
- package/dist/nft.test.js +44 -0
- package/dist/ratelimit.d.ts +19 -0
- package/dist/ratelimit.js +11 -0
- package/dist/reads.d.ts +12 -0
- package/dist/reads.js +36 -0
- package/dist/sessions.d.ts +8 -0
- package/dist/sessions.js +60 -0
- package/dist/signals.d.ts +131 -0
- package/dist/signals.js +146 -0
- package/dist/siwe.d.ts +24 -0
- package/dist/siwe.js +33 -0
- package/dist/sse.test.d.ts +1 -0
- package/dist/sse.test.js +28 -0
- package/dist/webhook.d.ts +18 -0
- package/dist/webhook.js +49 -0
- package/dist/webhook.test.d.ts +1 -0
- package/dist/webhook.test.js +46 -0
- package/package.json +1 -1
package/dist/siwe.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Sign-In With Ethereum (EIP-4361) — build the canonical message + verify a signed
|
|
2
|
+
// one. The standard way to authenticate a wallet to a backend; pairs with sessions.
|
|
3
|
+
import { verifyMessage } from "./sig.js";
|
|
4
|
+
export function createSiweMessage(p) {
|
|
5
|
+
const lines = [`${p.domain} wants you to sign in with your Ethereum account:`, p.address, ""];
|
|
6
|
+
if (p.statement)
|
|
7
|
+
lines.push(p.statement, "");
|
|
8
|
+
lines.push(`URI: ${p.uri}`, `Version: ${p.version ?? "1"}`, `Chain ID: ${p.chainId}`, `Nonce: ${p.nonce}`, `Issued At: ${p.issuedAt ?? new Date().toISOString()}`);
|
|
9
|
+
if (p.expirationTime)
|
|
10
|
+
lines.push(`Expiration Time: ${p.expirationTime}`);
|
|
11
|
+
return lines.join("\n");
|
|
12
|
+
}
|
|
13
|
+
function field(message, label) {
|
|
14
|
+
const m = new RegExp(`^${label}(.*)$`, "m").exec(message);
|
|
15
|
+
return m?.[1]?.trim();
|
|
16
|
+
}
|
|
17
|
+
// Verify a SIWE message + signature: the signature recovers to the claimed address,
|
|
18
|
+
// the nonce matches (if given), and it isn't expired.
|
|
19
|
+
export async function verifySiweMessage(message, signature, opts = {}) {
|
|
20
|
+
const address = message.split("\n")[1]?.trim();
|
|
21
|
+
if (!address || !/^0x[0-9a-fA-F]{40}$/.test(address))
|
|
22
|
+
return { valid: false };
|
|
23
|
+
const nonce = field(message, "Nonce: ");
|
|
24
|
+
if (opts.nonce && nonce !== opts.nonce)
|
|
25
|
+
return { valid: false };
|
|
26
|
+
if (opts.domain && !message.startsWith(opts.domain + " "))
|
|
27
|
+
return { valid: false };
|
|
28
|
+
const exp = field(message, "Expiration Time: ");
|
|
29
|
+
if (exp && Date.parse(exp) < Date.now())
|
|
30
|
+
return { valid: false };
|
|
31
|
+
const ok = await verifyMessage({ address: address, message, signature: signature });
|
|
32
|
+
return { valid: ok, address: ok ? address : undefined, nonce };
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/sse.test.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { sse } from "./http.js";
|
|
3
|
+
async function readAll(res) {
|
|
4
|
+
return await res.text();
|
|
5
|
+
}
|
|
6
|
+
describe("sse", () => {
|
|
7
|
+
it("formats events (data/event/id) and a heartbeat, and sets the right headers", async () => {
|
|
8
|
+
const conn = sse();
|
|
9
|
+
expect(conn.response.headers.get("content-type")).toBe("text/event-stream");
|
|
10
|
+
conn.send({ px: 100 });
|
|
11
|
+
conn.send("hello", { event: "tick", id: "7" });
|
|
12
|
+
conn.ping();
|
|
13
|
+
conn.send("a\nb"); // multiline → two data: lines
|
|
14
|
+
conn.close();
|
|
15
|
+
const text = await readAll(conn.response);
|
|
16
|
+
expect(text).toContain('data: {"px":100}\n\n');
|
|
17
|
+
expect(text).toContain("event: tick\nid: 7\ndata: hello\n\n");
|
|
18
|
+
expect(text).toContain(": ping\n\n");
|
|
19
|
+
expect(text).toContain("data: a\ndata: b\n\n");
|
|
20
|
+
expect(conn.closed).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it("stops writing after close (no throw)", async () => {
|
|
23
|
+
const conn = sse();
|
|
24
|
+
conn.close();
|
|
25
|
+
conn.send({ ignored: true });
|
|
26
|
+
expect(await readAll(conn.response)).toBe("");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type SignatureEncoding = "hex" | "base64";
|
|
2
|
+
/** Compute the HMAC-SHA256 signature of a payload (for signing outgoing webhooks/tests). */
|
|
3
|
+
export declare function signWebhook(payload: string, secret: string, encoding?: SignatureEncoding): Promise<string>;
|
|
4
|
+
export interface VerifyWebhookParams {
|
|
5
|
+
/** The RAW request body (verify before JSON.parse — re-serialization changes bytes). */
|
|
6
|
+
payload: string;
|
|
7
|
+
secret: string;
|
|
8
|
+
/** The signature header value. A `scheme=` prefix (e.g. "sha256=") is stripped for hex. */
|
|
9
|
+
signature: string;
|
|
10
|
+
/** Digest encoding of `signature` (default "hex"). */
|
|
11
|
+
encoding?: SignatureEncoding;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Verify a webhook's HMAC-SHA256 signature against the raw body. Returns true iff the
|
|
15
|
+
* signature matches. For hex signatures a leading `algo=` prefix (GitHub's "sha256=")
|
|
16
|
+
* is stripped automatically.
|
|
17
|
+
*/
|
|
18
|
+
export declare function verifyWebhook(params: VerifyWebhookParams): Promise<boolean>;
|
package/dist/webhook.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Inbound webhook signature verification (GitHub/Stripe-style HMAC-SHA256). Verify
|
|
2
|
+
// that a raw request body was signed with your shared secret before trusting it.
|
|
3
|
+
// Constant-time comparison; no hand-rolled crypto.
|
|
4
|
+
function enc(s) {
|
|
5
|
+
return new TextEncoder().encode(s);
|
|
6
|
+
}
|
|
7
|
+
function toHex(bytes) {
|
|
8
|
+
let out = "";
|
|
9
|
+
for (const b of bytes)
|
|
10
|
+
out += b.toString(16).padStart(2, "0");
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
function toBase64(bytes) {
|
|
14
|
+
let bin = "";
|
|
15
|
+
for (const b of bytes)
|
|
16
|
+
bin += String.fromCharCode(b);
|
|
17
|
+
return btoa(bin);
|
|
18
|
+
}
|
|
19
|
+
async function hmac(secret, payload) {
|
|
20
|
+
const key = await crypto.subtle.importKey("raw", enc(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
21
|
+
return new Uint8Array(await crypto.subtle.sign("HMAC", key, enc(payload)));
|
|
22
|
+
}
|
|
23
|
+
/** Constant-time string compare (length-independent leak is acceptable for digests). */
|
|
24
|
+
function timingSafeEqual(a, b) {
|
|
25
|
+
if (a.length !== b.length)
|
|
26
|
+
return false;
|
|
27
|
+
let diff = 0;
|
|
28
|
+
for (let i = 0; i < a.length; i++)
|
|
29
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
30
|
+
return diff === 0;
|
|
31
|
+
}
|
|
32
|
+
/** Compute the HMAC-SHA256 signature of a payload (for signing outgoing webhooks/tests). */
|
|
33
|
+
export async function signWebhook(payload, secret, encoding = "hex") {
|
|
34
|
+
const mac = await hmac(secret, payload);
|
|
35
|
+
return encoding === "base64" ? toBase64(mac) : toHex(mac);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Verify a webhook's HMAC-SHA256 signature against the raw body. Returns true iff the
|
|
39
|
+
* signature matches. For hex signatures a leading `algo=` prefix (GitHub's "sha256=")
|
|
40
|
+
* is stripped automatically.
|
|
41
|
+
*/
|
|
42
|
+
export async function verifyWebhook(params) {
|
|
43
|
+
const encoding = params.encoding ?? "hex";
|
|
44
|
+
let provided = params.signature.trim();
|
|
45
|
+
if (encoding === "hex")
|
|
46
|
+
provided = provided.replace(/^[A-Za-z0-9_-]+=/, ""); // strip "sha256=" etc (hex has no '=')
|
|
47
|
+
const expected = await signWebhook(params.payload, params.secret, encoding);
|
|
48
|
+
return timingSafeEqual(expected.toLowerCase(), provided.toLowerCase());
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { signWebhook, verifyWebhook } from "./webhook.js";
|
|
3
|
+
import { cached } from "./cache.js";
|
|
4
|
+
describe("webhook signatures", () => {
|
|
5
|
+
it("round-trips a hex signature and strips a sha256= prefix", async () => {
|
|
6
|
+
const sig = await signWebhook("payload-body", "shh");
|
|
7
|
+
expect(await verifyWebhook({ payload: "payload-body", secret: "shh", signature: sig })).toBe(true);
|
|
8
|
+
expect(await verifyWebhook({ payload: "payload-body", secret: "shh", signature: "sha256=" + sig })).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
it("rejects a wrong secret or tampered payload", async () => {
|
|
11
|
+
const sig = await signWebhook("a", "s1");
|
|
12
|
+
expect(await verifyWebhook({ payload: "a", secret: "s2", signature: sig })).toBe(false);
|
|
13
|
+
expect(await verifyWebhook({ payload: "b", secret: "s1", signature: sig })).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
it("supports base64 encoding", async () => {
|
|
16
|
+
const sig = await signWebhook("x", "k", "base64");
|
|
17
|
+
expect(await verifyWebhook({ payload: "x", secret: "k", signature: sig, encoding: "base64" })).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
function memKv() {
|
|
21
|
+
const store = new Map();
|
|
22
|
+
return {
|
|
23
|
+
store,
|
|
24
|
+
get: async (k) => store.get(k) ?? null,
|
|
25
|
+
put: async (k, v) => void store.set(k, v),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
describe("cached", () => {
|
|
29
|
+
it("computes once, then serves from cache", async () => {
|
|
30
|
+
const kv = memKv();
|
|
31
|
+
let calls = 0;
|
|
32
|
+
const produce = async () => {
|
|
33
|
+
calls++;
|
|
34
|
+
return { n: 42 };
|
|
35
|
+
};
|
|
36
|
+
expect(await cached(kv, "k", produce, { ttlSeconds: 60 })).toEqual({ n: 42 });
|
|
37
|
+
expect(await cached(kv, "k", produce, { ttlSeconds: 60 })).toEqual({ n: 42 });
|
|
38
|
+
expect(calls).toBe(1);
|
|
39
|
+
expect(kv.store.has("cache:k")).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it("recomputes when the cached value is corrupt", async () => {
|
|
42
|
+
const kv = memKv();
|
|
43
|
+
kv.store.set("cache:bad", "{not json");
|
|
44
|
+
expect(await cached(kv, "bad", async () => "fresh", { ttlSeconds: 60 })).toBe("fresh");
|
|
45
|
+
});
|
|
46
|
+
});
|