Skip to content

Commit 5819ebe

Browse files
authored
Add webhook helper (#7)
1 parent 3c23dbb commit 5819ebe

35 files changed

+1850
-105
lines changed

.github/workflows/test.yaml

+18-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ concurrency:
1515
cancel-in-progress: true
1616

1717
jobs:
18-
sdk:
19-
name: SDK
18+
node:
19+
name: Node
2020
runs-on: ubuntu-latest
2121
strategy:
2222
matrix:
@@ -44,3 +44,19 @@ jobs:
4444

4545
- name: Test
4646
run: make test
47+
48+
deno:
49+
name: Deno
50+
runs-on: ubuntu-latest
51+
52+
steps:
53+
- name: Checkout
54+
uses: actions/checkout@v4
55+
56+
- name: Setup Deno
57+
uses: denoland/setup-deno@v1
58+
with:
59+
deno-version: v1.x
60+
61+
- name: Check
62+
run: deno check src/mod.ts

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.typescript
22
dist
3+
examples/**/deno.json
34
node_modules
45
tsconfig.tsbuildinfo

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ fmt:
1212
.PHONY: lint
1313
lint:
1414
@pnpm prettier --check .
15+
@pnpm eslint src test
16+
ifneq (, $(shell command -v deno))
17+
@deno check src/mod.ts
18+
endif
1519

1620
.PHONY: test
1721
test:

eslint.config.mjs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @ts-check
2+
import eslint from "@eslint/js";
3+
import tseslint from "typescript-eslint";
4+
5+
export default tseslint.config(
6+
eslint.configs.recommended,
7+
...tseslint.configs.recommendedTypeChecked,
8+
{
9+
languageOptions: {
10+
parserOptions: {
11+
project: true,
12+
tsconfigRootDir: import.meta.dirname,
13+
},
14+
},
15+
rules: {
16+
"@typescript-eslint/no-explicit-any": "off",
17+
"@typescript-eslint/no-unsafe-argument": "off",
18+
"@typescript-eslint/no-unsafe-assignment": "off",
19+
"@typescript-eslint/no-unsafe-member-access": "off",
20+
},
21+
},
22+
);

examples/bun-webhook/main.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Webhook } from "@userhub/sdk";
2+
3+
async function main() {
4+
const port = process.env.PORT || "8000";
5+
6+
const signingSecret = process.env.SIGNING_SECRET;
7+
if (!signingSecret) {
8+
console.error("SIGNING_SECRET required");
9+
process.exit(1);
10+
}
11+
12+
const webhook = new Webhook(signingSecret);
13+
14+
webhook.onEvent((event) => {
15+
console.log("Event:", event.type);
16+
17+
if (event.type === "organizations.changed") {
18+
const organization = event.organizationsChanged?.organization;
19+
console.log(
20+
" - Organization:",
21+
organization?.id,
22+
organization?.displayName,
23+
);
24+
} else if (event.type === "users.changed") {
25+
const user = event.usersChanged?.user;
26+
console.log(" - User:", user?.id, user?.displayName);
27+
}
28+
});
29+
30+
Bun.serve({
31+
port,
32+
33+
async fetch(req: Request): Promise<Response> {
34+
return await webhook.handleFromWeb(req);
35+
},
36+
});
37+
}
38+
39+
main().catch(console.error);

examples/deno-webhook/main.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Webhook } from "npm:@userhub/sdk@latest";
2+
3+
async function main() {
4+
const port = parseInt(Deno.env.get("PORT") || "8000", 10);
5+
6+
const signingSecret = Deno.env.get("SIGNING_SECRET") || "";
7+
if (!signingSecret) {
8+
console.error("SIGNING_SECRET required");
9+
Deno.exit(1);
10+
}
11+
12+
const webhook = new Webhook(signingSecret);
13+
14+
webhook.onEvent((event) => {
15+
console.log("Event:", event.type);
16+
17+
if (event.type === "organizations.changed") {
18+
const organization = event.organizationsChanged?.organization;
19+
console.log(
20+
" - Organization:",
21+
organization?.id,
22+
organization?.displayName,
23+
);
24+
} else if (event.type === "users.changed") {
25+
const user = event.usersChanged?.user;
26+
console.log(" - User:", user?.id, user?.displayName);
27+
}
28+
});
29+
30+
Deno.serve({ port }, async (req: Request): Promise<Response> => {
31+
return await webhook.handleFromWeb(req);
32+
});
33+
}
34+
35+
main().catch(console.error);

examples/node-webhook/main.mjs

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Webhook, WebhookRequest } from "@userhub/sdk/webhook";
2+
import { createServer } from "http";
3+
4+
async function main() {
5+
const port = process.env.PORT || "8000";
6+
7+
const signingSecret = process.env.SIGNING_SECRET;
8+
if (!signingSecret) {
9+
console.error("SIGNING_SECRET required");
10+
process.exit(1);
11+
}
12+
13+
const webhook = new Webhook(signingSecret);
14+
15+
webhook.onEvent((event) => {
16+
console.log("Event:", event.type);
17+
18+
if (event.type === "organizations.changed") {
19+
const organization = event.organizationsChanged.organization;
20+
console.log(
21+
" - Organization:",
22+
organization.id,
23+
organization.displayName,
24+
);
25+
} else if (event.type === "users.changed") {
26+
const user = event.usersChanged.user;
27+
console.log(" - User:", user.id, user.displayName);
28+
}
29+
});
30+
31+
const server = createServer((req, res) => {
32+
const chunks = [];
33+
34+
req.on("data", (v) => {
35+
chunks.push(v);
36+
});
37+
38+
req.on("end", async () => {
39+
const r = await webhook.handle(
40+
new WebhookRequest({
41+
body: chunks.join(""),
42+
headers: req.headers,
43+
}),
44+
);
45+
46+
res.statusCode = r.statusCode;
47+
for (const [name, value] of r.headers.entries()) {
48+
res.setHeader(name, value);
49+
}
50+
res.end(r.body);
51+
});
52+
});
53+
54+
server.listen(port, () => {
55+
console.log(`Listening on ${port}`);
56+
});
57+
}
58+
59+
main().catch(console.error);

package.json

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@userhub/sdk",
3-
"version": "0.3.3",
3+
"version": "0.4.0",
44
"description": "UserHub JavaScript SDK",
55
"license": "MIT",
66
"author": "UserHub (https://userhub.com/)",
@@ -14,12 +14,12 @@
1414
},
1515
"exports": {
1616
".": {
17-
"require": "./dist/index.js",
18-
"import": "./dist/index.mjs",
19-
"types": "./dist/index.d.ts"
17+
"require": "./dist/mod.js",
18+
"import": "./dist/mod.mjs",
19+
"types": "./dist/mod.d.ts"
2020
}
2121
},
22-
"module": "./dist/index.mjs",
22+
"module": "./dist/mod.mjs",
2323
"files": [
2424
"/dist"
2525
],
@@ -30,12 +30,14 @@
3030
},
3131
"devDependencies": {
3232
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
33-
"@tsconfig/node18": "^18.2.2",
34-
"@types/node": "^20.11.19",
33+
"@tsconfig/node18": "^18",
34+
"@types/node": "^18",
35+
"eslint": "^8.57.0",
3536
"prettier": "^3.2.5",
3637
"prettier-package-json": "^2.8.0",
3738
"tsup": "^8.0.2",
3839
"typescript": "^5.3.3",
40+
"typescript-eslint": "^7.0.2",
3941
"vitest": "^1.3.0"
4042
},
4143
"keywords": [

0 commit comments

Comments
 (0)