puntersedge 0.1.0 → 1.0.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/LICENSE +21 -0
- package/README.md +283 -37
- package/dist/cjs/client.js +487 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/credits.js +82 -0
- package/dist/cjs/credits.js.map +1 -0
- package/dist/cjs/errors.js +130 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/http.js +218 -0
- package/dist/cjs/http.js.map +1 -0
- package/dist/cjs/index.js +58 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types.generated.js +13 -0
- package/dist/cjs/types.generated.js.map +1 -0
- package/dist/cjs/webhook.js +95 -0
- package/dist/cjs/webhook.js.map +1 -0
- package/dist/esm/client.d.ts +480 -0
- package/dist/esm/client.d.ts.map +1 -0
- package/dist/esm/client.js +483 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/credits.d.ts +78 -0
- package/dist/esm/credits.d.ts.map +1 -0
- package/dist/esm/credits.js +78 -0
- package/dist/esm/credits.js.map +1 -0
- package/dist/esm/errors.d.ts +132 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +117 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/http.d.ts +52 -0
- package/dist/esm/http.d.ts.map +1 -0
- package/dist/esm/http.js +214 -0
- package/dist/esm/http.js.map +1 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/types.generated.d.ts +1453 -0
- package/dist/esm/types.generated.d.ts.map +1 -0
- package/dist/esm/types.generated.js +12 -0
- package/dist/esm/types.generated.js.map +1 -0
- package/dist/esm/webhook.d.ts +56 -0
- package/dist/esm/webhook.d.ts.map +1 -0
- package/dist/esm/webhook.js +92 -0
- package/dist/esm/webhook.js.map +1 -0
- package/package.json +52 -19
- package/src/client.ts +799 -0
- package/src/credits.ts +131 -0
- package/src/errors.ts +166 -0
- package/src/http.ts +268 -0
- package/src/index.ts +68 -206
- package/src/types.generated.ts +1514 -0
- package/src/webhook.ts +113 -0
- package/dist/index.d.ts +0 -117
- package/dist/index.js +0 -99
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Typed errors for the PuntersEdge client.
|
|
4
|
+
*
|
|
5
|
+
* WHY A CLASS PER STATUS. Odds code has to branch on *why* a call failed, and it has to do it
|
|
6
|
+
* without parsing an error string. A 402 means buy more credits; a 429 means wait and try the
|
|
7
|
+
* same call again; a 422 means the request itself is wrong and retrying will fail identically
|
|
8
|
+
* forever. Collapsing those into one `Error` pushes the branch into a regex over `.message`,
|
|
9
|
+
* which is how a "rate limited, back off" path silently starts swallowing "sport_key does not
|
|
10
|
+
* exist".
|
|
11
|
+
*
|
|
12
|
+
* Every error carries the RFC 7807 `Problem` body the API returned, when it returned one, so
|
|
13
|
+
* `err.problem.type` is a stable URI you can switch on and `err.problem.detail` is the
|
|
14
|
+
* explanation. `instanceof PuntersEdgeError` catches everything from this SDK.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.APIError = exports.NetworkError = exports.ServerError = exports.NotFoundError = exports.ValidationError = exports.RateLimitError = exports.CreditsExhaustedError = exports.AuthError = exports.PuntersEdgeError = void 0;
|
|
18
|
+
exports.isProblem = isProblem;
|
|
19
|
+
/** Base class — `catch (e) { if (e instanceof PuntersEdgeError) ... }` catches every SDK error. */
|
|
20
|
+
class PuntersEdgeError extends Error {
|
|
21
|
+
/** HTTP status, or undefined when the request never got a response. */
|
|
22
|
+
status;
|
|
23
|
+
/** The RFC 7807 body, when the API supplied one. */
|
|
24
|
+
problem;
|
|
25
|
+
/** Raw response body (truncated to 2KB), for errors that are not a Problem document. */
|
|
26
|
+
body;
|
|
27
|
+
/** API path that produced this error. */
|
|
28
|
+
path;
|
|
29
|
+
/** Response headers, when there was a response. */
|
|
30
|
+
headers;
|
|
31
|
+
constructor(message, init = {}) {
|
|
32
|
+
super(message, init.cause === undefined ? undefined : { cause: init.cause });
|
|
33
|
+
this.name = new.target.name;
|
|
34
|
+
this.status = init.status;
|
|
35
|
+
this.problem = init.problem;
|
|
36
|
+
this.body = init.body;
|
|
37
|
+
this.path = init.path;
|
|
38
|
+
this.headers = init.headers;
|
|
39
|
+
// Restores `instanceof` when the package is compiled down to ES5 by a consumer's bundler.
|
|
40
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.PuntersEdgeError = PuntersEdgeError;
|
|
44
|
+
/**
|
|
45
|
+
* 401 or 403. The key is missing, wrong, inactive, IP-blocked, or on a plan that does not
|
|
46
|
+
* include this endpoint — the closing-line archive and webhook creation need Standard or above.
|
|
47
|
+
* Retrying will not help; read `problem.detail`.
|
|
48
|
+
*/
|
|
49
|
+
class AuthError extends PuntersEdgeError {
|
|
50
|
+
}
|
|
51
|
+
exports.AuthError = AuthError;
|
|
52
|
+
/**
|
|
53
|
+
* 402. The monthly credit allowance is spent.
|
|
54
|
+
*
|
|
55
|
+
* This is NOT a rate limit, and backing off does not clear it — the balance resets on the
|
|
56
|
+
* billing date. `upgrade` carries the API's own recommendation: which plan, what it costs, the
|
|
57
|
+
* link to send a human to, and the exact request to POST to buy it without leaving the shell.
|
|
58
|
+
*
|
|
59
|
+
* Check `upgrade.covers`. `"partial"` means the recommended plan is the largest self-serve tier
|
|
60
|
+
* and is *still* below the measured need, so buying it will not stop the 402s.
|
|
61
|
+
*/
|
|
62
|
+
class CreditsExhaustedError extends PuntersEdgeError {
|
|
63
|
+
/** The API's upgrade recommendation, from the `X-Upgrade-*` headers. */
|
|
64
|
+
upgrade;
|
|
65
|
+
constructor(message, init = {}) {
|
|
66
|
+
super(message, init);
|
|
67
|
+
this.upgrade = init.upgrade;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.CreditsExhaustedError = CreditsExhaustedError;
|
|
71
|
+
/**
|
|
72
|
+
* 429. Too many requests per minute for this key's plan. `retryAfter` is the server's own
|
|
73
|
+
* `Retry-After` in seconds — wait that long and repeat the identical call. This SDK never waits
|
|
74
|
+
* for you; see the note on retries in the README.
|
|
75
|
+
*
|
|
76
|
+
* A 429 is not billed, so nothing was spent.
|
|
77
|
+
*/
|
|
78
|
+
class RateLimitError extends PuntersEdgeError {
|
|
79
|
+
/** Seconds to wait, from `Retry-After`. The API sends 60 on a per-minute limit. */
|
|
80
|
+
retryAfter;
|
|
81
|
+
/** Requests-per-minute ceiling for this plan, from `X-RateLimit-Limit`. */
|
|
82
|
+
limit;
|
|
83
|
+
constructor(message, init = {}) {
|
|
84
|
+
super(message, init);
|
|
85
|
+
this.retryAfter = init.retryAfter;
|
|
86
|
+
this.limit = init.limit;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.RateLimitError = RateLimitError;
|
|
90
|
+
/**
|
|
91
|
+
* 422. The request is malformed — an unknown `sport_key`, an unrecognised bookmaker key, a
|
|
92
|
+
* `direction` other than firming/drifting. These are refused **before billing**, so a 422 costs
|
|
93
|
+
* nothing; `fields` lists what the server objected to.
|
|
94
|
+
*/
|
|
95
|
+
class ValidationError extends PuntersEdgeError {
|
|
96
|
+
/** Field-level complaints, when the 422 body carried them. */
|
|
97
|
+
fields;
|
|
98
|
+
constructor(message, init = {}) {
|
|
99
|
+
super(message, init);
|
|
100
|
+
this.fields = init.fields ?? [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.ValidationError = ValidationError;
|
|
104
|
+
/** 404. No such race, horse, webhook or route. */
|
|
105
|
+
class NotFoundError extends PuntersEdgeError {
|
|
106
|
+
}
|
|
107
|
+
exports.NotFoundError = NotFoundError;
|
|
108
|
+
/** Any 5xx. The request may be fine; the API or an upstream is not. Safe to retry with backoff. */
|
|
109
|
+
class ServerError extends PuntersEdgeError {
|
|
110
|
+
}
|
|
111
|
+
exports.ServerError = ServerError;
|
|
112
|
+
/**
|
|
113
|
+
* The request never completed: DNS failure, connection reset, TLS error, or the timeout /
|
|
114
|
+
* `AbortSignal` fired. `cause` holds the underlying `fetch` rejection.
|
|
115
|
+
*/
|
|
116
|
+
class NetworkError extends PuntersEdgeError {
|
|
117
|
+
}
|
|
118
|
+
exports.NetworkError = NetworkError;
|
|
119
|
+
/** A 4xx this SDK has no dedicated class for. */
|
|
120
|
+
class APIError extends PuntersEdgeError {
|
|
121
|
+
}
|
|
122
|
+
exports.APIError = APIError;
|
|
123
|
+
/** True when `value` looks like an RFC 7807 document rather than some other JSON body. */
|
|
124
|
+
function isProblem(value) {
|
|
125
|
+
return (typeof value === "object" && value !== null &&
|
|
126
|
+
typeof value.type === "string" &&
|
|
127
|
+
typeof value.title === "string" &&
|
|
128
|
+
typeof value.status === "number");
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/http.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The transport: one `fetch`, one place where a non-2xx becomes a typed error.
|
|
4
|
+
*
|
|
5
|
+
* Runs anywhere `fetch`, `AbortController` and `Headers` exist — Node 18+, Deno, Bun,
|
|
6
|
+
* Cloudflare Workers, Vercel Edge, and browsers. There are no dependencies and nothing is
|
|
7
|
+
* imported from `node:*`, so a bundler targeting the edge does not have to polyfill anything.
|
|
8
|
+
*
|
|
9
|
+
* NOTHING IS RETRIED HERE, on purpose. Odds are time-sensitive. A transparent retry inside the
|
|
10
|
+
* client can hand you a price recorded before a move you would have seen, and it can double a
|
|
11
|
+
* billed call without telling you. Retry deliberately, at the layer that knows whether a stale
|
|
12
|
+
* answer is acceptable — `RateLimitError.retryAfter` gives you the wait the server asked for.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.buildQuery = buildQuery;
|
|
16
|
+
exports.request = request;
|
|
17
|
+
const errors_js_1 = require("./errors.js");
|
|
18
|
+
const credits_js_1 = require("./credits.js");
|
|
19
|
+
/**
|
|
20
|
+
* Serialise query parameters.
|
|
21
|
+
*
|
|
22
|
+
* Booleans go over the wire as `true`/`false`. Arrays are joined with commas rather than
|
|
23
|
+
* repeated, because that is what this API's `categories`, `bookmakers`, `country` and `venue`
|
|
24
|
+
* parameters expect — `?categories=horse&categories=harness` is not the same request as
|
|
25
|
+
* `?categories=horse,harness` and only the second one works. `null` and `undefined` are dropped,
|
|
26
|
+
* so an unset option is an absent parameter rather than the string "undefined".
|
|
27
|
+
*/
|
|
28
|
+
function buildQuery(params) {
|
|
29
|
+
if (!params)
|
|
30
|
+
return "";
|
|
31
|
+
const usp = new URLSearchParams();
|
|
32
|
+
for (const [key, value] of Object.entries(params)) {
|
|
33
|
+
if (value === null || value === undefined)
|
|
34
|
+
continue;
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
if (value.length === 0)
|
|
37
|
+
continue;
|
|
38
|
+
usp.set(key, value.join(","));
|
|
39
|
+
}
|
|
40
|
+
else if (typeof value === "boolean") {
|
|
41
|
+
usp.set(key, value ? "true" : "false");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
usp.set(key, String(value));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const qs = usp.toString();
|
|
48
|
+
return qs ? "?" + qs : "";
|
|
49
|
+
}
|
|
50
|
+
/** Read a body as JSON, falling back to text. Never throws. */
|
|
51
|
+
async function readBody(res) {
|
|
52
|
+
const raw = await res.text().catch(() => "");
|
|
53
|
+
if (!raw)
|
|
54
|
+
return {};
|
|
55
|
+
try {
|
|
56
|
+
return { json: JSON.parse(raw), text: raw };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { text: raw };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function fieldErrors(problem) {
|
|
63
|
+
const detail = problem?.detail;
|
|
64
|
+
if (!Array.isArray(detail))
|
|
65
|
+
return [];
|
|
66
|
+
return detail.filter((d) => typeof d === "object" && d !== null && Array.isArray(d.loc));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Render `problem.detail` into one line.
|
|
70
|
+
*
|
|
71
|
+
* The API types `detail` three ways deliberately: a string for most errors, an ARRAY of field
|
|
72
|
+
* errors on a 422, and an OBJECT on a 429. Only handling the string case leaves the two most
|
|
73
|
+
* actionable errors reading `422 Validation Error` — a message that tells a developer nothing
|
|
74
|
+
* about which parameter they got wrong.
|
|
75
|
+
*/
|
|
76
|
+
function renderDetail(detail) {
|
|
77
|
+
if (detail === undefined || detail === null)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (typeof detail === "string")
|
|
80
|
+
return detail;
|
|
81
|
+
if (Array.isArray(detail)) {
|
|
82
|
+
const parts = detail.map((d) => {
|
|
83
|
+
if (typeof d === "object" && d !== null && Array.isArray(d.loc)) {
|
|
84
|
+
const fe = d;
|
|
85
|
+
// `["query", "direction"]` reads better as `direction` than as the full path.
|
|
86
|
+
const field = fe.loc.filter((p) => p !== "body" && p !== "query" && p !== "path").join(".");
|
|
87
|
+
return field ? `${field}: ${fe.msg}` : fe.msg;
|
|
88
|
+
}
|
|
89
|
+
return typeof d === "string" ? d : JSON.stringify(d);
|
|
90
|
+
});
|
|
91
|
+
return parts.length ? parts.join("; ") : undefined;
|
|
92
|
+
}
|
|
93
|
+
if (typeof detail === "object") {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.stringify(detail);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return String(detail);
|
|
102
|
+
}
|
|
103
|
+
/** Best available human-readable message: the Problem's own words, else the raw body, else the status. */
|
|
104
|
+
function messageFor(res, problem, text) {
|
|
105
|
+
if (problem) {
|
|
106
|
+
const detail = renderDetail(problem.detail);
|
|
107
|
+
return detail && detail !== problem.title
|
|
108
|
+
? `${res.status} ${problem.title}: ${detail}`
|
|
109
|
+
: `${res.status} ${problem.title}`;
|
|
110
|
+
}
|
|
111
|
+
const snippet = (text ?? "").trim().slice(0, 300);
|
|
112
|
+
return snippet ? `HTTP ${res.status}: ${snippet}` : `HTTP ${res.status} ${res.statusText}`;
|
|
113
|
+
}
|
|
114
|
+
function toError(res, path, body) {
|
|
115
|
+
const problem = (0, errors_js_1.isProblem)(body.json) ? body.json : undefined;
|
|
116
|
+
const init = {
|
|
117
|
+
status: res.status,
|
|
118
|
+
problem,
|
|
119
|
+
body: body.text?.slice(0, 2048),
|
|
120
|
+
path,
|
|
121
|
+
headers: res.headers,
|
|
122
|
+
};
|
|
123
|
+
const message = messageFor(res, problem, body.text);
|
|
124
|
+
switch (res.status) {
|
|
125
|
+
case 401:
|
|
126
|
+
case 403:
|
|
127
|
+
return new errors_js_1.AuthError(message, init);
|
|
128
|
+
case 402:
|
|
129
|
+
return new errors_js_1.CreditsExhaustedError(message, { ...init, upgrade: (0, credits_js_1.parseUpgrade)(res.headers) });
|
|
130
|
+
case 404:
|
|
131
|
+
return new errors_js_1.NotFoundError(message, init);
|
|
132
|
+
case 422:
|
|
133
|
+
return new errors_js_1.ValidationError(message, { ...init, fields: fieldErrors(problem) });
|
|
134
|
+
case 429: {
|
|
135
|
+
const retry = Number(res.headers.get("Retry-After"));
|
|
136
|
+
const limit = Number(res.headers.get("X-RateLimit-Limit"));
|
|
137
|
+
return new errors_js_1.RateLimitError(message, {
|
|
138
|
+
...init,
|
|
139
|
+
retryAfter: Number.isFinite(retry) ? retry : undefined,
|
|
140
|
+
limit: Number.isFinite(limit) ? limit : undefined,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
default:
|
|
144
|
+
return res.status >= 500 ? new errors_js_1.ServerError(message, init) : new errors_js_1.APIError(message, init);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Compose the caller's signal with a timeout.
|
|
149
|
+
*
|
|
150
|
+
* `AbortSignal.any` landed in Node 20 and is still missing in enough runtimes that relying on it
|
|
151
|
+
* would narrow where this package works, so the fallback wires the listener by hand.
|
|
152
|
+
*/
|
|
153
|
+
function withTimeout(signal, ms) {
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
const timer = setTimeout(() => controller.abort(new Error(`timed out after ${ms}ms`)), ms);
|
|
156
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
157
|
+
if (signal) {
|
|
158
|
+
if (signal.aborted)
|
|
159
|
+
onAbort();
|
|
160
|
+
else
|
|
161
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
signal: controller.signal,
|
|
165
|
+
done: () => {
|
|
166
|
+
clearTimeout(timer);
|
|
167
|
+
signal?.removeEventListener("abort", onAbort);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async function request(config, method, path, params, jsonBody, options = {}) {
|
|
172
|
+
const url = config.baseUrl + path + buildQuery(params);
|
|
173
|
+
const headers = {
|
|
174
|
+
Accept: "application/json",
|
|
175
|
+
"User-Agent": config.userAgent,
|
|
176
|
+
...options.headers,
|
|
177
|
+
};
|
|
178
|
+
if (config.apiKey)
|
|
179
|
+
headers["X-API-Key"] = config.apiKey;
|
|
180
|
+
if (jsonBody !== undefined)
|
|
181
|
+
headers["Content-Type"] = "application/json";
|
|
182
|
+
const { signal, done } = withTimeout(options.signal, options.timeoutMs ?? config.timeoutMs);
|
|
183
|
+
let res;
|
|
184
|
+
try {
|
|
185
|
+
res = await config.fetch(url, {
|
|
186
|
+
method,
|
|
187
|
+
headers,
|
|
188
|
+
body: jsonBody === undefined ? undefined : JSON.stringify(jsonBody),
|
|
189
|
+
signal,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch (cause) {
|
|
193
|
+
throw new errors_js_1.NetworkError(`${method} ${path} failed: ${cause instanceof Error ? cause.message : String(cause)}`, { path, cause });
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
done();
|
|
197
|
+
}
|
|
198
|
+
const credits = (0, credits_js_1.parseCredits)(res.headers);
|
|
199
|
+
// Reported even on an error: a 402 still tells you the balance, and that is the response
|
|
200
|
+
// where a caller most needs it.
|
|
201
|
+
if (credits && config.onCredits)
|
|
202
|
+
config.onCredits(credits);
|
|
203
|
+
if (!res.ok)
|
|
204
|
+
throw toError(res, path, await readBody(res));
|
|
205
|
+
// 204 and an empty 200 (DELETE /v1/webhooks/{id}) have no body to parse.
|
|
206
|
+
if (res.status === 204)
|
|
207
|
+
return { data: undefined, credits, headers: res.headers };
|
|
208
|
+
const body = await readBody(res);
|
|
209
|
+
if (body.text === undefined || body.text === "") {
|
|
210
|
+
return { data: undefined, credits, headers: res.headers };
|
|
211
|
+
}
|
|
212
|
+
if (!("json" in body)) {
|
|
213
|
+
// A 2xx that is not JSON: the CSV format on the archive endpoints comes back this way.
|
|
214
|
+
return { data: body.text, credits, headers: res.headers };
|
|
215
|
+
}
|
|
216
|
+
return { data: body.json, credits, headers: res.headers };
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAiDH,gCAgBC;AAqID,0BA0DC;AA9PD,2CAIqB;AACrB,6CAAwE;AAiCxE;;;;;;;;GAQG;AACH,SAAgB,UAAU,CAAC,MAAyB;IAClD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,IAAI,eAAe,EAAE,CAAC;IAClC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QACpD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YACjC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YACtC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC1B,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5B,CAAC;AAED,+DAA+D;AAC/D,KAAK,UAAU,QAAQ,CAAC,GAAa;IACnC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,OAA4B;IAC/C,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IACtC,OAAO,MAAM,CAAC,MAAM,CAClB,CAAC,CAAC,EAAmB,EAAE,CACrB,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAE,CAAgB,CAAC,GAAG,CAAC,CAC9E,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,MAAe;IACnC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC9D,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAE9C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC7B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAE,CAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChF,MAAM,EAAE,GAAG,CAAe,CAAC;gBAC3B,8EAA8E;gBAC9E,MAAM,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC5F,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC;YAChD,CAAC;YACD,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED,0GAA0G;AAC1G,SAAS,UAAU,CAAC,GAAa,EAAE,OAA4B,EAAE,IAAwB;IACvF,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5C,OAAO,MAAM,IAAI,MAAM,KAAK,OAAO,CAAC,KAAK;YACvC,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM,EAAE;YAC7C,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IACvC,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;AAC7F,CAAC;AAED,SAAS,OAAO,CAAC,GAAa,EAAE,IAAY,EAAE,IAAuC;IACnF,MAAM,OAAO,GAAG,IAAA,qBAAS,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,MAAM,IAAI,GAAG;QACX,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;QAC/B,IAAI;QACJ,OAAO,EAAE,GAAG,CAAC,OAAO;KACrB,CAAC;IACF,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEpD,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,GAAG,CAAC;QACT,KAAK,GAAG;YACN,OAAO,IAAI,qBAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACtC,KAAK,GAAG;YACN,OAAO,IAAI,iCAAqB,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAA,yBAAY,EAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7F,KAAK,GAAG;YACN,OAAO,IAAI,yBAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC1C,KAAK,GAAG;YACN,OAAO,IAAI,2BAAe,CAAC,OAAO,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjF,KAAK,GAAG,CAAC,CAAC,CAAC;YACT,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;YACrD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;YAC3D,OAAO,IAAI,0BAAc,CAAC,OAAO,EAAE;gBACjC,GAAG,IAAI;gBACP,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;gBACtD,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;aAClD,CAAC,CAAC;QACL,CAAC;QACD;YACE,OAAO,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,uBAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,oBAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAAC,MAA+B,EAAE,EAAU;IAI9D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,EAC5D,EAAE,CACH,CAAC;IACF,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;;YACzB,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,OAAO;QACL,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,IAAI,EAAE,GAAG,EAAE;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAChD,CAAC;KACF,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,OAAO,CAC3B,MAAuB,EACvB,MAAiC,EACjC,IAAY,EACZ,MAAc,EACd,QAAkB,EAClB,UAA0B,EAAE;IAE5B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAEvD,MAAM,OAAO,GAA2B;QACtC,MAAM,EAAE,kBAAkB;QAC1B,YAAY,EAAE,MAAM,CAAC,SAAS;QAC9B,GAAG,OAAO,CAAC,OAAO;KACnB,CAAC;IACF,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACxD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAEzE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,WAAW,CAClC,OAAO,CAAC,MAAM,EACd,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,CACtC,CAAC;IAEF,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;YAC5B,MAAM;YACN,OAAO;YACP,IAAI,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;YACnE,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,wBAAY,CACpB,GAAG,MAAM,IAAI,IAAI,YAAY,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACrF,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,CAAC;IACT,CAAC;IAED,MAAM,OAAO,GAAG,IAAA,yBAAY,EAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1C,yFAAyF;IACzF,gCAAgC;IAChC,IAAI,OAAO,IAAI,MAAM,CAAC,SAAS;QAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAE3D,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAE3D,yEAAyE;IACzE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,SAAc,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;IACvF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QAChD,OAAO,EAAE,IAAI,EAAE,SAAc,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,CAAC;QACtB,uFAAuF;QACvF,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAS,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* puntersedge — the official TypeScript/JavaScript client for the PuntersEdge Odds API.
|
|
4
|
+
*
|
|
5
|
+
* Australian and New Zealand racing (thoroughbred, greyhound, harness) and Australian sports
|
|
6
|
+
* odds from 14 Australian bookmakers. One REST/JSON API, an `X-API-Key` header, a free tier.
|
|
7
|
+
*
|
|
8
|
+
* npm install puntersedge
|
|
9
|
+
*
|
|
10
|
+
* import { PuntersEdge } from "puntersedge";
|
|
11
|
+
* const pe = new PuntersEdge({ apiKey: process.env.PUNTERSEDGE_API_KEY });
|
|
12
|
+
* const races = await pe.racing.nextToGo({ numRaces: 5 });
|
|
13
|
+
*
|
|
14
|
+
* Get a free key at https://puntersedge.online/api.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
28
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
29
|
+
};
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.default = exports.verifyWebhookSignature = exports.buildQuery = exports.parseUpgrade = exports.parseCredits = exports.isProblem = exports.APIError = exports.NetworkError = exports.ServerError = exports.NotFoundError = exports.ValidationError = exports.RateLimitError = exports.CreditsExhaustedError = exports.AuthError = exports.PuntersEdgeError = exports.VERSION = exports.DEFAULT_BASE_URL = exports.PuntersEdge = void 0;
|
|
32
|
+
var client_js_1 = require("./client.js");
|
|
33
|
+
Object.defineProperty(exports, "PuntersEdge", { enumerable: true, get: function () { return client_js_1.PuntersEdge; } });
|
|
34
|
+
Object.defineProperty(exports, "DEFAULT_BASE_URL", { enumerable: true, get: function () { return client_js_1.DEFAULT_BASE_URL; } });
|
|
35
|
+
Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return client_js_1.VERSION; } });
|
|
36
|
+
var errors_js_1 = require("./errors.js");
|
|
37
|
+
Object.defineProperty(exports, "PuntersEdgeError", { enumerable: true, get: function () { return errors_js_1.PuntersEdgeError; } });
|
|
38
|
+
Object.defineProperty(exports, "AuthError", { enumerable: true, get: function () { return errors_js_1.AuthError; } });
|
|
39
|
+
Object.defineProperty(exports, "CreditsExhaustedError", { enumerable: true, get: function () { return errors_js_1.CreditsExhaustedError; } });
|
|
40
|
+
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_js_1.RateLimitError; } });
|
|
41
|
+
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_js_1.ValidationError; } });
|
|
42
|
+
Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_js_1.NotFoundError; } });
|
|
43
|
+
Object.defineProperty(exports, "ServerError", { enumerable: true, get: function () { return errors_js_1.ServerError; } });
|
|
44
|
+
Object.defineProperty(exports, "NetworkError", { enumerable: true, get: function () { return errors_js_1.NetworkError; } });
|
|
45
|
+
Object.defineProperty(exports, "APIError", { enumerable: true, get: function () { return errors_js_1.APIError; } });
|
|
46
|
+
Object.defineProperty(exports, "isProblem", { enumerable: true, get: function () { return errors_js_1.isProblem; } });
|
|
47
|
+
var credits_js_1 = require("./credits.js");
|
|
48
|
+
Object.defineProperty(exports, "parseCredits", { enumerable: true, get: function () { return credits_js_1.parseCredits; } });
|
|
49
|
+
Object.defineProperty(exports, "parseUpgrade", { enumerable: true, get: function () { return credits_js_1.parseUpgrade; } });
|
|
50
|
+
var http_js_1 = require("./http.js");
|
|
51
|
+
Object.defineProperty(exports, "buildQuery", { enumerable: true, get: function () { return http_js_1.buildQuery; } });
|
|
52
|
+
var webhook_js_1 = require("./webhook.js");
|
|
53
|
+
Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhook_js_1.verifyWebhookSignature; } });
|
|
54
|
+
// Every response shape, generated from the live OpenAPI document.
|
|
55
|
+
__exportStar(require("./types.generated.js"), exports);
|
|
56
|
+
var client_js_2 = require("./client.js");
|
|
57
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return client_js_2.PuntersEdge; } });
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;;AAEH,yCAwBqB;AAvBnB,wGAAA,WAAW,OAAA;AACX,6GAAA,gBAAgB,OAAA;AAChB,oGAAA,OAAO,OAAA;AAuBT,yCAaqB;AAZnB,6GAAA,gBAAgB,OAAA;AAChB,sGAAA,SAAS,OAAA;AACT,kHAAA,qBAAqB,OAAA;AACrB,2GAAA,cAAc,OAAA;AACd,4GAAA,eAAe,OAAA;AACf,0GAAA,aAAa,OAAA;AACb,wGAAA,WAAW,OAAA;AACX,yGAAA,YAAY,OAAA;AACZ,qGAAA,QAAQ,OAAA;AACR,sGAAA,SAAS,OAAA;AAKX,2CAKsB;AAJpB,0GAAA,YAAY,OAAA;AACZ,0GAAA,YAAY,OAAA;AAKd,qCAAsG;AAA7F,qGAAA,UAAU,OAAA;AAEnB,2CAA0E;AAAjE,oHAAA,sBAAsB,OAAA;AAE/B,kEAAkE;AAClE,uDAAqC;AAErC,yCAAqD;AAA5C,oGAAA,WAAW,OAAW"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// GENERATED FILE — do not edit by hand.
|
|
3
|
+
//
|
|
4
|
+
// Source: openapi.json (API version 1.0.0)
|
|
5
|
+
// Rebuild: python3 scripts/generate-types.py
|
|
6
|
+
//
|
|
7
|
+
// Every interface below mirrors one `components.schemas` entry of the PuntersEdge
|
|
8
|
+
// OpenAPI document. A field marked optional here is one the API declares optional:
|
|
9
|
+
// racing data is scraped from bookmakers that each publish a different subset, so
|
|
10
|
+
// `barrier?: number` means some books genuinely do not report a barrier, not that
|
|
11
|
+
// the schema is sloppy. Check before you read.
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
//# sourceMappingURL=types.generated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.generated.js","sourceRoot":"","sources":["../../src/types.generated.ts"],"names":[],"mappings":";AAAA,wCAAwC;AACxC,EAAE;AACF,4CAA4C;AAC5C,6CAA6C;AAC7C,EAAE;AACF,kFAAkF;AAClF,mFAAmF;AACnF,kFAAkF;AAClF,kFAAkF;AAClF,+CAA+C"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Verify the signature on a delivered webhook.
|
|
4
|
+
*
|
|
5
|
+
* Every delivery carries `X-Webhook-Signature: sha256=<hex>`, an HMAC-SHA256 over the **exact
|
|
6
|
+
* bytes of the request body**, keyed with the secret the API returned when you created the
|
|
7
|
+
* subscription.
|
|
8
|
+
*
|
|
9
|
+
* THE MISTAKE THIS FUNCTION EXISTS TO PREVENT. Most web frameworks parse JSON for you, so the
|
|
10
|
+
* obvious implementation is `JSON.stringify(req.body)` and hash that. It will never match. The
|
|
11
|
+
* API signs a canonical serialisation — keys sorted, no spaces — and your framework's
|
|
12
|
+
* re-serialisation will differ in key order or whitespace. Hash the raw body you received,
|
|
13
|
+
* before anything parses it:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* // Express
|
|
17
|
+
* app.post("/hook", express.raw({ type: "application/json" }), async (req, res) => {
|
|
18
|
+
* const ok = await verifyWebhookSignature({
|
|
19
|
+
* body: req.body, // a Buffer, unparsed
|
|
20
|
+
* signature: req.header("X-Webhook-Signature"),
|
|
21
|
+
* secret: process.env.PUNTERSEDGE_WEBHOOK_SECRET!,
|
|
22
|
+
* });
|
|
23
|
+
* if (!ok) return res.sendStatus(401);
|
|
24
|
+
* const event = JSON.parse(req.body.toString("utf8"));
|
|
25
|
+
* res.sendStatus(200); // ack fast, work later
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* // Cloudflare Workers / Hono / any Fetch-API runtime
|
|
31
|
+
* const raw = await request.text();
|
|
32
|
+
* const ok = await verifyWebhookSignature({
|
|
33
|
+
* body: raw, signature: request.headers.get("X-Webhook-Signature"), secret: SECRET,
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Uses WebCrypto, so it runs unchanged on Node 18+, Deno, Bun, Cloudflare Workers and in the
|
|
38
|
+
* browser, with no dependency on `node:crypto`.
|
|
39
|
+
*/
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
42
|
+
function toBytes(body) {
|
|
43
|
+
if (typeof body === "string")
|
|
44
|
+
return new TextEncoder().encode(body);
|
|
45
|
+
if (body instanceof Uint8Array)
|
|
46
|
+
return body;
|
|
47
|
+
return new Uint8Array(body);
|
|
48
|
+
}
|
|
49
|
+
function hex(buffer) {
|
|
50
|
+
const bytes = new Uint8Array(buffer);
|
|
51
|
+
let out = "";
|
|
52
|
+
for (let i = 0; i < bytes.length; i++)
|
|
53
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Constant-time string comparison.
|
|
58
|
+
*
|
|
59
|
+
* `a === b` on hex digests leaks, through timing, how many leading characters an attacker got
|
|
60
|
+
* right, which turns forging a signature into 64 cheap guesses instead of one impossible one.
|
|
61
|
+
* Both strings are lower-cased by the caller, and length is folded into the result rather than
|
|
62
|
+
* short-circuiting on it.
|
|
63
|
+
*/
|
|
64
|
+
function timingSafeEqual(a, b) {
|
|
65
|
+
let diff = a.length ^ b.length;
|
|
66
|
+
const n = Math.max(a.length, b.length);
|
|
67
|
+
for (let i = 0; i < n; i++) {
|
|
68
|
+
diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
69
|
+
}
|
|
70
|
+
return diff === 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* True when `signature` is a valid HMAC-SHA256 of `body` under `secret`.
|
|
74
|
+
*
|
|
75
|
+
* Returns false — never throws — for a missing, malformed or wrong signature, so a bad delivery
|
|
76
|
+
* is a 401 rather than a 500 in your logs.
|
|
77
|
+
*/
|
|
78
|
+
async function verifyWebhookSignature(options) {
|
|
79
|
+
const { body, signature, secret } = options;
|
|
80
|
+
if (!signature || !secret)
|
|
81
|
+
return false;
|
|
82
|
+
const subtle = (options.crypto ?? globalThis.crypto)?.subtle;
|
|
83
|
+
if (!subtle) {
|
|
84
|
+
throw new Error("No WebCrypto available. Use Node 18 or later, or pass one: " +
|
|
85
|
+
"verifyWebhookSignature({ crypto, ... }).");
|
|
86
|
+
}
|
|
87
|
+
const provided = (signature.startsWith("sha256=") ? signature.slice(7) : signature)
|
|
88
|
+
.trim().toLowerCase();
|
|
89
|
+
if (!/^[0-9a-f]{64}$/.test(provided))
|
|
90
|
+
return false;
|
|
91
|
+
const key = await subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
92
|
+
const mac = await subtle.sign("HMAC", key, toBytes(body));
|
|
93
|
+
return timingSafeEqual(hex(mac), provided);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;;AAiDH,wDA0BC;AA9DD,SAAS,OAAO,CAAC,IAAuC;IACtD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,IAAI,YAAY,UAAU;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,GAAG,CAAC,MAAmB;IAC9B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACrF,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IAC3C,IAAI,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,sBAAsB,CAAC,OAAsB;IACjE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC5C,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAExC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAC7D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC7D,0CAA0C,CAC3C,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;SAChF,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAChC,KAAK,EACL,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAChC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,CAAiB,CAAC,CAAC;IAE1E,OAAO,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC7C,CAAC"}
|