@aivisibilityskill/sdk 0.0.0-stage → 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/dist/schema.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import type { components } from "./schema.js";
2
+ /**
3
+ * Verifies a webhook delivery (Standard Webhooks: https://www.standardwebhooks.com).
4
+ *
5
+ * // Express: app.post("/hooks", express.text({ type: "*\/*" }), async (req, res) => { … })
6
+ * const event = await verifyWebhook(rawBody, req.headers, process.env.AIV_WEBHOOK_SECRET!);
7
+ * if (event.type === "check.completed") …
8
+ *
9
+ * Pass the body exactly as received (a string, before any JSON parsing) and the request's
10
+ * headers. During a secret rotation, pass both secrets. Throws WebhookVerificationError for a
11
+ * bad signature, a missing header, or a timestamp more than 5 minutes off (a replay).
12
+ */
13
+ type Event = components["schemas"]["Event"];
14
+ export type WebhookHeaders = Headers | Record<string, string | string[] | undefined> | {
15
+ get(name: string): string | null;
16
+ };
17
+ export declare class WebhookVerificationError extends Error {
18
+ constructor(message: string);
19
+ }
20
+ export declare function verifyWebhook(body: string | Uint8Array | ArrayBuffer, headers: WebhookHeaders, secret: string | readonly string[], opts?: {
21
+ toleranceSeconds?: number;
22
+ now?: number;
23
+ }): Promise<Event>;
24
+ export {};
@@ -0,0 +1,91 @@
1
+ export class WebhookVerificationError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "WebhookVerificationError";
5
+ }
6
+ }
7
+ const TOLERANCE_SECONDS = 300;
8
+ const encoder = new TextEncoder();
9
+ function header(headers, name) {
10
+ if (typeof headers.get === "function")
11
+ return headers.get(name);
12
+ const record = headers;
13
+ const key = Object.keys(record).find((k) => k.toLowerCase() === name);
14
+ const value = key === undefined ? undefined : record[key];
15
+ return Array.isArray(value) ? (value[0] ?? null) : (value ?? null);
16
+ }
17
+ function fromBase64(text) {
18
+ const binary = atob(text);
19
+ const out = new Uint8Array(binary.length);
20
+ for (let i = 0; i < binary.length; i++)
21
+ out[i] = binary.charCodeAt(i);
22
+ return out;
23
+ }
24
+ async function verifiesWith(secret, signed, signatures) {
25
+ const raw = secret.startsWith("whsec_") ? secret.slice(6) : secret;
26
+ let bytes;
27
+ try {
28
+ bytes = fromBase64(raw);
29
+ }
30
+ catch {
31
+ bytes = new Uint8Array();
32
+ }
33
+ // An empty or tiny key would let anyone forge a signature.
34
+ if (bytes.length < 16) {
35
+ throw new WebhookVerificationError("That webhook secret isn't valid (expected whsec_…).");
36
+ }
37
+ let key;
38
+ try {
39
+ key = await crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
40
+ }
41
+ catch {
42
+ throw new WebhookVerificationError("That webhook secret isn't valid (expected whsec_…).");
43
+ }
44
+ for (const part of signatures) {
45
+ const [version, value] = part.split(",");
46
+ if (version !== "v1" || !value)
47
+ continue;
48
+ let given;
49
+ try {
50
+ given = fromBase64(value);
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ // WebCrypto compares in constant time.
56
+ if (await crypto.subtle.verify("HMAC", key, given, signed))
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ export async function verifyWebhook(body, headers, secret, opts = {}) {
62
+ const text = typeof body === "string"
63
+ ? body
64
+ : new TextDecoder().decode(body instanceof Uint8Array ? body : new Uint8Array(body));
65
+ const id = header(headers, "webhook-id");
66
+ const timestamp = header(headers, "webhook-timestamp");
67
+ const signature = header(headers, "webhook-signature");
68
+ if (!id || !timestamp || !signature) {
69
+ throw new WebhookVerificationError("Missing webhook-id, webhook-timestamp or webhook-signature header.");
70
+ }
71
+ if (!/^\d+$/.test(timestamp))
72
+ throw new WebhookVerificationError("Bad webhook-timestamp.");
73
+ const now = (opts.now ?? Date.now()) / 1000;
74
+ if (Math.abs(now - Number(timestamp)) > (opts.toleranceSeconds ?? TOLERANCE_SECONDS)) {
75
+ throw new WebhookVerificationError("Timestamp too old or in the future (possible replay).");
76
+ }
77
+ const signed = encoder.encode(`${id}.${timestamp}.${text}`);
78
+ const parts = signature.split(" ").filter(Boolean);
79
+ const secrets = typeof secret === "string" ? [secret] : secret;
80
+ for (const s of secrets) {
81
+ if (await verifiesWith(s, signed, parts)) {
82
+ try {
83
+ return JSON.parse(text);
84
+ }
85
+ catch {
86
+ throw new WebhookVerificationError("The body isn't JSON.");
87
+ }
88
+ }
89
+ }
90
+ throw new WebhookVerificationError("No signature matches this secret.");
91
+ }
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@aivisibilityskill/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "description": "Official TypeScript client for the aivisibilityskill API: see how ChatGPT, Gemini, AI Overviews, Perplexity, Claude and Grok answer about any brand.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://aivisibilityskill.com/sdk",
8
+ "bugs": {
9
+ "email": "support@aivisibilityskill.com"
10
+ },
11
+ "keywords": [
12
+ "ai visibility",
13
+ "geo",
14
+ "generative engine optimization",
15
+ "chatgpt",
16
+ "ai overviews",
17
+ "llm",
18
+ "sdk",
19
+ "api"
20
+ ],
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.18.0",
41
+ "openapi-typescript": "7.13.0",
42
+ "tsx": "4.23.15"
43
+ },
44
+ "scripts": {
45
+ "typecheck": "tsc -p tsconfig.json",
46
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
47
+ "generate": "tsx scripts/generate.ts",
48
+ "check-generated": "tsx scripts/generate.ts --check"
49
+ },
50
+ "types": "./dist/index.d.ts",
51
+ "main": "./dist/index.js"
6
52
  }