@rerout/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 +22 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/index.cjs +369 -0
- package/dist/index.d.cts +309 -0
- package/dist/index.d.ts +309 -0
- package/dist/index.js +334 -0
- package/package.json +61 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types returned by the Rerout API. Fields mirror the server-side
|
|
3
|
+
* `LinkResponse` / `ProjectStatsResponse` etc. shapes so JSON is parsed
|
|
4
|
+
* without transformation.
|
|
5
|
+
*/
|
|
6
|
+
interface Link {
|
|
7
|
+
code: string;
|
|
8
|
+
short_url: string;
|
|
9
|
+
domain_hostname: string | null;
|
|
10
|
+
target_url: string;
|
|
11
|
+
project_id: string;
|
|
12
|
+
expires_at: number | null;
|
|
13
|
+
is_active: boolean;
|
|
14
|
+
seo_title: string | null;
|
|
15
|
+
seo_description: string | null;
|
|
16
|
+
seo_image_url: string | null;
|
|
17
|
+
seo_canonical_url: string | null;
|
|
18
|
+
seo_noindex: boolean;
|
|
19
|
+
seo_updated_at: number | null;
|
|
20
|
+
created_at: number;
|
|
21
|
+
updated_at: number;
|
|
22
|
+
}
|
|
23
|
+
interface CreateLinkInput {
|
|
24
|
+
target_url: string;
|
|
25
|
+
/** Verified custom domain to host this link on. Omit for `rerout.co/:code`. */
|
|
26
|
+
domain_hostname?: string;
|
|
27
|
+
/** Custom path. Only allowed with verified domain_hostname. */
|
|
28
|
+
code?: string;
|
|
29
|
+
/** Unix seconds. Omit for a permanent link. */
|
|
30
|
+
expires_at?: number;
|
|
31
|
+
/** Override social preview title. Max 90 chars. */
|
|
32
|
+
seo_title?: string | null;
|
|
33
|
+
/** Override social preview description. Max 220 chars. */
|
|
34
|
+
seo_description?: string | null;
|
|
35
|
+
/** Absolute https:// social preview image. */
|
|
36
|
+
seo_image_url?: string | null;
|
|
37
|
+
/** Canonical URL for the preview HTML. */
|
|
38
|
+
seo_canonical_url?: string | null;
|
|
39
|
+
/** Whether the preview page should be indexed. Default: false (noindex). */
|
|
40
|
+
seo_noindex?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface UpdateLinkInput {
|
|
43
|
+
target_url?: string;
|
|
44
|
+
expires_at?: number | null;
|
|
45
|
+
is_active?: boolean;
|
|
46
|
+
seo_title?: string | null;
|
|
47
|
+
seo_description?: string | null;
|
|
48
|
+
seo_image_url?: string | null;
|
|
49
|
+
seo_canonical_url?: string | null;
|
|
50
|
+
seo_noindex?: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface ListLinksParams {
|
|
53
|
+
/** Pagination cursor returned by a previous call. */
|
|
54
|
+
cursor?: number;
|
|
55
|
+
/** Page size. Server-side default + max apply. */
|
|
56
|
+
limit?: number;
|
|
57
|
+
}
|
|
58
|
+
interface ListLinksResult {
|
|
59
|
+
links: Link[];
|
|
60
|
+
next_cursor: number | null;
|
|
61
|
+
}
|
|
62
|
+
interface StatsBreakdown {
|
|
63
|
+
value: string;
|
|
64
|
+
clicks: number;
|
|
65
|
+
}
|
|
66
|
+
interface DailyClicksPoint {
|
|
67
|
+
day: number;
|
|
68
|
+
clicks: number;
|
|
69
|
+
qr_scans: number;
|
|
70
|
+
}
|
|
71
|
+
interface ProjectStats {
|
|
72
|
+
days: number;
|
|
73
|
+
total_clicks: number;
|
|
74
|
+
qr_scans: number;
|
|
75
|
+
daily: DailyClicksPoint[];
|
|
76
|
+
countries: StatsBreakdown[];
|
|
77
|
+
referrers: StatsBreakdown[];
|
|
78
|
+
devices: StatsBreakdown[];
|
|
79
|
+
browsers: StatsBreakdown[];
|
|
80
|
+
top_codes: StatsBreakdown[];
|
|
81
|
+
}
|
|
82
|
+
interface LinkStats {
|
|
83
|
+
code: string;
|
|
84
|
+
days: number;
|
|
85
|
+
total_clicks: number;
|
|
86
|
+
qr_scans: number;
|
|
87
|
+
countries: StatsBreakdown[];
|
|
88
|
+
referrers: StatsBreakdown[];
|
|
89
|
+
}
|
|
90
|
+
interface QrUrlOptions {
|
|
91
|
+
/** Module size in px. 1–32. Server default: 8. */
|
|
92
|
+
size?: number;
|
|
93
|
+
/** Quiet zone in modules. 0–16. Server default: 4. */
|
|
94
|
+
margin?: number;
|
|
95
|
+
/** Error correction level. */
|
|
96
|
+
ecc?: 'L' | 'M' | 'Q' | 'H';
|
|
97
|
+
/** Force the QR to encode a specific verified custom domain. */
|
|
98
|
+
domain?: string;
|
|
99
|
+
/** Cache-bust on regenerate. */
|
|
100
|
+
refresh?: string | true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Default production API URL. Override via `baseUrl` for staging / self-hosted. */
|
|
104
|
+
declare const DEFAULT_BASE_URL = "https://api.rerout.co";
|
|
105
|
+
interface ReroutClientOptions {
|
|
106
|
+
/** Project API key (`rrk_…`). Required. */
|
|
107
|
+
apiKey: string;
|
|
108
|
+
/** Override the API base URL. Defaults to `https://api.rerout.co`. */
|
|
109
|
+
baseUrl?: string;
|
|
110
|
+
/** Inject a custom `fetch` implementation. Useful in tests and edge runtimes. */
|
|
111
|
+
fetch?: typeof fetch;
|
|
112
|
+
/** Request timeout in ms. Defaults to 30 000. */
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
/** Additional headers added to every request (e.g. `user-agent`). */
|
|
115
|
+
defaultHeaders?: Record<string, string>;
|
|
116
|
+
}
|
|
117
|
+
interface RequestInitLike {
|
|
118
|
+
method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
119
|
+
path: string;
|
|
120
|
+
query?: Record<string, string | number | undefined>;
|
|
121
|
+
body?: unknown;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Official client for the Rerout API.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import { Rerout } from '@rerout/sdk'
|
|
129
|
+
*
|
|
130
|
+
* const rerout = new Rerout({ apiKey: process.env.REROUT_API_KEY! })
|
|
131
|
+
*
|
|
132
|
+
* const link = await rerout.links.create({
|
|
133
|
+
* target_url: 'https://example.com/sale',
|
|
134
|
+
* })
|
|
135
|
+
* console.log(link.short_url)
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
declare class Rerout {
|
|
139
|
+
/** Link operations: create, list, get, update, delete, stats. */
|
|
140
|
+
readonly links: Links;
|
|
141
|
+
/** Project-level operations: aggregate stats. */
|
|
142
|
+
readonly project: Project;
|
|
143
|
+
/** QR helpers (pure URL builders + signed fetch). */
|
|
144
|
+
readonly qr: Qr;
|
|
145
|
+
private readonly apiKey;
|
|
146
|
+
private readonly baseUrl;
|
|
147
|
+
private readonly fetchImpl;
|
|
148
|
+
private readonly timeoutMs;
|
|
149
|
+
private readonly defaultHeaders;
|
|
150
|
+
constructor(options: ReroutClientOptions);
|
|
151
|
+
/** @internal — invoked by Links / Project / Qr. */
|
|
152
|
+
request<T>(init: RequestInitLike): Promise<T>;
|
|
153
|
+
/** Internal — used by Qr to expose the resolved base URL. */
|
|
154
|
+
get resolvedBaseUrl(): string;
|
|
155
|
+
}
|
|
156
|
+
declare class Links {
|
|
157
|
+
private readonly client;
|
|
158
|
+
/** @internal */
|
|
159
|
+
constructor(client: Rerout);
|
|
160
|
+
/** Create a new short link. */
|
|
161
|
+
create(input: CreateLinkInput): Promise<Link>;
|
|
162
|
+
/** Paginated list of links in the project. */
|
|
163
|
+
list(params?: ListLinksParams): Promise<ListLinksResult>;
|
|
164
|
+
/** Get a single link by code. */
|
|
165
|
+
get(code: string): Promise<Link>;
|
|
166
|
+
/** Patch a link. Only fields present in `input` are changed. */
|
|
167
|
+
update(code: string, input: UpdateLinkInput): Promise<Link>;
|
|
168
|
+
/** Soft-delete a link. The short URL stops redirecting and is gone from lists. */
|
|
169
|
+
delete(code: string): Promise<{
|
|
170
|
+
deleted: boolean;
|
|
171
|
+
}>;
|
|
172
|
+
/** Per-link click stats. Defaults to 30 days. */
|
|
173
|
+
stats(code: string, days?: number): Promise<LinkStats>;
|
|
174
|
+
}
|
|
175
|
+
declare class Project {
|
|
176
|
+
private readonly client;
|
|
177
|
+
/** @internal */
|
|
178
|
+
constructor(client: Rerout);
|
|
179
|
+
/** Aggregate stats across every link in the project. */
|
|
180
|
+
stats(days?: number): Promise<ProjectStats>;
|
|
181
|
+
/** Info about the project that owns the current API key. */
|
|
182
|
+
me(): Promise<{
|
|
183
|
+
id: string;
|
|
184
|
+
name: string;
|
|
185
|
+
slug: string;
|
|
186
|
+
}>;
|
|
187
|
+
}
|
|
188
|
+
declare class Qr {
|
|
189
|
+
private readonly client;
|
|
190
|
+
/** @internal */
|
|
191
|
+
constructor(client: Rerout);
|
|
192
|
+
/**
|
|
193
|
+
* Build the URL the API serves the QR SVG from. Pure — does not call the API.
|
|
194
|
+
*/
|
|
195
|
+
url(code: string, options?: QrUrlOptions): string;
|
|
196
|
+
/**
|
|
197
|
+
* Fetch the QR as an SVG string. Hits the same endpoint as `url()` but
|
|
198
|
+
* attaches the bearer token and returns the rendered body.
|
|
199
|
+
*/
|
|
200
|
+
svg(code: string, options?: QrUrlOptions): Promise<string>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Error thrown for any Rerout API failure — network, HTTP non-2xx, or invalid
|
|
205
|
+
* response shape. The `code` field matches the stable string identifier
|
|
206
|
+
* returned by the API (e.g. `bad_target_url`, `rate_limited`, `not_found`) so
|
|
207
|
+
* callers can branch on it without parsing the human-readable message.
|
|
208
|
+
*
|
|
209
|
+
* For network or non-JSON failures the `code` is set to one of the synthetic
|
|
210
|
+
* values: `network_error`, `unexpected_response`, `client_error`.
|
|
211
|
+
*/
|
|
212
|
+
declare class ReroutError extends Error {
|
|
213
|
+
/** Stable error code, either from the API or a synthetic client-side one. */
|
|
214
|
+
readonly code: string;
|
|
215
|
+
/** HTTP status code, or 0 when the request never reached the server. */
|
|
216
|
+
readonly status: number;
|
|
217
|
+
/** The raw response body (parsed JSON or string), useful for debugging. */
|
|
218
|
+
readonly details: unknown;
|
|
219
|
+
constructor(opts: {
|
|
220
|
+
code: string;
|
|
221
|
+
message: string;
|
|
222
|
+
status: number;
|
|
223
|
+
details?: unknown;
|
|
224
|
+
});
|
|
225
|
+
/** True for HTTP 5xx responses (server-side issues). */
|
|
226
|
+
get isServerError(): boolean;
|
|
227
|
+
/** True for HTTP 429 — caller should back off and retry. */
|
|
228
|
+
get isRateLimited(): boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Default tolerance window (in seconds) between the `t=` timestamp in the
|
|
233
|
+
* signature header and the current time. Any signed payload older than this
|
|
234
|
+
* is rejected — protects against captured-replay attacks. Five minutes.
|
|
235
|
+
*/
|
|
236
|
+
declare const DEFAULT_SIGNATURE_TOLERANCE_SECONDS = 300;
|
|
237
|
+
interface VerifyOptions {
|
|
238
|
+
/** Raw, unmodified request body. Reading as `await req.text()` or equivalent. */
|
|
239
|
+
rawBody: string;
|
|
240
|
+
/** Value of the `X-Rerout-Signature` header. */
|
|
241
|
+
signatureHeader: string;
|
|
242
|
+
/** Endpoint signing secret (`whsec_…`) from the dashboard. */
|
|
243
|
+
secret: string;
|
|
244
|
+
/** Window in seconds; defaults to 300. Set 0 to disable timestamp check. */
|
|
245
|
+
toleranceSeconds?: number;
|
|
246
|
+
/**
|
|
247
|
+
* Injectable clock for testing. Returns the current time in unix seconds.
|
|
248
|
+
* Defaults to `Math.floor(Date.now() / 1000)`.
|
|
249
|
+
*/
|
|
250
|
+
now?: () => number;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Verify a Rerout webhook signature.
|
|
254
|
+
*
|
|
255
|
+
* The header format is `t={unix},v1={hex_hmac}`. The HMAC is computed over
|
|
256
|
+
* `${timestamp}.${rawBody}` with the endpoint signing secret as the key.
|
|
257
|
+
*
|
|
258
|
+
* Returns `true` only when:
|
|
259
|
+
* - the header parses cleanly,
|
|
260
|
+
* - the timestamp is within `toleranceSeconds` of `now()`,
|
|
261
|
+
* - and the computed HMAC matches the supplied `v1` in constant time.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* import { verifyReroutSignature } from '@rerout/sdk'
|
|
266
|
+
*
|
|
267
|
+
* app.post('/webhooks/rerout', async (req, res) => {
|
|
268
|
+
* const raw = await readRawBody(req)
|
|
269
|
+
* const ok = verifyReroutSignature({
|
|
270
|
+
* rawBody: raw,
|
|
271
|
+
* signatureHeader: req.headers['x-rerout-signature'] as string,
|
|
272
|
+
* secret: process.env.REROUT_WEBHOOK_SECRET!,
|
|
273
|
+
* })
|
|
274
|
+
* if (!ok) return res.status(400).end('bad signature')
|
|
275
|
+
* // handle event…
|
|
276
|
+
* res.status(200).end()
|
|
277
|
+
* })
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
declare function verifyReroutSignature(opts: VerifyOptions): boolean;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Build the URL the Rerout API serves the QR SVG from. This is a pure helper
|
|
284
|
+
* — it does not call the API. Pass the resulting URL to an `<img>` tag,
|
|
285
|
+
* download it server-side, or send it to a render pipeline.
|
|
286
|
+
*
|
|
287
|
+
* Authentication is the caller's responsibility — the endpoint is API-key
|
|
288
|
+
* authenticated, so any `<img>` tag will need a way to send the bearer token
|
|
289
|
+
* (typically via a server-side proxy).
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* const url = buildQrUrl({ baseUrl: 'https://api.rerout.co', code: 'q4' })
|
|
294
|
+
* // → https://api.rerout.co/v1/links/q4/qr
|
|
295
|
+
*
|
|
296
|
+
* const branded = buildQrUrl({
|
|
297
|
+
* baseUrl: 'https://api.rerout.co',
|
|
298
|
+
* code: 'q4',
|
|
299
|
+
* options: { size: 12, ecc: 'H', domain: 'go.brand.com' },
|
|
300
|
+
* })
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
declare function buildQrUrl(args: {
|
|
304
|
+
baseUrl: string;
|
|
305
|
+
code: string;
|
|
306
|
+
options?: QrUrlOptions;
|
|
307
|
+
}): string;
|
|
308
|
+
|
|
309
|
+
export { type CreateLinkInput, DEFAULT_BASE_URL, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, type DailyClicksPoint, type Link, type LinkStats, Links, type ListLinksParams, type ListLinksResult, Project, type ProjectStats, Qr, type QrUrlOptions, Rerout, type ReroutClientOptions, ReroutError, type StatsBreakdown, type UpdateLinkInput, type VerifyOptions, buildQrUrl, verifyReroutSignature };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types returned by the Rerout API. Fields mirror the server-side
|
|
3
|
+
* `LinkResponse` / `ProjectStatsResponse` etc. shapes so JSON is parsed
|
|
4
|
+
* without transformation.
|
|
5
|
+
*/
|
|
6
|
+
interface Link {
|
|
7
|
+
code: string;
|
|
8
|
+
short_url: string;
|
|
9
|
+
domain_hostname: string | null;
|
|
10
|
+
target_url: string;
|
|
11
|
+
project_id: string;
|
|
12
|
+
expires_at: number | null;
|
|
13
|
+
is_active: boolean;
|
|
14
|
+
seo_title: string | null;
|
|
15
|
+
seo_description: string | null;
|
|
16
|
+
seo_image_url: string | null;
|
|
17
|
+
seo_canonical_url: string | null;
|
|
18
|
+
seo_noindex: boolean;
|
|
19
|
+
seo_updated_at: number | null;
|
|
20
|
+
created_at: number;
|
|
21
|
+
updated_at: number;
|
|
22
|
+
}
|
|
23
|
+
interface CreateLinkInput {
|
|
24
|
+
target_url: string;
|
|
25
|
+
/** Verified custom domain to host this link on. Omit for `rerout.co/:code`. */
|
|
26
|
+
domain_hostname?: string;
|
|
27
|
+
/** Custom path. Only allowed with verified domain_hostname. */
|
|
28
|
+
code?: string;
|
|
29
|
+
/** Unix seconds. Omit for a permanent link. */
|
|
30
|
+
expires_at?: number;
|
|
31
|
+
/** Override social preview title. Max 90 chars. */
|
|
32
|
+
seo_title?: string | null;
|
|
33
|
+
/** Override social preview description. Max 220 chars. */
|
|
34
|
+
seo_description?: string | null;
|
|
35
|
+
/** Absolute https:// social preview image. */
|
|
36
|
+
seo_image_url?: string | null;
|
|
37
|
+
/** Canonical URL for the preview HTML. */
|
|
38
|
+
seo_canonical_url?: string | null;
|
|
39
|
+
/** Whether the preview page should be indexed. Default: false (noindex). */
|
|
40
|
+
seo_noindex?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface UpdateLinkInput {
|
|
43
|
+
target_url?: string;
|
|
44
|
+
expires_at?: number | null;
|
|
45
|
+
is_active?: boolean;
|
|
46
|
+
seo_title?: string | null;
|
|
47
|
+
seo_description?: string | null;
|
|
48
|
+
seo_image_url?: string | null;
|
|
49
|
+
seo_canonical_url?: string | null;
|
|
50
|
+
seo_noindex?: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface ListLinksParams {
|
|
53
|
+
/** Pagination cursor returned by a previous call. */
|
|
54
|
+
cursor?: number;
|
|
55
|
+
/** Page size. Server-side default + max apply. */
|
|
56
|
+
limit?: number;
|
|
57
|
+
}
|
|
58
|
+
interface ListLinksResult {
|
|
59
|
+
links: Link[];
|
|
60
|
+
next_cursor: number | null;
|
|
61
|
+
}
|
|
62
|
+
interface StatsBreakdown {
|
|
63
|
+
value: string;
|
|
64
|
+
clicks: number;
|
|
65
|
+
}
|
|
66
|
+
interface DailyClicksPoint {
|
|
67
|
+
day: number;
|
|
68
|
+
clicks: number;
|
|
69
|
+
qr_scans: number;
|
|
70
|
+
}
|
|
71
|
+
interface ProjectStats {
|
|
72
|
+
days: number;
|
|
73
|
+
total_clicks: number;
|
|
74
|
+
qr_scans: number;
|
|
75
|
+
daily: DailyClicksPoint[];
|
|
76
|
+
countries: StatsBreakdown[];
|
|
77
|
+
referrers: StatsBreakdown[];
|
|
78
|
+
devices: StatsBreakdown[];
|
|
79
|
+
browsers: StatsBreakdown[];
|
|
80
|
+
top_codes: StatsBreakdown[];
|
|
81
|
+
}
|
|
82
|
+
interface LinkStats {
|
|
83
|
+
code: string;
|
|
84
|
+
days: number;
|
|
85
|
+
total_clicks: number;
|
|
86
|
+
qr_scans: number;
|
|
87
|
+
countries: StatsBreakdown[];
|
|
88
|
+
referrers: StatsBreakdown[];
|
|
89
|
+
}
|
|
90
|
+
interface QrUrlOptions {
|
|
91
|
+
/** Module size in px. 1–32. Server default: 8. */
|
|
92
|
+
size?: number;
|
|
93
|
+
/** Quiet zone in modules. 0–16. Server default: 4. */
|
|
94
|
+
margin?: number;
|
|
95
|
+
/** Error correction level. */
|
|
96
|
+
ecc?: 'L' | 'M' | 'Q' | 'H';
|
|
97
|
+
/** Force the QR to encode a specific verified custom domain. */
|
|
98
|
+
domain?: string;
|
|
99
|
+
/** Cache-bust on regenerate. */
|
|
100
|
+
refresh?: string | true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Default production API URL. Override via `baseUrl` for staging / self-hosted. */
|
|
104
|
+
declare const DEFAULT_BASE_URL = "https://api.rerout.co";
|
|
105
|
+
interface ReroutClientOptions {
|
|
106
|
+
/** Project API key (`rrk_…`). Required. */
|
|
107
|
+
apiKey: string;
|
|
108
|
+
/** Override the API base URL. Defaults to `https://api.rerout.co`. */
|
|
109
|
+
baseUrl?: string;
|
|
110
|
+
/** Inject a custom `fetch` implementation. Useful in tests and edge runtimes. */
|
|
111
|
+
fetch?: typeof fetch;
|
|
112
|
+
/** Request timeout in ms. Defaults to 30 000. */
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
/** Additional headers added to every request (e.g. `user-agent`). */
|
|
115
|
+
defaultHeaders?: Record<string, string>;
|
|
116
|
+
}
|
|
117
|
+
interface RequestInitLike {
|
|
118
|
+
method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
119
|
+
path: string;
|
|
120
|
+
query?: Record<string, string | number | undefined>;
|
|
121
|
+
body?: unknown;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Official client for the Rerout API.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* import { Rerout } from '@rerout/sdk'
|
|
129
|
+
*
|
|
130
|
+
* const rerout = new Rerout({ apiKey: process.env.REROUT_API_KEY! })
|
|
131
|
+
*
|
|
132
|
+
* const link = await rerout.links.create({
|
|
133
|
+
* target_url: 'https://example.com/sale',
|
|
134
|
+
* })
|
|
135
|
+
* console.log(link.short_url)
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
declare class Rerout {
|
|
139
|
+
/** Link operations: create, list, get, update, delete, stats. */
|
|
140
|
+
readonly links: Links;
|
|
141
|
+
/** Project-level operations: aggregate stats. */
|
|
142
|
+
readonly project: Project;
|
|
143
|
+
/** QR helpers (pure URL builders + signed fetch). */
|
|
144
|
+
readonly qr: Qr;
|
|
145
|
+
private readonly apiKey;
|
|
146
|
+
private readonly baseUrl;
|
|
147
|
+
private readonly fetchImpl;
|
|
148
|
+
private readonly timeoutMs;
|
|
149
|
+
private readonly defaultHeaders;
|
|
150
|
+
constructor(options: ReroutClientOptions);
|
|
151
|
+
/** @internal — invoked by Links / Project / Qr. */
|
|
152
|
+
request<T>(init: RequestInitLike): Promise<T>;
|
|
153
|
+
/** Internal — used by Qr to expose the resolved base URL. */
|
|
154
|
+
get resolvedBaseUrl(): string;
|
|
155
|
+
}
|
|
156
|
+
declare class Links {
|
|
157
|
+
private readonly client;
|
|
158
|
+
/** @internal */
|
|
159
|
+
constructor(client: Rerout);
|
|
160
|
+
/** Create a new short link. */
|
|
161
|
+
create(input: CreateLinkInput): Promise<Link>;
|
|
162
|
+
/** Paginated list of links in the project. */
|
|
163
|
+
list(params?: ListLinksParams): Promise<ListLinksResult>;
|
|
164
|
+
/** Get a single link by code. */
|
|
165
|
+
get(code: string): Promise<Link>;
|
|
166
|
+
/** Patch a link. Only fields present in `input` are changed. */
|
|
167
|
+
update(code: string, input: UpdateLinkInput): Promise<Link>;
|
|
168
|
+
/** Soft-delete a link. The short URL stops redirecting and is gone from lists. */
|
|
169
|
+
delete(code: string): Promise<{
|
|
170
|
+
deleted: boolean;
|
|
171
|
+
}>;
|
|
172
|
+
/** Per-link click stats. Defaults to 30 days. */
|
|
173
|
+
stats(code: string, days?: number): Promise<LinkStats>;
|
|
174
|
+
}
|
|
175
|
+
declare class Project {
|
|
176
|
+
private readonly client;
|
|
177
|
+
/** @internal */
|
|
178
|
+
constructor(client: Rerout);
|
|
179
|
+
/** Aggregate stats across every link in the project. */
|
|
180
|
+
stats(days?: number): Promise<ProjectStats>;
|
|
181
|
+
/** Info about the project that owns the current API key. */
|
|
182
|
+
me(): Promise<{
|
|
183
|
+
id: string;
|
|
184
|
+
name: string;
|
|
185
|
+
slug: string;
|
|
186
|
+
}>;
|
|
187
|
+
}
|
|
188
|
+
declare class Qr {
|
|
189
|
+
private readonly client;
|
|
190
|
+
/** @internal */
|
|
191
|
+
constructor(client: Rerout);
|
|
192
|
+
/**
|
|
193
|
+
* Build the URL the API serves the QR SVG from. Pure — does not call the API.
|
|
194
|
+
*/
|
|
195
|
+
url(code: string, options?: QrUrlOptions): string;
|
|
196
|
+
/**
|
|
197
|
+
* Fetch the QR as an SVG string. Hits the same endpoint as `url()` but
|
|
198
|
+
* attaches the bearer token and returns the rendered body.
|
|
199
|
+
*/
|
|
200
|
+
svg(code: string, options?: QrUrlOptions): Promise<string>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Error thrown for any Rerout API failure — network, HTTP non-2xx, or invalid
|
|
205
|
+
* response shape. The `code` field matches the stable string identifier
|
|
206
|
+
* returned by the API (e.g. `bad_target_url`, `rate_limited`, `not_found`) so
|
|
207
|
+
* callers can branch on it without parsing the human-readable message.
|
|
208
|
+
*
|
|
209
|
+
* For network or non-JSON failures the `code` is set to one of the synthetic
|
|
210
|
+
* values: `network_error`, `unexpected_response`, `client_error`.
|
|
211
|
+
*/
|
|
212
|
+
declare class ReroutError extends Error {
|
|
213
|
+
/** Stable error code, either from the API or a synthetic client-side one. */
|
|
214
|
+
readonly code: string;
|
|
215
|
+
/** HTTP status code, or 0 when the request never reached the server. */
|
|
216
|
+
readonly status: number;
|
|
217
|
+
/** The raw response body (parsed JSON or string), useful for debugging. */
|
|
218
|
+
readonly details: unknown;
|
|
219
|
+
constructor(opts: {
|
|
220
|
+
code: string;
|
|
221
|
+
message: string;
|
|
222
|
+
status: number;
|
|
223
|
+
details?: unknown;
|
|
224
|
+
});
|
|
225
|
+
/** True for HTTP 5xx responses (server-side issues). */
|
|
226
|
+
get isServerError(): boolean;
|
|
227
|
+
/** True for HTTP 429 — caller should back off and retry. */
|
|
228
|
+
get isRateLimited(): boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Default tolerance window (in seconds) between the `t=` timestamp in the
|
|
233
|
+
* signature header and the current time. Any signed payload older than this
|
|
234
|
+
* is rejected — protects against captured-replay attacks. Five minutes.
|
|
235
|
+
*/
|
|
236
|
+
declare const DEFAULT_SIGNATURE_TOLERANCE_SECONDS = 300;
|
|
237
|
+
interface VerifyOptions {
|
|
238
|
+
/** Raw, unmodified request body. Reading as `await req.text()` or equivalent. */
|
|
239
|
+
rawBody: string;
|
|
240
|
+
/** Value of the `X-Rerout-Signature` header. */
|
|
241
|
+
signatureHeader: string;
|
|
242
|
+
/** Endpoint signing secret (`whsec_…`) from the dashboard. */
|
|
243
|
+
secret: string;
|
|
244
|
+
/** Window in seconds; defaults to 300. Set 0 to disable timestamp check. */
|
|
245
|
+
toleranceSeconds?: number;
|
|
246
|
+
/**
|
|
247
|
+
* Injectable clock for testing. Returns the current time in unix seconds.
|
|
248
|
+
* Defaults to `Math.floor(Date.now() / 1000)`.
|
|
249
|
+
*/
|
|
250
|
+
now?: () => number;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Verify a Rerout webhook signature.
|
|
254
|
+
*
|
|
255
|
+
* The header format is `t={unix},v1={hex_hmac}`. The HMAC is computed over
|
|
256
|
+
* `${timestamp}.${rawBody}` with the endpoint signing secret as the key.
|
|
257
|
+
*
|
|
258
|
+
* Returns `true` only when:
|
|
259
|
+
* - the header parses cleanly,
|
|
260
|
+
* - the timestamp is within `toleranceSeconds` of `now()`,
|
|
261
|
+
* - and the computed HMAC matches the supplied `v1` in constant time.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* import { verifyReroutSignature } from '@rerout/sdk'
|
|
266
|
+
*
|
|
267
|
+
* app.post('/webhooks/rerout', async (req, res) => {
|
|
268
|
+
* const raw = await readRawBody(req)
|
|
269
|
+
* const ok = verifyReroutSignature({
|
|
270
|
+
* rawBody: raw,
|
|
271
|
+
* signatureHeader: req.headers['x-rerout-signature'] as string,
|
|
272
|
+
* secret: process.env.REROUT_WEBHOOK_SECRET!,
|
|
273
|
+
* })
|
|
274
|
+
* if (!ok) return res.status(400).end('bad signature')
|
|
275
|
+
* // handle event…
|
|
276
|
+
* res.status(200).end()
|
|
277
|
+
* })
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
declare function verifyReroutSignature(opts: VerifyOptions): boolean;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Build the URL the Rerout API serves the QR SVG from. This is a pure helper
|
|
284
|
+
* — it does not call the API. Pass the resulting URL to an `<img>` tag,
|
|
285
|
+
* download it server-side, or send it to a render pipeline.
|
|
286
|
+
*
|
|
287
|
+
* Authentication is the caller's responsibility — the endpoint is API-key
|
|
288
|
+
* authenticated, so any `<img>` tag will need a way to send the bearer token
|
|
289
|
+
* (typically via a server-side proxy).
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* const url = buildQrUrl({ baseUrl: 'https://api.rerout.co', code: 'q4' })
|
|
294
|
+
* // → https://api.rerout.co/v1/links/q4/qr
|
|
295
|
+
*
|
|
296
|
+
* const branded = buildQrUrl({
|
|
297
|
+
* baseUrl: 'https://api.rerout.co',
|
|
298
|
+
* code: 'q4',
|
|
299
|
+
* options: { size: 12, ecc: 'H', domain: 'go.brand.com' },
|
|
300
|
+
* })
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
declare function buildQrUrl(args: {
|
|
304
|
+
baseUrl: string;
|
|
305
|
+
code: string;
|
|
306
|
+
options?: QrUrlOptions;
|
|
307
|
+
}): string;
|
|
308
|
+
|
|
309
|
+
export { type CreateLinkInput, DEFAULT_BASE_URL, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, type DailyClicksPoint, type Link, type LinkStats, Links, type ListLinksParams, type ListLinksResult, Project, type ProjectStats, Qr, type QrUrlOptions, Rerout, type ReroutClientOptions, ReroutError, type StatsBreakdown, type UpdateLinkInput, type VerifyOptions, buildQrUrl, verifyReroutSignature };
|