@playmos/sdk 0.1.4 → 0.1.6

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.
@@ -0,0 +1,26 @@
1
+ import { k as PlaymosError, W as WebhookEvent } from './errors-B-85VYMv.cjs';
2
+
3
+ /**
4
+ * Webhook signature verification (spec §6.2) — server-side only.
5
+ *
6
+ * Signature scheme (mirrors the Playmos service byte-for-byte):
7
+ * header `X-Playmos-Signature: t=<unixSeconds>,v1=<hex>`
8
+ * where <hex> = HMAC_SHA256(secret, `${t}.${rawBody}`)
9
+ *
10
+ * Verify BEFORE trusting an event. Uses node:crypto (constant-time compare) and
11
+ * rejects stale timestamps to blunt replay. Throws on any mismatch.
12
+ */
13
+
14
+ declare class WebhookSignatureError extends PlaymosError {
15
+ constructor(message: string);
16
+ }
17
+ /**
18
+ * Verify a webhook and return the parsed event. `rawBody` MUST be the exact raw
19
+ * request bytes (use express.raw / fastify rawBody) — a re-serialized JSON body
20
+ * will not match the signature.
21
+ */
22
+ declare function verifyWebhook(rawBody: string | Buffer, signatureHeader: string | string[] | undefined, secret: string, opts?: {
23
+ toleranceSeconds?: number;
24
+ }): WebhookEvent;
25
+
26
+ export { WebhookSignatureError, verifyWebhook };
@@ -0,0 +1,26 @@
1
+ import { k as PlaymosError, W as WebhookEvent } from './errors-B-85VYMv.js';
2
+
3
+ /**
4
+ * Webhook signature verification (spec §6.2) — server-side only.
5
+ *
6
+ * Signature scheme (mirrors the Playmos service byte-for-byte):
7
+ * header `X-Playmos-Signature: t=<unixSeconds>,v1=<hex>`
8
+ * where <hex> = HMAC_SHA256(secret, `${t}.${rawBody}`)
9
+ *
10
+ * Verify BEFORE trusting an event. Uses node:crypto (constant-time compare) and
11
+ * rejects stale timestamps to blunt replay. Throws on any mismatch.
12
+ */
13
+
14
+ declare class WebhookSignatureError extends PlaymosError {
15
+ constructor(message: string);
16
+ }
17
+ /**
18
+ * Verify a webhook and return the parsed event. `rawBody` MUST be the exact raw
19
+ * request bytes (use express.raw / fastify rawBody) — a re-serialized JSON body
20
+ * will not match the signature.
21
+ */
22
+ declare function verifyWebhook(rawBody: string | Buffer, signatureHeader: string | string[] | undefined, secret: string, opts?: {
23
+ toleranceSeconds?: number;
24
+ }): WebhookEvent;
25
+
26
+ export { WebhookSignatureError, verifyWebhook };
package/dist/server.js ADDED
@@ -0,0 +1,47 @@
1
+ import { PlaymosError } from './chunk-B7SHFZYY.js';
2
+ import { timingSafeEqual, createHmac } from 'crypto';
3
+
4
+ var WebhookSignatureError = class extends PlaymosError {
5
+ constructor(message) {
6
+ super("api_error", `Webhook signature verification failed: ${message}`);
7
+ }
8
+ };
9
+ function parseHeader(header) {
10
+ const parts = Object.fromEntries(
11
+ header.split(",").map((kv) => {
12
+ const [k, v] = kv.split("=");
13
+ return [k?.trim(), v?.trim()];
14
+ })
15
+ );
16
+ const t = Number(parts["t"]);
17
+ const v1 = parts["v1"];
18
+ if (!Number.isFinite(t) || !v1) throw new WebhookSignatureError("malformed signature header");
19
+ return { t, v1 };
20
+ }
21
+ function computeSignature(secret, t, rawBody) {
22
+ return createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
23
+ }
24
+ function verifyWebhook(rawBody, signatureHeader, secret, opts = {}) {
25
+ if (!signatureHeader || Array.isArray(signatureHeader)) {
26
+ throw new WebhookSignatureError("missing signature header");
27
+ }
28
+ if (!secret) throw new WebhookSignatureError("missing signing secret");
29
+ const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
30
+ const { t, v1 } = parseHeader(signatureHeader);
31
+ const tolerance = opts.toleranceSeconds ?? 300;
32
+ const nowSec = Math.floor(Date.now() / 1e3);
33
+ if (Math.abs(nowSec - t) > tolerance) {
34
+ throw new WebhookSignatureError(`timestamp outside tolerance (${tolerance}s)`);
35
+ }
36
+ const expected = computeSignature(secret, t, body);
37
+ const a = Buffer.from(expected, "hex");
38
+ const b = Buffer.from(v1, "hex");
39
+ if (a.length !== b.length || !timingSafeEqual(a, b)) {
40
+ throw new WebhookSignatureError("signature mismatch");
41
+ }
42
+ return JSON.parse(body);
43
+ }
44
+
45
+ export { WebhookSignatureError, verifyWebhook };
46
+ //# sourceMappingURL=server.js.map
47
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webhooks.ts"],"names":[],"mappings":";;;AAeO,IAAM,qBAAA,GAAN,cAAoC,YAAA,CAAa;AAAA,EACtD,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,WAAA,EAAa,CAAA,uCAAA,EAA0C,OAAO,CAAA,CAAE,CAAA;AAAA,EACxE;AACF;AAEA,SAAS,YAAY,MAAA,EAA2C;AAC9D,EAAA,MAAM,QAAQ,MAAA,CAAO,WAAA;AAAA,IACnB,OAAO,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAC,EAAA,KAAO;AAC5B,MAAA,MAAM,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,EAAA,CAAG,MAAM,GAAG,CAAA;AAC3B,MAAA,OAAO,CAAC,CAAA,EAAG,IAAA,EAAK,EAAG,CAAA,EAAG,MAAM,CAAA;AAAA,IAC9B,CAAC;AAAA,GACH;AACA,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,KAAA,CAAM,GAAG,CAAC,CAAA;AAC3B,EAAA,MAAM,EAAA,GAAK,MAAM,IAAI,CAAA;AACrB,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,IAAK,CAAC,EAAA,EAAI,MAAM,IAAI,qBAAA,CAAsB,4BAA4B,CAAA;AAC5F,EAAA,OAAO,EAAE,GAAG,EAAA,EAAG;AACjB;AAEA,SAAS,gBAAA,CAAiB,MAAA,EAAgB,CAAA,EAAW,OAAA,EAAyB;AAC5E,EAAA,OAAO,UAAA,CAAW,QAAA,EAAU,MAAM,CAAA,CAAE,MAAA,CAAO,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AAC5E;AAOO,SAAS,cACd,OAAA,EACA,eAAA,EACA,MAAA,EACA,IAAA,GAAsC,EAAC,EACzB;AACd,EAAA,IAAI,CAAC,eAAA,IAAmB,KAAA,CAAM,OAAA,CAAQ,eAAe,CAAA,EAAG;AACtD,IAAA,MAAM,IAAI,sBAAsB,0BAA0B,CAAA;AAAA,EAC5D;AACA,EAAA,IAAI,CAAC,MAAA,EAAQ,MAAM,IAAI,sBAAsB,wBAAwB,CAAA;AAErE,EAAA,MAAM,OAAO,OAAO,OAAA,KAAY,WAAW,OAAA,GAAU,OAAA,CAAQ,SAAS,MAAM,CAAA;AAC5E,EAAA,MAAM,EAAE,CAAA,EAAG,EAAA,EAAG,GAAI,YAAY,eAAe,CAAA;AAE7C,EAAA,MAAM,SAAA,GAAY,KAAK,gBAAA,IAAoB,GAAA;AAC3C,EAAA,MAAM,SAAS,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AAC3C,EAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAA,GAAS,CAAC,IAAI,SAAA,EAAW;AACpC,IAAA,MAAM,IAAI,qBAAA,CAAsB,CAAA,6BAAA,EAAgC,SAAS,CAAA,EAAA,CAAI,CAAA;AAAA,EAC/E;AAEA,EAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,MAAA,EAAQ,CAAA,EAAG,IAAI,CAAA;AACjD,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,QAAA,EAAU,KAAK,CAAA;AACrC,EAAA,MAAM,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,EAAA,EAAI,KAAK,CAAA;AAC/B,EAAA,IAAI,CAAA,CAAE,WAAW,CAAA,CAAE,MAAA,IAAU,CAAC,eAAA,CAAgB,CAAA,EAAG,CAAC,CAAA,EAAG;AACnD,IAAA,MAAM,IAAI,sBAAsB,oBAAoB,CAAA;AAAA,EACtD;AAEA,EAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AACxB","file":"server.js","sourcesContent":["/**\n * Webhook signature verification (spec §6.2) — server-side only.\n *\n * Signature scheme (mirrors the Playmos service byte-for-byte):\n * header `X-Playmos-Signature: t=<unixSeconds>,v1=<hex>`\n * where <hex> = HMAC_SHA256(secret, `${t}.${rawBody}`)\n *\n * Verify BEFORE trusting an event. Uses node:crypto (constant-time compare) and\n * rejects stale timestamps to blunt replay. Throws on any mismatch.\n */\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { PlaymosError } from \"./errors.js\";\nimport type { WebhookEvent } from \"./types.js\";\n\nexport class WebhookSignatureError extends PlaymosError {\n constructor(message: string) {\n super(\"api_error\", `Webhook signature verification failed: ${message}`);\n }\n}\n\nfunction parseHeader(header: string): { t: number; v1: string } {\n const parts = Object.fromEntries(\n header.split(\",\").map((kv) => {\n const [k, v] = kv.split(\"=\");\n return [k?.trim(), v?.trim()];\n }),\n );\n const t = Number(parts[\"t\"]);\n const v1 = parts[\"v1\"];\n if (!Number.isFinite(t) || !v1) throw new WebhookSignatureError(\"malformed signature header\");\n return { t, v1 };\n}\n\nfunction computeSignature(secret: string, t: number, rawBody: string): string {\n return createHmac(\"sha256\", secret).update(`${t}.${rawBody}`).digest(\"hex\");\n}\n\n/**\n * Verify a webhook and return the parsed event. `rawBody` MUST be the exact raw\n * request bytes (use express.raw / fastify rawBody) — a re-serialized JSON body\n * will not match the signature.\n */\nexport function verifyWebhook(\n rawBody: string | Buffer,\n signatureHeader: string | string[] | undefined,\n secret: string,\n opts: { toleranceSeconds?: number } = {},\n): WebhookEvent {\n if (!signatureHeader || Array.isArray(signatureHeader)) {\n throw new WebhookSignatureError(\"missing signature header\");\n }\n if (!secret) throw new WebhookSignatureError(\"missing signing secret\");\n\n const body = typeof rawBody === \"string\" ? rawBody : rawBody.toString(\"utf8\");\n const { t, v1 } = parseHeader(signatureHeader);\n\n const tolerance = opts.toleranceSeconds ?? 300;\n const nowSec = Math.floor(Date.now() / 1000);\n if (Math.abs(nowSec - t) > tolerance) {\n throw new WebhookSignatureError(`timestamp outside tolerance (${tolerance}s)`);\n }\n\n const expected = computeSignature(secret, t, body);\n const a = Buffer.from(expected, \"hex\");\n const b = Buffer.from(v1, \"hex\");\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n throw new WebhookSignatureError(\"signature mismatch\");\n }\n\n return JSON.parse(body) as WebhookEvent;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playmos/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Playmos SDK — stablecoin payments for games on Base. One SDK for IAP (1%), skill-game prize-pool entries (10%, 60/30/10), and agent economies. USD in, USDC on-chain, no crypto UX for players.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -14,6 +14,11 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js",
16
16
  "require": "./dist/index.cjs"
17
+ },
18
+ "./server": {
19
+ "types": "./dist/server.d.ts",
20
+ "import": "./dist/server.js",
21
+ "require": "./dist/server.cjs"
17
22
  }
18
23
  },
19
24
  "files": [