@noirtrack/sdk 0.1.0 → 0.2.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 +17 -0
- package/README.md +6 -3
- package/dist/core/ingest.js +9 -1
- package/dist/core/types.d.ts +7 -1
- package/dist/index.js +3 -0
- package/dist/web.d.ts +4 -3
- package/dist/web.js +4 -1
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@noirtrack/sdk` are documented here. This project follows semantic versioning (while pre-1.0, a minor bump may include breaking changes).
|
|
4
|
+
|
|
5
|
+
## 0.2.0
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Breaking (web client):** `block` is now a boolean. Turn on the Traffic Filter with `createClient({ block: true })` instead of `block: 'redirect' | 'overlay'`. Whether it monitors or blocks, and how a block looks (redirect or overlay), are now set per site in Firewall settings, with no code change. If you passed `block: 'redirect'` or `block: 'overlay'`, change it to `block: true`.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `Verdict.style` (`'redirect' | 'overlay' | null`): the block style returned by the server. The web client applies it automatically, so the snippet and SDK no longer hardcode the style.
|
|
14
|
+
|
|
15
|
+
## 0.1.0
|
|
16
|
+
|
|
17
|
+
- Initial release. Framework-agnostic core with browser, React Native, Next.js, Express, and fetch/edge adapters. Analytics, firewall decisions, goals, revenue, identify, and Form Shield, all from one client.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ One NoirTrack SDK for the browser, React Native, and your server. You set your k
|
|
|
4
4
|
|
|
5
5
|
Works in any runtime with a global `fetch`: Node 18+, Next.js, Cloudflare Workers, Deno, Bun, the browser, and React Native. ESM only.
|
|
6
6
|
|
|
7
|
-
**Docs:** [noirtrack.com/docs](https://noirtrack.com/docs)
|
|
7
|
+
**Docs:** [noirtrack.com/docs](https://noirtrack.com/docs) · [NPM SDK guide](https://noirtrack.com/docs/npm-sdk) · [Server SDK reference](https://noirtrack.com/docs/server-sdk)
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -26,7 +26,7 @@ noir.identify('user_123', { email });
|
|
|
26
26
|
noir.revenue({ checkoutId: 'cs_test_123' });
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Pageviews and
|
|
29
|
+
Pageviews and route changes are automatic. Events are batched and sent with `sendBeacon`. Set `cookieless: true` for a no-cookie mode, or `block: true` to turn on the Traffic Filter. Whether it monitors or blocks, and how a block looks (redirect or overlay), are set in your Firewall settings, with no code change. Safe to import in SSR (it does nothing without a window). Also: `view(path?)`, `reset()`, `flush()`, `links.decorate(url)`.
|
|
30
30
|
|
|
31
31
|
## React Native
|
|
32
32
|
|
|
@@ -81,7 +81,9 @@ export const config = { matcher: ['/((?!_next/|favicon.ico).*)'] };
|
|
|
81
81
|
|
|
82
82
|
`@noirtrack/sdk/express` (`guard(noir, { onBlock })`) and `@noirtrack/sdk/fetch` (`createGuard(noir, { onBlock })`) work the same way. You can also pass `{ publicKey, secretKey, onBlock }` instead of a client.
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
**Monitor or Block is set in the dashboard.** Adding a guard turns the firewall on, but whether it blocks or just monitors is your Firewall Mode, not the code. New sites start in **Monitor** (detect and log, don't block anyone) until you switch to **Block** in Firewall settings. `onBlock` only changes how a block is delivered once you are in Block mode.
|
|
85
|
+
|
|
86
|
+
The guards resolve the visitor IP from `CF-Connecting-IP`, `True-Client-IP`, then the left-most `X-Forwarded-For`. Run them **behind a trusted proxy/CDN that sets those headers**. If requests can reach your app directly, a client can spoof `X-Forwarded-For` to forge its IP. Keep your secret key server-side only; it is never needed in the browser or app bundle.
|
|
85
87
|
|
|
86
88
|
## Options
|
|
87
89
|
|
|
@@ -93,6 +95,7 @@ The guards resolve the visitor IP from `CF-Connecting-IP`, `True-Client-IP`, the
|
|
|
93
95
|
| `timeoutMs` | `800` | all | Abort a call after this, then fail open. |
|
|
94
96
|
| `autoPageviews` | `true` | web | Capture initial + SPA pageviews. |
|
|
95
97
|
| `cookieless` | `false` | web | No cookies; ids in sessionStorage. |
|
|
98
|
+
| `block` | `false` | web | Turn on the Traffic Filter (Monitor/Block set in dashboard). |
|
|
96
99
|
| `flushIntervalMs` / `maxQueueSize` | `5000` / `10` | web, RN | Batch flush tuning. |
|
|
97
100
|
| `storage` | in-memory | RN | Async store (pass AsyncStorage). |
|
|
98
101
|
| `onBlock` / `blockStatus` / `blockedPage` | `block` / `403` / none | adapters | Block handling. |
|
package/dist/core/ingest.js
CHANGED
|
@@ -111,7 +111,15 @@ export function createIngest(platform) {
|
|
|
111
111
|
function check(path) {
|
|
112
112
|
// /api/v1/check responds with { block, reason, blocked_page } — not the { action } shape that
|
|
113
113
|
// /decide uses — so normalise it into a Verdict before returning.
|
|
114
|
-
return postJson(`${endpoint}/api/v1/check`, payload('pageview', path), {}, timeoutMs).then((r) => r
|
|
114
|
+
return postJson(`${endpoint}/api/v1/check`, payload('pageview', path), {}, timeoutMs).then((r) => r
|
|
115
|
+
? {
|
|
116
|
+
action: r.block ? 'block' : 'allow',
|
|
117
|
+
reason: r.reason ?? null,
|
|
118
|
+
blocked_page: r.blocked_page ?? null,
|
|
119
|
+
style: r.style ?? null,
|
|
120
|
+
ttl: 0,
|
|
121
|
+
}
|
|
122
|
+
: null);
|
|
115
123
|
}
|
|
116
124
|
return { view, event, identify, revenue, reset, flush, ping, links, shield, check };
|
|
117
125
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface Verdict {
|
|
|
9
9
|
action: 'allow' | 'block' | 'challenge';
|
|
10
10
|
reason: string | null;
|
|
11
11
|
blocked_page: string | null;
|
|
12
|
+
/** How to present a block (set in Firewall settings): redirect to a page or show an overlay. */
|
|
13
|
+
style?: 'redirect' | 'overlay' | null;
|
|
12
14
|
ttl: number;
|
|
13
15
|
}
|
|
14
16
|
/** Input to a hard firewall decision (server, secret key). */
|
|
@@ -52,7 +54,11 @@ export type RevenueInput = {
|
|
|
52
54
|
provider?: 'stripe' | 'polar' | 'lemonsqueezy';
|
|
53
55
|
visitorId?: string;
|
|
54
56
|
} | {
|
|
55
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Required for `paid`/`refunded`; omit for `cancelled`/`subscription_ended` (no money moves).
|
|
59
|
+
* The server rejects a `paid`/`refunded` call with no amount (422) and the SDK fails open
|
|
60
|
+
* silently — the revenue is dropped, not thrown — so always pass it for those two statuses.
|
|
61
|
+
*/
|
|
56
62
|
amount?: number;
|
|
57
63
|
currency?: string;
|
|
58
64
|
visitorId?: string;
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,9 @@ export function createClient(options) {
|
|
|
31
31
|
// cancellation — goes through the secret-key payment endpoint.
|
|
32
32
|
if (!('checkoutId' in input))
|
|
33
33
|
return secret.payment(input);
|
|
34
|
+
// Capture is authenticated by the PUBLIC key (`site.key` middleware). A guard-only client built
|
|
35
|
+
// without `publicKey` sends `site_key: undefined`, so the server rejects it and this fails open
|
|
36
|
+
// silently — set `publicKey` on the client if you capture revenue by checkout id.
|
|
34
37
|
const provider = input.provider ?? 'stripe';
|
|
35
38
|
return postOk(`${endpoint}/api/v1/payment/capture`, { site_key: options.publicKey, provider, external_id: input.checkoutId, visitor_id: input.visitorId }, {}, timeoutMs);
|
|
36
39
|
}
|
package/dist/web.d.ts
CHANGED
|
@@ -11,9 +11,10 @@ export interface WebClientOptions {
|
|
|
11
11
|
cookieless?: boolean;
|
|
12
12
|
/** Track on localhost too (off by default, like the hosted script). */
|
|
13
13
|
allowLocalhost?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/** Turn on the Traffic Filter on the first view. Whether it monitors or blocks, and how a block
|
|
15
|
+
* looks (redirect or overlay), are set per site in Firewall settings. Off by default. */
|
|
16
|
+
block?: boolean;
|
|
17
|
+
/** Optional redirect target if none is set in Firewall settings. */
|
|
17
18
|
blockedPage?: string;
|
|
18
19
|
/** Self-hosted base URL. Defaults to the hosted service. */
|
|
19
20
|
endpoint?: string;
|
package/dist/web.js
CHANGED
|
@@ -230,7 +230,10 @@ export function createClient(options) {
|
|
|
230
230
|
if (verdict?.action !== 'block')
|
|
231
231
|
return;
|
|
232
232
|
const page = verdict.blocked_page ?? options.blockedPage;
|
|
233
|
-
|
|
233
|
+
// Style comes from Firewall settings (redirect or overlay); fall back to redirect when
|
|
234
|
+
// a page exists, else overlay.
|
|
235
|
+
const style = verdict.style ?? (page ? 'redirect' : 'overlay');
|
|
236
|
+
if (style === 'redirect' && page)
|
|
234
237
|
location.replace(page);
|
|
235
238
|
else
|
|
236
239
|
overlay();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noirtrack/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "NoirTrack server SDK. Block bots and bad traffic before your app renders, and record goals, revenue, and identify from your backend with one secret key. Framework-agnostic core plus Next.js, Express, and fetch/edge adapters.",
|
|
5
5
|
"homepage": "https://noirtrack.com/docs",
|
|
6
6
|
"bugs": {
|
|
@@ -41,9 +41,10 @@
|
|
|
41
41
|
"import": "./dist/form-shield.js"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
"files": ["dist"],
|
|
44
|
+
"files": ["dist", "README.md", "CHANGELOG.md"],
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc",
|
|
47
|
+
"prepublishOnly": "tsc",
|
|
47
48
|
"test:live": "node ../docs/live-test.mjs"
|
|
48
49
|
},
|
|
49
50
|
"keywords": ["noirtrack", "firewall", "bot-detection", "waf", "revenue", "goals", "middleware", "nextjs", "express", "edge"],
|