webhook-openapi
Version:
Helps you create you own webhook server
193 lines (188 loc) • 4.97 kB
JavaScript
import { OptionalKind } from '@sinclair/typebox';
export * from '@sinclair/typebox';
function mapProperties(to, schema) {
return Object.entries(schema.properties ?? []).map(([key, value]) => {
const { type, description, examples, ...otherProps } = value;
return {
in: to,
name: key,
schema: { type, ...otherProps },
required: schema.required?.includes(key) ?? false
};
});
}
function mapTypeContents(mimeTypes, schema) {
const responses = {};
for (const type of mimeTypes) {
responses[type] = { schema };
}
return responses;
}
class WebhookEvent {
openapi = {};
_ = {
method: "post",
body: null,
bodyHeaders: null,
response: {}
//OpenAPIV3_1.ResponsesObject,
};
// method<Method extends HTTPMethods>(method: Method) {
// this._.method = method.toLocaleLowerCase() as Lowercase<typeof method>;
// return this;
// }
bodyHeaders(schema) {
this._.bodyHeaders = schema;
return this;
}
body(contentTypeOrSchema, schema) {
this._.body = // biome-ignore lint/style/noNonNullAssertion: <explanation>
typeof contentTypeOrSchema === "object" ? contentTypeOrSchema : schema;
return this;
}
response(schema, options) {
this._.response = schema;
return this;
}
}
class Webhook {
openapi;
mimeTypes = {
"application/json": {
serialization: JSON.stringify,
deserialization: (response) => response.json()
},
"text/plain": {
serialization: (data) => data,
deserialization: (response) => response.text()
}
};
hooks = {
beforeRequest: [],
afterResponse: [],
sendError: []
};
constructor() {
this.openapi = {
openapi: "3.1.0",
info: {
title: "Webhook-openapi",
version: "0.0.1"
},
webhooks: {},
paths: {}
};
}
extend(webhook) {
this.hooks.afterResponse.push(...webhook.hooks.afterResponse);
this.hooks.beforeRequest.push(...webhook.hooks.beforeRequest);
this.hooks.sendError.push(...webhook.hooks.sendError);
return this;
}
async runHooks(name, args) {
for (const hook of this.hooks[name]) {
hook(args);
}
}
async runMutationHooks(name, args) {
let data = args;
for await (const hook of this.hooks[name]) {
data = await hook(args);
}
return data;
}
onBeforeRequest(hook) {
this.hooks.beforeRequest.push(hook);
return this;
}
onAfterResponse(hook) {
this.hooks.afterResponse.push(hook);
return this;
}
onSendError(hook) {
this.hooks.sendError.push(hook);
return this;
}
mimeType(mimeType, options) {
this.mimeTypes[mimeType] = options;
return this;
}
event(name, event, params) {
const webhookEvent = event(new WebhookEvent());
this.openapi.webhooks[name] = {
...params,
[webhookEvent._.method]: {
parameters: webhookEvent._.bodyHeaders ? mapProperties("header", webhookEvent._.bodyHeaders) : void 0,
requestBody: {
content: mapTypeContents(
Object.keys(this.mimeTypes),
webhookEvent._.body ?? void 0
),
required: !(OptionalKind in (webhookEvent?._?.body ?? {})),
description: webhookEvent._.body?.description
},
responses: {
"200": {
description: webhookEvent._.response?.description ?? "",
content: mapTypeContents(
Object.keys(this.mimeTypes),
webhookEvent._.response ?? void 0
)
}
}
}
};
return this;
}
async call(url, event, params, requestOptions) {
const custom = requestOptions?.custom ?? {};
const requestInit = {
method: "POST",
headers: new Headers({
"Content-Type": requestOptions?.mimeType ?? "application/json"
}),
...requestOptions
};
await this.runMutationHooks("beforeRequest", {
request: requestInit,
data: params,
event,
url,
custom,
webhook: this
});
if (!requestInit.body)
requestInit.body = await this.mimeTypes[requestOptions?.mimeType ?? "application/json"].serialization(params);
try {
const response = await fetch(url, requestInit);
const mimeType = response.headers.get("content-type")?.split(";")[0].trim();
let data;
if (mimeType)
data = await this.mimeTypes[mimeType ?? "application/text"]?.deserialization(
response
);
this.runHooks("afterResponse", {
response,
request: requestInit,
url,
body: params,
data,
event,
custom,
webhook: this
});
} catch (error) {
if (error instanceof Error)
await this.runHooks("sendError", {
request: requestInit,
data: params,
url,
event,
webhook: this,
error
});
}
return null;
}
}
export { Webhook, WebhookEvent };