contextkit-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/README.md +120 -0
- package/dist/index.cjs +646 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +524 -0
- package/dist/index.d.ts +524 -0
- package/dist/index.js +604 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# contextkit-sdk
|
|
2
|
+
|
|
3
|
+
Node.js SDK for [ContextKit](https://contextkit.com). Ask scoped questions about a
|
|
4
|
+
connected user's location — "are they inside this zone right now?" — without ever
|
|
5
|
+
holding their coordinates.
|
|
6
|
+
|
|
7
|
+
Full documentation lives at **[docs.contextkit.com](https://docs.contextkit.com)**.
|
|
8
|
+
This README covers only the connect flow.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pnpm add contextkit-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Node 20+. Server-side only: the client secret must never reach a browser.
|
|
15
|
+
|
|
16
|
+
## Connect a user
|
|
17
|
+
|
|
18
|
+
ContextKit uses OAuth 2 with PKCE. Three steps, all on your backend.
|
|
19
|
+
|
|
20
|
+
**1. Send the user to consent.** Keep `state` and the verifier in the user's session.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { ContextKit, generateCodeVerifier, generateState } from "contextkit-sdk";
|
|
24
|
+
|
|
25
|
+
const ck = new ContextKit({
|
|
26
|
+
clientId: process.env.CONTEXTKIT_CLIENT_ID!,
|
|
27
|
+
clientSecret: process.env.CONTEXTKIT_CLIENT_SECRET!,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const state = generateState();
|
|
31
|
+
const codeVerifier = generateCodeVerifier();
|
|
32
|
+
session.contextkit = { state, codeVerifier };
|
|
33
|
+
|
|
34
|
+
redirect(
|
|
35
|
+
ck.authorizeUrl({
|
|
36
|
+
redirectUri: "https://yourapp.example/contextkit/callback",
|
|
37
|
+
scopes: ["location.verify.zone", "location.place.current"],
|
|
38
|
+
state,
|
|
39
|
+
codeVerifier,
|
|
40
|
+
externalUserId: user.id, // optional; shown to you, never to the user
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**2. Handle the callback.** Refuse it if `state` does not match, then exchange the code.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
if (query.state !== session.contextkit.state) throw new Error("state mismatch");
|
|
49
|
+
|
|
50
|
+
const tokens = await ck.exchangeCode({
|
|
51
|
+
code: query.code,
|
|
52
|
+
codeVerifier: session.contextkit.codeVerifier,
|
|
53
|
+
redirectUri: "https://yourapp.example/contextkit/callback",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await db.users.update(user.id, { contextkit: tokens }); // persist the whole set
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**3. Ask questions.** Refresh tokens rotate, so persist whatever `onTokens` hands you.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const stored = await db.users.get(user.id).contextkit;
|
|
63
|
+
|
|
64
|
+
const ckUser = ck.forUser(stored, {
|
|
65
|
+
onTokens: (next) => db.users.update(user.id, { contextkit: next }),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const answer = await ckUser.answers.verifyZone({
|
|
69
|
+
lat: 48.3537,
|
|
70
|
+
lon: 11.7861,
|
|
71
|
+
radiusM: 500,
|
|
72
|
+
label: "Munich Airport arrivals",
|
|
73
|
+
});
|
|
74
|
+
// answer.state is "inside" | "outside" | "unknown" — unknown is a value, not an error
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Errors
|
|
78
|
+
|
|
79
|
+
Every failure is a `ContextKitError`; the subclass says what to do.
|
|
80
|
+
|
|
81
|
+
| Error | Meaning | Do |
|
|
82
|
+
| ------------------------------ | --------------------------------------------------- | ------------------------ |
|
|
83
|
+
| `TokenRevokedError` | The grant is gone (revoked, expired, replayed). | Send the user to step 1. |
|
|
84
|
+
| `RateLimitedError` | Per-app budget or rate limit. `retryAfterSeconds`. | Wait, then retry. |
|
|
85
|
+
| `ScopeError` | The grant lacks the scope this call needs. | Request it at step 1. |
|
|
86
|
+
| `NotFoundError` | Unshared and nonexistent look identical on purpose. | Refresh your place list. |
|
|
87
|
+
| `ValidationError` | Request shape was wrong. `messages` says how. | Fix the call. |
|
|
88
|
+
| `TimeoutError`, `NetworkError` | Transient. | Retry with backoff. |
|
|
89
|
+
|
|
90
|
+
## Webhooks
|
|
91
|
+
|
|
92
|
+
Rules and subscriptions deliver signed POSTs. Verify with the **raw** body bytes.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { verifyWebhook, isRuleEvent } from "contextkit-sdk";
|
|
96
|
+
|
|
97
|
+
app.post("/hooks/contextkit", express.raw({ type: "application/json" }), async (req, res) => {
|
|
98
|
+
let event;
|
|
99
|
+
try {
|
|
100
|
+
event = await verifyWebhook({
|
|
101
|
+
rawBody: req.body,
|
|
102
|
+
signature: req.headers["x-contextkit-signature"],
|
|
103
|
+
secret: process.env.CONTEXTKIT_WEBHOOK_SECRET!,
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
106
|
+
return res.status(400).end();
|
|
107
|
+
}
|
|
108
|
+
if (isRuleEvent(event)) queue.enqueue(event);
|
|
109
|
+
res.status(204).end();
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
pnpm install
|
|
117
|
+
pnpm typecheck && pnpm test && pnpm build
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Releases publish to npm on a `v*` tag that matches `package.json`.
|