@aimearn/webhook-sdk 0.1.0
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/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/dist/browser/index.d.ts +70 -0
- package/dist/browser/index.js +100 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.cjs +338 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +246 -0
- package/dist/index.d.ts +246 -0
- package/dist/index.js +294 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @aimearn/webhook-sdk changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — initial release
|
|
4
|
+
|
|
5
|
+
- `AimearnClient` with `orderCompleted` / `orderShipped` /
|
|
6
|
+
`orderRefunded` / `orderCancelled` methods.
|
|
7
|
+
- HMAC-SHA256 request signing + response-signature verification
|
|
8
|
+
(5-minute replay window).
|
|
9
|
+
- Exponential-backoff retry policy (1s → 60s, 24 attempts ≈ 24h).
|
|
10
|
+
- Typed error hierarchy keyed on `WebhookDelivery.result` enum:
|
|
11
|
+
`AimearnAuthError`, `AimearnValidationError`,
|
|
12
|
+
`AimearnNotFoundError`, `AimearnForbiddenError`,
|
|
13
|
+
`AimearnConflictError`, `AimearnPlatformError`.
|
|
14
|
+
- Auto-generated ULID-shaped `eventId`s; brand-supplied IDs are
|
|
15
|
+
preserved when provided.
|
|
16
|
+
- Browser entry: `captureReferralFromUrl`,
|
|
17
|
+
`getStoredReferralCode`, `clearReferralCode` for the
|
|
18
|
+
`?aimearn=…` cookie-based affiliate-link capture flow. ~1KB
|
|
19
|
+
gzipped, no external deps.
|
|
20
|
+
- Full docs at `/docs/sdk` in the host Next.js app.
|
|
21
|
+
- Runnable Express demo at `examples/webhook-sdk-express/`.
|
|
22
|
+
|
|
23
|
+
Targets webhook contract `v1`. SDK majors will lag spec majors —
|
|
24
|
+
deprecation window will be published when a breaking spec change
|
|
25
|
+
lands.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aim Earn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @aimearn/webhook-sdk
|
|
2
|
+
|
|
3
|
+
Brand SDK for the Aim Earn webhook ingest API.
|
|
4
|
+
|
|
5
|
+
Wraps:
|
|
6
|
+
- HMAC-SHA256 request signing + response-signature verification
|
|
7
|
+
- Exponential-backoff retries on 5xx / 429 / network errors
|
|
8
|
+
- ULID-shaped `eventId` auto-generation for idempotent retries
|
|
9
|
+
- Typed error hierarchy keyed on `WebhookDelivery.result`
|
|
10
|
+
- Browser entry for the affiliate-link `?aimearn=…` capture flow
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @aimearn/webhook-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
(or via the workspace symlink in this monorepo).
|
|
19
|
+
|
|
20
|
+
## Server-side usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { AimearnClient } from "@aimearn/webhook-sdk";
|
|
24
|
+
|
|
25
|
+
const client = new AimearnClient({
|
|
26
|
+
keyId: process.env.AIMEARN_KEY_ID!,
|
|
27
|
+
secret: process.env.AIMEARN_WEBHOOK_SECRET!,
|
|
28
|
+
endpoint: "https://webhooks.aimearn.platform.com",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = await client.orderCompleted({
|
|
32
|
+
orderId: "ord_12345",
|
|
33
|
+
data: {
|
|
34
|
+
items: [
|
|
35
|
+
{ productId: "shopwave-sku-789", quantity: 1, unitPriceCents: 19900 },
|
|
36
|
+
],
|
|
37
|
+
subtotalCents: 19900,
|
|
38
|
+
currency: "SGD",
|
|
39
|
+
country: "SG",
|
|
40
|
+
referralCode: "XX0025",
|
|
41
|
+
customer: { name: "Jane Doe", email: "jane@example.com" },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
// → { status: "accepted", orderId: "shopwave#ord_12345", matchedItems: 1 }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Methods: `orderCompleted`, `orderShipped`, `orderRefunded`, `orderCancelled`.
|
|
48
|
+
|
|
49
|
+
## Browser usage (affiliate-link capture)
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// On every page load
|
|
53
|
+
import { captureReferralFromUrl } from "@aimearn/webhook-sdk/browser";
|
|
54
|
+
captureReferralFromUrl();
|
|
55
|
+
|
|
56
|
+
// At checkout
|
|
57
|
+
import { getStoredReferralCode } from "@aimearn/webhook-sdk/browser";
|
|
58
|
+
const referralCode = getStoredReferralCode(); // "XX0025" or null
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
ESM-only, ~1KB gzipped, no external deps.
|
|
62
|
+
|
|
63
|
+
## Documentation
|
|
64
|
+
|
|
65
|
+
Full docs live in the host Next.js app under `/docs/sdk`:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run dev
|
|
69
|
+
# → http://localhost:3000/docs/sdk
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or browse the source: [`docs/sdk/*.mdx`](https://github.com/aimearn/aim-earn/tree/features/order-pipeline/app/docs/sdk).
|
|
73
|
+
|
|
74
|
+
## Build
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# From the monorepo root:
|
|
78
|
+
npm run build:sdk
|
|
79
|
+
|
|
80
|
+
# Or inside this package:
|
|
81
|
+
npm run build
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Produces:
|
|
85
|
+
|
|
86
|
+
| Path | Use |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `dist/index.js` | Server entry, ESM |
|
|
89
|
+
| `dist/index.cjs` | Server entry, CJS |
|
|
90
|
+
| `dist/index.d.ts` | Server entry types |
|
|
91
|
+
| `dist/browser/index.js` | Browser entry, ESM |
|
|
92
|
+
| `dist/browser/index.d.ts` | Browser entry types |
|
|
93
|
+
|
|
94
|
+
## Engines
|
|
95
|
+
|
|
96
|
+
Requires Node.js 20+ (Node 24 LTS preferred). Node 18 is EOL.
|
|
97
|
+
|
|
98
|
+
## Tests
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm test
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
54 unit tests covering signing, retry, eventId, errors, client, and
|
|
105
|
+
browser cookies (Vitest + jsdom).
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
interface SetCookieOptions {
|
|
2
|
+
/** Number of days until expiry. Default 30. */
|
|
3
|
+
ttlDays?: number;
|
|
4
|
+
/** Cookie path. Default "/". */
|
|
5
|
+
path?: string;
|
|
6
|
+
/** Domain attribute (e.g. ".acme.com"). Defaults to host-only. */
|
|
7
|
+
domain?: string;
|
|
8
|
+
/** Same-site policy. Default "Lax" — covers normal cross-page nav. */
|
|
9
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
10
|
+
/** Secure flag. Default true (assumes HTTPS storefront). */
|
|
11
|
+
secure?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare function setCookie(name: string, value: string, options?: SetCookieOptions): void;
|
|
14
|
+
declare function getCookie(name: string): string | null;
|
|
15
|
+
declare function deleteCookie(name: string, options?: Pick<SetCookieOptions, "path" | "domain">): void;
|
|
16
|
+
|
|
17
|
+
declare const BROWSER_SDK_VERSION = "0.1.0";
|
|
18
|
+
/** Default URL parameter name. Brands can override per-call. */
|
|
19
|
+
declare const DEFAULT_QUERY_PARAM = "aimearn";
|
|
20
|
+
/** Default cookie name. Brands can override per-call. */
|
|
21
|
+
declare const DEFAULT_COOKIE_NAME = "aimearn_referral";
|
|
22
|
+
interface CaptureOptions {
|
|
23
|
+
/** Query string parameter to read. Default "aimearn". */
|
|
24
|
+
queryParam?: string;
|
|
25
|
+
/** Cookie name to write. Default "aimearn_referral". */
|
|
26
|
+
cookieName?: string;
|
|
27
|
+
/** Cookie TTL in days. Default 30. */
|
|
28
|
+
ttlDays?: number;
|
|
29
|
+
/** Cookie domain (e.g. ".acme.com"). Defaults to host-only. */
|
|
30
|
+
domain?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Override the URL to read from. Defaults to `window.location.href`.
|
|
33
|
+
* Useful for tests or for processing a deep-linked URL the storefront
|
|
34
|
+
* received from a campaign.
|
|
35
|
+
*/
|
|
36
|
+
url?: string;
|
|
37
|
+
/**
|
|
38
|
+
* If true, even an empty `?aimearn=` value will overwrite an existing
|
|
39
|
+
* cookie. Default false (preserves the previously-captured code).
|
|
40
|
+
*/
|
|
41
|
+
allowEmptyOverwrite?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Reads the `?aimearn=…` query param (or configured equivalent) and
|
|
45
|
+
* writes it to a first-party cookie. Returns the captured code, or
|
|
46
|
+
* null if none was present in the URL.
|
|
47
|
+
*
|
|
48
|
+
* Idempotent — calling repeatedly is fine; the cookie is just rewritten
|
|
49
|
+
* with a fresh expiry. Never throws on SSR (no-op when document is
|
|
50
|
+
* undefined).
|
|
51
|
+
*/
|
|
52
|
+
declare function captureReferralFromUrl(options?: CaptureOptions): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Reads the previously-captured referral code from the cookie. Returns
|
|
55
|
+
* null when no cookie has been set, when the cookie has expired, or in
|
|
56
|
+
* SSR contexts.
|
|
57
|
+
*/
|
|
58
|
+
declare function getStoredReferralCode(cookieName?: string): string | null;
|
|
59
|
+
/**
|
|
60
|
+
* Deletes the referral cookie. Typical use: after the brand
|
|
61
|
+
* successfully POSTed `order.completed` with the referral code on it,
|
|
62
|
+
* clear the cookie so a future visit without an `?aimearn=` param
|
|
63
|
+
* doesn't replay an already-attributed sale.
|
|
64
|
+
*/
|
|
65
|
+
declare function clearReferralCode(cookieName?: string, options?: {
|
|
66
|
+
domain?: string;
|
|
67
|
+
path?: string;
|
|
68
|
+
}): void;
|
|
69
|
+
|
|
70
|
+
export { BROWSER_SDK_VERSION, type CaptureOptions, DEFAULT_COOKIE_NAME, DEFAULT_QUERY_PARAM, captureReferralFromUrl, clearReferralCode, deleteCookie, getCookie, getStoredReferralCode, setCookie };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/browser/cookies.ts
|
|
2
|
+
var DEFAULTS = {
|
|
3
|
+
ttlDays: 30,
|
|
4
|
+
path: "/",
|
|
5
|
+
sameSite: "Lax",
|
|
6
|
+
secure: true
|
|
7
|
+
};
|
|
8
|
+
function ssr() {
|
|
9
|
+
return typeof document === "undefined";
|
|
10
|
+
}
|
|
11
|
+
function setCookie(name, value, options = {}) {
|
|
12
|
+
if (ssr()) return;
|
|
13
|
+
const cfg = { ...DEFAULTS, ...options };
|
|
14
|
+
const expires = new Date(
|
|
15
|
+
Date.now() + cfg.ttlDays * 24 * 60 * 60 * 1e3
|
|
16
|
+
).toUTCString();
|
|
17
|
+
const parts = [
|
|
18
|
+
`${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
|
|
19
|
+
`Expires=${expires}`,
|
|
20
|
+
`Path=${cfg.path}`,
|
|
21
|
+
`SameSite=${cfg.sameSite}`
|
|
22
|
+
];
|
|
23
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
24
|
+
if (cfg.secure) parts.push("Secure");
|
|
25
|
+
document.cookie = parts.join("; ");
|
|
26
|
+
}
|
|
27
|
+
function getCookie(name) {
|
|
28
|
+
if (ssr()) return null;
|
|
29
|
+
const target = encodeURIComponent(name);
|
|
30
|
+
const cookies = document.cookie ? document.cookie.split(";") : [];
|
|
31
|
+
for (const raw of cookies) {
|
|
32
|
+
const eq = raw.indexOf("=");
|
|
33
|
+
if (eq < 0) continue;
|
|
34
|
+
const k = raw.slice(0, eq).trim();
|
|
35
|
+
if (k !== target) continue;
|
|
36
|
+
const v = raw.slice(eq + 1).trim();
|
|
37
|
+
try {
|
|
38
|
+
return decodeURIComponent(v);
|
|
39
|
+
} catch {
|
|
40
|
+
return v;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function deleteCookie(name, options = {}) {
|
|
46
|
+
if (ssr()) return;
|
|
47
|
+
const parts = [
|
|
48
|
+
`${encodeURIComponent(name)}=`,
|
|
49
|
+
`Expires=Thu, 01 Jan 1970 00:00:00 GMT`,
|
|
50
|
+
`Path=${options.path ?? "/"}`
|
|
51
|
+
];
|
|
52
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
53
|
+
document.cookie = parts.join("; ");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/browser/index.ts
|
|
57
|
+
var BROWSER_SDK_VERSION = "0.1.0";
|
|
58
|
+
var DEFAULT_QUERY_PARAM = "aimearn";
|
|
59
|
+
var DEFAULT_COOKIE_NAME = "aimearn_referral";
|
|
60
|
+
function captureReferralFromUrl(options = {}) {
|
|
61
|
+
const queryParam = options.queryParam ?? DEFAULT_QUERY_PARAM;
|
|
62
|
+
const cookieName = options.cookieName ?? DEFAULT_COOKIE_NAME;
|
|
63
|
+
if (typeof window === "undefined") return null;
|
|
64
|
+
const href = options.url ?? window.location.href;
|
|
65
|
+
let url;
|
|
66
|
+
try {
|
|
67
|
+
url = new URL(href);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const value = url.searchParams.get(queryParam);
|
|
72
|
+
if (value === null) return null;
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
if (trimmed === "" && !options.allowEmptyOverwrite) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
setCookie(cookieName, trimmed, {
|
|
78
|
+
ttlDays: options.ttlDays,
|
|
79
|
+
domain: options.domain
|
|
80
|
+
});
|
|
81
|
+
return trimmed;
|
|
82
|
+
}
|
|
83
|
+
function getStoredReferralCode(cookieName = DEFAULT_COOKIE_NAME) {
|
|
84
|
+
return getCookie(cookieName);
|
|
85
|
+
}
|
|
86
|
+
function clearReferralCode(cookieName = DEFAULT_COOKIE_NAME, options = {}) {
|
|
87
|
+
deleteCookie(cookieName, options);
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
BROWSER_SDK_VERSION,
|
|
91
|
+
DEFAULT_COOKIE_NAME,
|
|
92
|
+
DEFAULT_QUERY_PARAM,
|
|
93
|
+
captureReferralFromUrl,
|
|
94
|
+
clearReferralCode,
|
|
95
|
+
deleteCookie,
|
|
96
|
+
getCookie,
|
|
97
|
+
getStoredReferralCode,
|
|
98
|
+
setCookie
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/browser/cookies.ts","../../src/browser/index.ts"],"sourcesContent":["// Tiny first-party cookie helpers, browser-only. No external deps so\n// the bundle stays small (target <2KB gzipped).\n\nexport interface SetCookieOptions {\n /** Number of days until expiry. Default 30. */\n ttlDays?: number;\n /** Cookie path. Default \"/\". */\n path?: string;\n /** Domain attribute (e.g. \".acme.com\"). Defaults to host-only. */\n domain?: string;\n /** Same-site policy. Default \"Lax\" — covers normal cross-page nav. */\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n /** Secure flag. Default true (assumes HTTPS storefront). */\n secure?: boolean;\n}\n\nconst DEFAULTS: Required<Omit<SetCookieOptions, \"domain\">> = {\n ttlDays: 30,\n path: \"/\",\n sameSite: \"Lax\",\n secure: true,\n};\n\nfunction ssr(): boolean {\n return typeof document === \"undefined\";\n}\n\nexport function setCookie(\n name: string,\n value: string,\n options: SetCookieOptions = {},\n): void {\n if (ssr()) return;\n const cfg = { ...DEFAULTS, ...options };\n const expires = new Date(\n Date.now() + cfg.ttlDays * 24 * 60 * 60 * 1000,\n ).toUTCString();\n const parts = [\n `${encodeURIComponent(name)}=${encodeURIComponent(value)}`,\n `Expires=${expires}`,\n `Path=${cfg.path}`,\n `SameSite=${cfg.sameSite}`,\n ];\n if (options.domain) parts.push(`Domain=${options.domain}`);\n if (cfg.secure) parts.push(\"Secure\");\n document.cookie = parts.join(\"; \");\n}\n\nexport function getCookie(name: string): string | null {\n if (ssr()) return null;\n const target = encodeURIComponent(name);\n const cookies = document.cookie ? document.cookie.split(\";\") : [];\n for (const raw of cookies) {\n const eq = raw.indexOf(\"=\");\n if (eq < 0) continue;\n const k = raw.slice(0, eq).trim();\n if (k !== target) continue;\n const v = raw.slice(eq + 1).trim();\n try {\n return decodeURIComponent(v);\n } catch {\n return v;\n }\n }\n return null;\n}\n\nexport function deleteCookie(\n name: string,\n options: Pick<SetCookieOptions, \"path\" | \"domain\"> = {},\n): void {\n if (ssr()) return;\n const parts = [\n `${encodeURIComponent(name)}=`,\n `Expires=Thu, 01 Jan 1970 00:00:00 GMT`,\n `Path=${options.path ?? \"/\"}`,\n ];\n if (options.domain) parts.push(`Domain=${options.domain}`);\n document.cookie = parts.join(\"; \");\n}\n","// Browser entry — affiliate-link capture flow.\n//\n// Brand storefronts read the `?aimearn=<inviteCode>` query param when\n// a buyer lands from an affiliate's share link, persist it in a\n// first-party cookie (default 30 days), then echo it back as\n// `data.referralCode` on the `order.completed` webhook so commission\n// attribution works.\n//\n// Spec: docs/superpowers/specs/2026-04-29-webhook-ingest-design.md §6.2.\n\nimport { deleteCookie, getCookie, setCookie } from \"./cookies\";\n\nexport const BROWSER_SDK_VERSION = \"0.1.0\";\n\n/** Default URL parameter name. Brands can override per-call. */\nexport const DEFAULT_QUERY_PARAM = \"aimearn\";\n\n/** Default cookie name. Brands can override per-call. */\nexport const DEFAULT_COOKIE_NAME = \"aimearn_referral\";\n\nexport interface CaptureOptions {\n /** Query string parameter to read. Default \"aimearn\". */\n queryParam?: string;\n /** Cookie name to write. Default \"aimearn_referral\". */\n cookieName?: string;\n /** Cookie TTL in days. Default 30. */\n ttlDays?: number;\n /** Cookie domain (e.g. \".acme.com\"). Defaults to host-only. */\n domain?: string;\n /**\n * Override the URL to read from. Defaults to `window.location.href`.\n * Useful for tests or for processing a deep-linked URL the storefront\n * received from a campaign.\n */\n url?: string;\n /**\n * If true, even an empty `?aimearn=` value will overwrite an existing\n * cookie. Default false (preserves the previously-captured code).\n */\n allowEmptyOverwrite?: boolean;\n}\n\n/**\n * Reads the `?aimearn=…` query param (or configured equivalent) and\n * writes it to a first-party cookie. Returns the captured code, or\n * null if none was present in the URL.\n *\n * Idempotent — calling repeatedly is fine; the cookie is just rewritten\n * with a fresh expiry. Never throws on SSR (no-op when document is\n * undefined).\n */\nexport function captureReferralFromUrl(\n options: CaptureOptions = {},\n): string | null {\n const queryParam = options.queryParam ?? DEFAULT_QUERY_PARAM;\n const cookieName = options.cookieName ?? DEFAULT_COOKIE_NAME;\n\n // SSR guard — return null silently.\n if (typeof window === \"undefined\") return null;\n\n const href = options.url ?? window.location.href;\n let url: URL;\n try {\n url = new URL(href);\n } catch {\n return null;\n }\n\n const value = url.searchParams.get(queryParam);\n if (value === null) return null;\n\n // Treat whitespace-only as empty.\n const trimmed = value.trim();\n if (trimmed === \"\" && !options.allowEmptyOverwrite) {\n return null;\n }\n\n setCookie(cookieName, trimmed, {\n ttlDays: options.ttlDays,\n domain: options.domain,\n });\n return trimmed;\n}\n\n/**\n * Reads the previously-captured referral code from the cookie. Returns\n * null when no cookie has been set, when the cookie has expired, or in\n * SSR contexts.\n */\nexport function getStoredReferralCode(\n cookieName: string = DEFAULT_COOKIE_NAME,\n): string | null {\n return getCookie(cookieName);\n}\n\n/**\n * Deletes the referral cookie. Typical use: after the brand\n * successfully POSTed `order.completed` with the referral code on it,\n * clear the cookie so a future visit without an `?aimearn=` param\n * doesn't replay an already-attributed sale.\n */\nexport function clearReferralCode(\n cookieName: string = DEFAULT_COOKIE_NAME,\n options: { domain?: string; path?: string } = {},\n): void {\n deleteCookie(cookieName, options);\n}\n\n// Re-export cookie primitives in case brands need them for adjacent\n// flows (e.g. tracking a checkout step). No backward-compat promise on\n// these — the public API is the three functions above.\nexport { setCookie, getCookie, deleteCookie } from \"./cookies\";\n"],"mappings":";AAgBA,IAAM,WAAuD;AAAA,EAC3D,SAAS;AAAA,EACT,MAAM;AAAA,EACN,UAAU;AAAA,EACV,QAAQ;AACV;AAEA,SAAS,MAAe;AACtB,SAAO,OAAO,aAAa;AAC7B;AAEO,SAAS,UACd,MACA,OACA,UAA4B,CAAC,GACvB;AACN,MAAI,IAAI,EAAG;AACX,QAAM,MAAM,EAAE,GAAG,UAAU,GAAG,QAAQ;AACtC,QAAM,UAAU,IAAI;AAAA,IAClB,KAAK,IAAI,IAAI,IAAI,UAAU,KAAK,KAAK,KAAK;AAAA,EAC5C,EAAE,YAAY;AACd,QAAM,QAAQ;AAAA,IACZ,GAAG,mBAAmB,IAAI,CAAC,IAAI,mBAAmB,KAAK,CAAC;AAAA,IACxD,WAAW,OAAO;AAAA,IAClB,QAAQ,IAAI,IAAI;AAAA,IAChB,YAAY,IAAI,QAAQ;AAAA,EAC1B;AACA,MAAI,QAAQ,OAAQ,OAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;AACzD,MAAI,IAAI,OAAQ,OAAM,KAAK,QAAQ;AACnC,WAAS,SAAS,MAAM,KAAK,IAAI;AACnC;AAEO,SAAS,UAAU,MAA6B;AACrD,MAAI,IAAI,EAAG,QAAO;AAClB,QAAM,SAAS,mBAAmB,IAAI;AACtC,QAAM,UAAU,SAAS,SAAS,SAAS,OAAO,MAAM,GAAG,IAAI,CAAC;AAChE,aAAW,OAAO,SAAS;AACzB,UAAM,KAAK,IAAI,QAAQ,GAAG;AAC1B,QAAI,KAAK,EAAG;AACZ,UAAM,IAAI,IAAI,MAAM,GAAG,EAAE,EAAE,KAAK;AAChC,QAAI,MAAM,OAAQ;AAClB,UAAM,IAAI,IAAI,MAAM,KAAK,CAAC,EAAE,KAAK;AACjC,QAAI;AACF,aAAO,mBAAmB,CAAC;AAAA,IAC7B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,aACd,MACA,UAAqD,CAAC,GAChD;AACN,MAAI,IAAI,EAAG;AACX,QAAM,QAAQ;AAAA,IACZ,GAAG,mBAAmB,IAAI,CAAC;AAAA,IAC3B;AAAA,IACA,QAAQ,QAAQ,QAAQ,GAAG;AAAA,EAC7B;AACA,MAAI,QAAQ,OAAQ,OAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;AACzD,WAAS,SAAS,MAAM,KAAK,IAAI;AACnC;;;ACnEO,IAAM,sBAAsB;AAG5B,IAAM,sBAAsB;AAG5B,IAAM,sBAAsB;AAiC5B,SAAS,uBACd,UAA0B,CAAC,GACZ;AACf,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,aAAa,QAAQ,cAAc;AAGzC,MAAI,OAAO,WAAW,YAAa,QAAO;AAE1C,QAAM,OAAO,QAAQ,OAAO,OAAO,SAAS;AAC5C,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,IAAI;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,IAAI,aAAa,IAAI,UAAU;AAC7C,MAAI,UAAU,KAAM,QAAO;AAG3B,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,YAAY,MAAM,CAAC,QAAQ,qBAAqB;AAClD,WAAO;AAAA,EACT;AAEA,YAAU,YAAY,SAAS;AAAA,IAC7B,SAAS,QAAQ;AAAA,IACjB,QAAQ,QAAQ;AAAA,EAClB,CAAC;AACD,SAAO;AACT;AAOO,SAAS,sBACd,aAAqB,qBACN;AACf,SAAO,UAAU,UAAU;AAC7B;AAQO,SAAS,kBACd,aAAqB,qBACrB,UAA8C,CAAC,GACzC;AACN,eAAa,YAAY,OAAO;AAClC;","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
AimearnAuthError: () => AimearnAuthError,
|
|
24
|
+
AimearnClient: () => AimearnClient,
|
|
25
|
+
AimearnConflictError: () => AimearnConflictError,
|
|
26
|
+
AimearnError: () => AimearnError,
|
|
27
|
+
AimearnForbiddenError: () => AimearnForbiddenError,
|
|
28
|
+
AimearnNotFoundError: () => AimearnNotFoundError,
|
|
29
|
+
AimearnPlatformError: () => AimearnPlatformError,
|
|
30
|
+
AimearnValidationError: () => AimearnValidationError,
|
|
31
|
+
REPLAY_WINDOW_SECONDS: () => REPLAY_WINDOW_SECONDS,
|
|
32
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
33
|
+
backoffDelayMs: () => backoffDelayMs,
|
|
34
|
+
errorFromResponse: () => errorFromResponse,
|
|
35
|
+
generateEventId: () => generateEventId,
|
|
36
|
+
isRetriable: () => isRetriable,
|
|
37
|
+
parseSignatureHeader: () => parseSignatureHeader,
|
|
38
|
+
resolveRetryConfig: () => resolveRetryConfig,
|
|
39
|
+
signRequest: () => signRequest,
|
|
40
|
+
verifyResponseSignature: () => verifyResponseSignature
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(src_exports);
|
|
43
|
+
|
|
44
|
+
// src/eventId.ts
|
|
45
|
+
var import_node_crypto = require("crypto");
|
|
46
|
+
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
47
|
+
var ENCODING_LEN = ENCODING.length;
|
|
48
|
+
var TIME_LEN = 10;
|
|
49
|
+
var RANDOM_LEN = 16;
|
|
50
|
+
function encodeTime(now, len) {
|
|
51
|
+
let out = "";
|
|
52
|
+
let n = now;
|
|
53
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
54
|
+
const mod = n % ENCODING_LEN;
|
|
55
|
+
out = ENCODING[mod] + out;
|
|
56
|
+
n = (n - mod) / ENCODING_LEN;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function encodeRandom(len) {
|
|
61
|
+
const bytes = (0, import_node_crypto.randomBytes)(len);
|
|
62
|
+
let out = "";
|
|
63
|
+
for (let i = 0; i < len; i++) {
|
|
64
|
+
out += ENCODING[bytes[i] & 31];
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function generateEventId(now = Date.now()) {
|
|
69
|
+
return `evt_${encodeTime(now, TIME_LEN)}${encodeRandom(RANDOM_LEN)}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/signing.ts
|
|
73
|
+
var import_node_crypto2 = require("crypto");
|
|
74
|
+
var REPLAY_WINDOW_SECONDS = 300;
|
|
75
|
+
function signRequest(secret, rawBody, now = Math.floor(Date.now() / 1e3)) {
|
|
76
|
+
const v1 = (0, import_node_crypto2.createHmac)("sha256", secret).update(`${now}.${rawBody}`).digest("hex");
|
|
77
|
+
return `t=${now},v1=${v1}`;
|
|
78
|
+
}
|
|
79
|
+
function parseSignatureHeader(header) {
|
|
80
|
+
if (!header) return null;
|
|
81
|
+
let t = null;
|
|
82
|
+
let v1 = null;
|
|
83
|
+
for (const part of header.split(",")) {
|
|
84
|
+
const eq = part.indexOf("=");
|
|
85
|
+
if (eq < 0) return null;
|
|
86
|
+
const k = part.slice(0, eq).trim();
|
|
87
|
+
const v = part.slice(eq + 1).trim();
|
|
88
|
+
if (k === "t") {
|
|
89
|
+
const parsed = Number(v);
|
|
90
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
91
|
+
t = parsed;
|
|
92
|
+
} else if (k === "v1") {
|
|
93
|
+
if (!/^[0-9a-f]{64}$/.test(v)) return null;
|
|
94
|
+
v1 = v;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (t === null || v1 === null) return null;
|
|
98
|
+
return { t, v1 };
|
|
99
|
+
}
|
|
100
|
+
function verifyResponseSignature(secret, headerValue, responseBody, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
101
|
+
const sig = parseSignatureHeader(headerValue);
|
|
102
|
+
if (!sig) return false;
|
|
103
|
+
if (Math.abs(nowSeconds - sig.t) > REPLAY_WINDOW_SECONDS) return false;
|
|
104
|
+
const expected = (0, import_node_crypto2.createHmac)("sha256", secret).update(`${sig.t}.${responseBody}`).digest("hex");
|
|
105
|
+
if (expected.length !== sig.v1.length) return false;
|
|
106
|
+
return (0, import_node_crypto2.timingSafeEqual)(Buffer.from(expected), Buffer.from(sig.v1));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/errors.ts
|
|
110
|
+
var AimearnError = class extends Error {
|
|
111
|
+
code;
|
|
112
|
+
status;
|
|
113
|
+
responseBody;
|
|
114
|
+
constructor(code, message, status, responseBody) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.name = "AimearnError";
|
|
117
|
+
this.code = code;
|
|
118
|
+
this.status = status;
|
|
119
|
+
this.responseBody = responseBody;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var AimearnAuthError = class extends AimearnError {
|
|
123
|
+
constructor(code, message, status = 401, responseBody) {
|
|
124
|
+
super(code, message, status, responseBody);
|
|
125
|
+
this.name = "AimearnAuthError";
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var AimearnValidationError = class extends AimearnError {
|
|
129
|
+
constructor(code, message, status = 400, responseBody) {
|
|
130
|
+
super(code, message, status, responseBody);
|
|
131
|
+
this.name = "AimearnValidationError";
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var AimearnNotFoundError = class extends AimearnError {
|
|
135
|
+
constructor(code, message, status = 404, responseBody) {
|
|
136
|
+
super(code, message, status, responseBody);
|
|
137
|
+
this.name = "AimearnNotFoundError";
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var AimearnForbiddenError = class extends AimearnError {
|
|
141
|
+
constructor(code, message, status = 403, responseBody) {
|
|
142
|
+
super(code, message, status, responseBody);
|
|
143
|
+
this.name = "AimearnForbiddenError";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
var AimearnConflictError = class extends AimearnError {
|
|
147
|
+
constructor(code, message, status = 409, responseBody) {
|
|
148
|
+
super(code, message, status, responseBody);
|
|
149
|
+
this.name = "AimearnConflictError";
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var AimearnPlatformError = class extends AimearnError {
|
|
153
|
+
constructor(code, message, status = 500, responseBody) {
|
|
154
|
+
super(code, message, status, responseBody);
|
|
155
|
+
this.name = "AimearnPlatformError";
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var ERROR_CTOR_BY_STATUS = {
|
|
159
|
+
400: AimearnValidationError,
|
|
160
|
+
401: AimearnAuthError,
|
|
161
|
+
403: AimearnForbiddenError,
|
|
162
|
+
404: AimearnNotFoundError,
|
|
163
|
+
409: AimearnConflictError,
|
|
164
|
+
413: AimearnValidationError,
|
|
165
|
+
429: AimearnPlatformError
|
|
166
|
+
};
|
|
167
|
+
function errorFromResponse(status, body) {
|
|
168
|
+
const errorCode = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : "internal_error";
|
|
169
|
+
const Ctor = ERROR_CTOR_BY_STATUS[status] ?? (status >= 500 ? AimearnPlatformError : AimearnError);
|
|
170
|
+
return new Ctor(
|
|
171
|
+
errorCode,
|
|
172
|
+
`Aim Earn ${status}: ${errorCode}`,
|
|
173
|
+
status,
|
|
174
|
+
body
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/retry.ts
|
|
179
|
+
var DEFAULTS = {
|
|
180
|
+
maxAttempts: 24,
|
|
181
|
+
baseDelayMs: 1e3,
|
|
182
|
+
maxDelayMs: 6e4,
|
|
183
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
184
|
+
};
|
|
185
|
+
function resolveRetryConfig(config) {
|
|
186
|
+
return { ...DEFAULTS, ...config ?? {} };
|
|
187
|
+
}
|
|
188
|
+
function isRetriable(status) {
|
|
189
|
+
return status === 0 || status === 429 || status >= 500;
|
|
190
|
+
}
|
|
191
|
+
function backoffDelayMs(attempt, config) {
|
|
192
|
+
if (attempt <= 1) return 0;
|
|
193
|
+
const raw = config.baseDelayMs * 2 ** (attempt - 2);
|
|
194
|
+
return Math.min(raw, config.maxDelayMs);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/client.ts
|
|
198
|
+
var ROUTE = "/api/webhooks/order";
|
|
199
|
+
var AimearnClient = class {
|
|
200
|
+
config;
|
|
201
|
+
constructor(config) {
|
|
202
|
+
if (!config.keyId) throw new Error("AimearnClient: keyId is required.");
|
|
203
|
+
if (!config.secret) throw new Error("AimearnClient: secret is required.");
|
|
204
|
+
if (!config.endpoint) throw new Error("AimearnClient: endpoint is required.");
|
|
205
|
+
this.config = {
|
|
206
|
+
keyId: config.keyId,
|
|
207
|
+
secret: config.secret,
|
|
208
|
+
endpoint: config.endpoint.replace(/\/$/, ""),
|
|
209
|
+
fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
|
|
210
|
+
retry: resolveRetryConfig(config.retry)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
orderCompleted(input) {
|
|
214
|
+
return this.send("order.completed", input);
|
|
215
|
+
}
|
|
216
|
+
orderShipped(input) {
|
|
217
|
+
return this.send("order.shipped", input);
|
|
218
|
+
}
|
|
219
|
+
orderRefunded(input) {
|
|
220
|
+
return this.send("order.refunded", input);
|
|
221
|
+
}
|
|
222
|
+
orderCancelled(input) {
|
|
223
|
+
return this.send("order.cancelled", input);
|
|
224
|
+
}
|
|
225
|
+
async send(type, input) {
|
|
226
|
+
const envelope = {
|
|
227
|
+
eventId: input.eventId ?? generateEventId(),
|
|
228
|
+
type,
|
|
229
|
+
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
230
|
+
orderId: input.orderId,
|
|
231
|
+
data: input.data,
|
|
232
|
+
metadata: input.metadata ?? null
|
|
233
|
+
};
|
|
234
|
+
const rawBody = JSON.stringify(envelope);
|
|
235
|
+
const url = `${this.config.endpoint}${ROUTE}`;
|
|
236
|
+
return this.executeWithRetry(url, rawBody);
|
|
237
|
+
}
|
|
238
|
+
async executeWithRetry(url, rawBody) {
|
|
239
|
+
const { maxAttempts, sleep } = this.config.retry;
|
|
240
|
+
let lastError;
|
|
241
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
242
|
+
const delayMs = backoffDelayMs(attempt, this.config.retry);
|
|
243
|
+
if (delayMs > 0) await sleep(delayMs);
|
|
244
|
+
try {
|
|
245
|
+
return await this.attemptOnce(url, rawBody);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (!(err instanceof AimearnError)) throw err;
|
|
248
|
+
lastError = err;
|
|
249
|
+
if (!isRetriable(err.status)) {
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
throw lastError ?? new AimearnPlatformError(
|
|
255
|
+
"internal_error",
|
|
256
|
+
"AimearnClient retry budget exhausted with no error captured.",
|
|
257
|
+
500
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
async attemptOnce(url, rawBody) {
|
|
261
|
+
const sigHeader = signRequest(this.config.secret, rawBody);
|
|
262
|
+
let response;
|
|
263
|
+
try {
|
|
264
|
+
response = await this.config.fetch(url, {
|
|
265
|
+
method: "POST",
|
|
266
|
+
headers: {
|
|
267
|
+
"Content-Type": "application/json",
|
|
268
|
+
"X-Aimearn-Key-Id": this.config.keyId,
|
|
269
|
+
"X-Aimearn-Signature": sigHeader
|
|
270
|
+
},
|
|
271
|
+
body: rawBody
|
|
272
|
+
});
|
|
273
|
+
} catch (err) {
|
|
274
|
+
throw new AimearnPlatformError(
|
|
275
|
+
"network_error",
|
|
276
|
+
`Network error during webhook POST: ${err.message ?? err}`,
|
|
277
|
+
0
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const responseBody = await response.text();
|
|
281
|
+
if (response.status >= 200 && response.status < 300) {
|
|
282
|
+
const sigOk = verifyResponseSignature(
|
|
283
|
+
this.config.secret,
|
|
284
|
+
response.headers.get("x-aimearn-response-signature"),
|
|
285
|
+
responseBody
|
|
286
|
+
);
|
|
287
|
+
if (!sigOk) {
|
|
288
|
+
throw new AimearnPlatformError(
|
|
289
|
+
"response_signature_failed",
|
|
290
|
+
`Aim Earn response signature missing or invalid (HTTP ${response.status}).`,
|
|
291
|
+
response.status,
|
|
292
|
+
responseBody
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
return JSON.parse(responseBody);
|
|
297
|
+
} catch {
|
|
298
|
+
throw new AimearnPlatformError(
|
|
299
|
+
"internal_error",
|
|
300
|
+
"Aim Earn returned 2xx with an unparseable JSON body.",
|
|
301
|
+
response.status,
|
|
302
|
+
responseBody
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
let parsed = responseBody;
|
|
307
|
+
try {
|
|
308
|
+
parsed = JSON.parse(responseBody);
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
throw errorFromResponse(response.status, parsed);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/index.ts
|
|
316
|
+
var SDK_VERSION = "0.1.0";
|
|
317
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
318
|
+
0 && (module.exports = {
|
|
319
|
+
AimearnAuthError,
|
|
320
|
+
AimearnClient,
|
|
321
|
+
AimearnConflictError,
|
|
322
|
+
AimearnError,
|
|
323
|
+
AimearnForbiddenError,
|
|
324
|
+
AimearnNotFoundError,
|
|
325
|
+
AimearnPlatformError,
|
|
326
|
+
AimearnValidationError,
|
|
327
|
+
REPLAY_WINDOW_SECONDS,
|
|
328
|
+
SDK_VERSION,
|
|
329
|
+
backoffDelayMs,
|
|
330
|
+
errorFromResponse,
|
|
331
|
+
generateEventId,
|
|
332
|
+
isRetriable,
|
|
333
|
+
parseSignatureHeader,
|
|
334
|
+
resolveRetryConfig,
|
|
335
|
+
signRequest,
|
|
336
|
+
verifyResponseSignature
|
|
337
|
+
});
|
|
338
|
+
//# sourceMappingURL=index.cjs.map
|