@hocuspocus/extension-webhook 4.0.0-rc.1 → 4.0.0-rc.3

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.
@@ -1,36 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- //#region \0rolldown/runtime.js
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
- key = keys[i];
13
- if (!__hasOwnProp.call(to, key) && key !== except) {
14
- __defProp(to, key, {
15
- get: ((k) => from[k]).bind(null, key),
16
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
- });
18
- }
19
- }
20
- }
21
- return to;
22
- };
23
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
- value: mod,
25
- enumerable: true
26
- }) : target, mod));
27
-
28
- //#endregion
29
2
  let node_crypto = require("node:crypto");
30
3
  let _hocuspocus_common = require("@hocuspocus/common");
31
4
  let _hocuspocus_transformer = require("@hocuspocus/transformer");
32
- let axios = require("axios");
33
- axios = __toESM(axios);
34
5
 
35
6
  //#region packages/extension-webhook/src/index.ts
36
7
  let Events = /* @__PURE__ */ function(Events) {
@@ -91,10 +62,21 @@ var Webhook = class {
91
62
  event,
92
63
  payload
93
64
  });
94
- return axios.default.post(this.configuration.url, json, { headers: {
95
- "X-Hocuspocus-Signature-256": this.createSignature(json),
96
- "Content-Type": "application/json"
97
- } });
65
+ const response = await fetch(this.configuration.url, {
66
+ method: "POST",
67
+ body: json,
68
+ headers: {
69
+ "X-Hocuspocus-Signature-256": this.createSignature(json),
70
+ "Content-Type": "application/json"
71
+ }
72
+ });
73
+ if (!response.ok) throw new Error(`Webhook request to ${this.configuration.url} failed with status ${response.status}`);
74
+ const text = await response.text();
75
+ const data = (response.headers.get("content-type") ?? "").includes("application/json") && text ? JSON.parse(text) : text;
76
+ return {
77
+ status: response.status,
78
+ data
79
+ };
98
80
  }
99
81
  /**
100
82
  * onChange hook
@@ -123,13 +105,12 @@ var Webhook = class {
123
105
  async onLoadDocument(data) {
124
106
  if (!this.configuration.events.includes(Events.onCreate)) return;
125
107
  try {
126
- const response = await this.sendRequest(Events.onCreate, {
108
+ const document = (await this.sendRequest(Events.onCreate, {
127
109
  documentName: data.documentName,
128
110
  requestHeaders: data.requestHeaders,
129
111
  requestParameters: Object.fromEntries(data.requestParameters.entries())
130
- });
131
- if (response.status !== 200 || !response.data) return;
132
- const document = typeof response.data === "string" ? JSON.parse(response.data) : response.data;
112
+ })).data;
113
+ if (!document) return;
133
114
  for (const fieldName in document) if (data.document.isEmpty(fieldName)) data.document.merge(this.configuration.transformer.toYdoc(document[fieldName], fieldName));
134
115
  } catch (e) {
135
116
  console.error(`Caught error in extension-webhook: ${e}`);
@@ -141,12 +122,11 @@ var Webhook = class {
141
122
  async onConnect(data) {
142
123
  if (!this.configuration.events.includes(Events.onConnect)) return;
143
124
  try {
144
- const response = await this.sendRequest(Events.onConnect, {
125
+ return (await this.sendRequest(Events.onConnect, {
145
126
  documentName: data.documentName,
146
127
  requestHeaders: data.requestHeaders,
147
128
  requestParameters: Object.fromEntries(data.requestParameters.entries())
148
- });
149
- return typeof response.data === "string" && response.data.length > 0 ? JSON.parse(response.data) : response.data;
129
+ })).data;
150
130
  } catch (e) {
151
131
  console.error(`Caught error in extension-webhook: ${e}`);
152
132
  throw _hocuspocus_common.Forbidden;
@@ -1 +1 @@
1
- {"version":3,"file":"hocuspocus-webhook.cjs","names":["TiptapTransformer","Forbidden"],"sources":["../src/index.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\";\nimport { Forbidden } from \"@hocuspocus/common\";\nimport type {\n\tExtension,\n\tonChangePayload,\n\tonConnectPayload,\n\tonDisconnectPayload,\n\tonLoadDocumentPayload,\n} from \"@hocuspocus/server\";\nimport type { Transformer } from \"@hocuspocus/transformer\";\nimport { TiptapTransformer } from \"@hocuspocus/transformer\";\nimport axios from \"axios\";\nimport type { Doc } from \"yjs\";\n\nexport enum Events {\n\tonChange = \"change\",\n\tonConnect = \"connect\",\n\tonCreate = \"create\",\n\tonDisconnect = \"disconnect\",\n}\n\nexport interface Configuration {\n\tdebounce: number | false | null;\n\tdebounceMaxWait: number;\n\tsecret: string;\n\ttransformer:\n\t\t| Transformer\n\t\t| {\n\t\t\t\ttoYdoc: (document: any) => Doc;\n\t\t\t\tfromYdoc: (document: Doc) => any;\n\t\t };\n\turl: string;\n\tevents: Array<Events>;\n}\n\nexport class Webhook implements Extension {\n\tconfiguration: Configuration = {\n\t\tdebounce: 2000,\n\t\tdebounceMaxWait: 10000,\n\t\tsecret: \"\",\n\t\ttransformer: TiptapTransformer,\n\t\turl: \"\",\n\t\tevents: [Events.onChange],\n\t};\n\n\tdebounced: Map<string, { timeout: NodeJS.Timeout; start: number }> =\n\t\tnew Map();\n\n\t/**\n\t * Constructor\n\t */\n\tconstructor(configuration?: Partial<Configuration>) {\n\t\tthis.configuration = {\n\t\t\t...this.configuration,\n\t\t\t...configuration,\n\t\t};\n\n\t\tif (!this.configuration.url) {\n\t\t\tthrow new Error(\"url is required!\");\n\t\t}\n\t}\n\n\t/**\n\t * Create a signature for the response body\n\t */\n\tcreateSignature(body: string): string {\n\t\tconst hmac = createHmac(\"sha256\", this.configuration.secret);\n\n\t\treturn `sha256=${hmac.update(body).digest(\"hex\")}`;\n\t}\n\n\t/**\n\t * debounce the given function, using the given identifier\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tdebounce(id: string, func: Function) {\n\t\tconst old = this.debounced.get(id);\n\t\tconst start = old?.start || Date.now();\n\n\t\tconst run = () => {\n\t\t\tthis.debounced.delete(id);\n\t\t\tfunc();\n\t\t};\n\n\t\tif (old?.timeout) clearTimeout(old.timeout);\n\t\tif (Date.now() - start >= this.configuration.debounceMaxWait) return run();\n\n\t\tthis.debounced.set(id, {\n\t\t\tstart,\n\t\t\ttimeout: setTimeout(run, <number>this.configuration.debounce),\n\t\t});\n\t}\n\n\t/**\n\t * Send a request to the given url containing the given data\n\t */\n\tasync sendRequest(event: Events, payload: any) {\n\t\tconst json = JSON.stringify({ event, payload });\n\n\t\treturn axios.post(this.configuration.url, json, {\n\t\t\theaders: {\n\t\t\t\t\"X-Hocuspocus-Signature-256\": this.createSignature(json),\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * onChange hook\n\t */\n\tasync onChange(data: onChangePayload) {\n\t\tif (!this.configuration.events.includes(Events.onChange)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst save = async () => {\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest(Events.onChange, {\n\t\t\t\t\tdocument: this.configuration.transformer.fromYdoc(data.document),\n\t\t\t\t\tdocumentName: data.documentName,\n\t\t\t\t\tcontext: data.context,\n\t\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\t\trequestParameters: Object.fromEntries(\n\t\t\t\t\t\tdata.requestParameters.entries(),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\t}\n\t\t};\n\n\t\tif (!this.configuration.debounce) {\n\t\t\treturn save();\n\t\t}\n\n\t\tthis.debounce(data.documentName, save);\n\t}\n\n\t/**\n\t * onLoadDocument hook\n\t */\n\tasync onLoadDocument(data: onLoadDocumentPayload) {\n\t\tif (!this.configuration.events.includes(Events.onCreate)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onCreate, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\tif (response.status !== 200 || !response.data) return;\n\n\t\t\tconst document =\n\t\t\t\ttypeof response.data === \"string\"\n\t\t\t\t\t? JSON.parse(response.data)\n\t\t\t\t\t: response.data;\n\n\t\t\t// eslint-disable-next-line guard-for-in,no-restricted-syntax\n\t\t\tfor (const fieldName in document) {\n\t\t\t\tif (data.document.isEmpty(fieldName)) {\n\t\t\t\t\tdata.document.merge(\n\t\t\t\t\t\tthis.configuration.transformer.toYdoc(\n\t\t\t\t\t\t\tdocument[fieldName],\n\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n\n\t/**\n\t * onConnect hook\n\t */\n\tasync onConnect(data: onConnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onConnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onConnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\treturn typeof response.data === \"string\" && response.data.length > 0\n\t\t\t\t? JSON.parse(response.data)\n\t\t\t\t: response.data;\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\tthrow Forbidden;\n\t\t}\n\t}\n\n\tasync onDisconnect(data: onDisconnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onDisconnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.sendRequest(Events.onDisconnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t\tcontext: data.context,\n\t\t\t});\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hocuspocus-webhook.cjs","names":["TiptapTransformer","Forbidden"],"sources":["../src/index.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\";\nimport { Forbidden } from \"@hocuspocus/common\";\nimport type {\n\tExtension,\n\tonChangePayload,\n\tonConnectPayload,\n\tonDisconnectPayload,\n\tonLoadDocumentPayload,\n} from \"@hocuspocus/server\";\nimport type { Transformer } from \"@hocuspocus/transformer\";\nimport { TiptapTransformer } from \"@hocuspocus/transformer\";\nimport type { Doc } from \"yjs\";\n\nexport enum Events {\n\tonChange = \"change\",\n\tonConnect = \"connect\",\n\tonCreate = \"create\",\n\tonDisconnect = \"disconnect\",\n}\n\nexport interface Configuration {\n\tdebounce: number | false | null;\n\tdebounceMaxWait: number;\n\tsecret: string;\n\ttransformer:\n\t\t| Transformer\n\t\t| {\n\t\t\t\ttoYdoc: (document: any) => Doc;\n\t\t\t\tfromYdoc: (document: Doc) => any;\n\t\t };\n\turl: string;\n\tevents: Array<Events>;\n}\n\nexport class Webhook implements Extension {\n\tconfiguration: Configuration = {\n\t\tdebounce: 2000,\n\t\tdebounceMaxWait: 10000,\n\t\tsecret: \"\",\n\t\ttransformer: TiptapTransformer,\n\t\turl: \"\",\n\t\tevents: [Events.onChange],\n\t};\n\n\tdebounced: Map<string, { timeout: NodeJS.Timeout; start: number }> =\n\t\tnew Map();\n\n\t/**\n\t * Constructor\n\t */\n\tconstructor(configuration?: Partial<Configuration>) {\n\t\tthis.configuration = {\n\t\t\t...this.configuration,\n\t\t\t...configuration,\n\t\t};\n\n\t\tif (!this.configuration.url) {\n\t\t\tthrow new Error(\"url is required!\");\n\t\t}\n\t}\n\n\t/**\n\t * Create a signature for the response body\n\t */\n\tcreateSignature(body: string): string {\n\t\tconst hmac = createHmac(\"sha256\", this.configuration.secret);\n\n\t\treturn `sha256=${hmac.update(body).digest(\"hex\")}`;\n\t}\n\n\t/**\n\t * debounce the given function, using the given identifier\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tdebounce(id: string, func: Function) {\n\t\tconst old = this.debounced.get(id);\n\t\tconst start = old?.start || Date.now();\n\n\t\tconst run = () => {\n\t\t\tthis.debounced.delete(id);\n\t\t\tfunc();\n\t\t};\n\n\t\tif (old?.timeout) clearTimeout(old.timeout);\n\t\tif (Date.now() - start >= this.configuration.debounceMaxWait) return run();\n\n\t\tthis.debounced.set(id, {\n\t\t\tstart,\n\t\t\ttimeout: setTimeout(run, <number>this.configuration.debounce),\n\t\t});\n\t}\n\n\t/**\n\t * Send a request to the given url containing the given data\n\t */\n\tasync sendRequest(event: Events, payload: any) {\n\t\tconst json = JSON.stringify({ event, payload });\n\n\t\tconst response = await fetch(this.configuration.url, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: json,\n\t\t\theaders: {\n\t\t\t\t\"X-Hocuspocus-Signature-256\": this.createSignature(json),\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Webhook request to ${this.configuration.url} failed with status ${response.status}`,\n\t\t\t);\n\t\t}\n\n\t\tconst text = await response.text();\n\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\tconst data =\n\t\t\tcontentType.includes(\"application/json\") && text\n\t\t\t\t? JSON.parse(text)\n\t\t\t\t: text;\n\n\t\treturn { status: response.status, data };\n\t}\n\n\t/**\n\t * onChange hook\n\t */\n\tasync onChange(data: onChangePayload) {\n\t\tif (!this.configuration.events.includes(Events.onChange)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst save = async () => {\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest(Events.onChange, {\n\t\t\t\t\tdocument: this.configuration.transformer.fromYdoc(data.document),\n\t\t\t\t\tdocumentName: data.documentName,\n\t\t\t\t\tcontext: data.context,\n\t\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\t\trequestParameters: Object.fromEntries(\n\t\t\t\t\t\tdata.requestParameters.entries(),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\t}\n\t\t};\n\n\t\tif (!this.configuration.debounce) {\n\t\t\treturn save();\n\t\t}\n\n\t\tthis.debounce(data.documentName, save);\n\t}\n\n\t/**\n\t * onLoadDocument hook\n\t */\n\tasync onLoadDocument(data: onLoadDocumentPayload) {\n\t\tif (!this.configuration.events.includes(Events.onCreate)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onCreate, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\tconst document = response.data;\n\t\t\tif (!document) return;\n\n\t\t\t// eslint-disable-next-line guard-for-in,no-restricted-syntax\n\t\t\tfor (const fieldName in document) {\n\t\t\t\tif (data.document.isEmpty(fieldName)) {\n\t\t\t\t\tdata.document.merge(\n\t\t\t\t\t\tthis.configuration.transformer.toYdoc(\n\t\t\t\t\t\t\tdocument[fieldName],\n\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n\n\t/**\n\t * onConnect hook\n\t */\n\tasync onConnect(data: onConnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onConnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onConnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\treturn response.data;\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\tthrow Forbidden;\n\t\t}\n\t}\n\n\tasync onDisconnect(data: onDisconnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onDisconnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.sendRequest(Events.onDisconnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t\tcontext: data.context,\n\t\t\t});\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import { createHmac } from "node:crypto";
2
2
  import { Forbidden } from "@hocuspocus/common";
3
3
  import { TiptapTransformer } from "@hocuspocus/transformer";
4
- import axios from "axios";
5
4
 
6
5
  //#region packages/extension-webhook/src/index.ts
7
6
  let Events = /* @__PURE__ */ function(Events) {
@@ -62,10 +61,21 @@ var Webhook = class {
62
61
  event,
63
62
  payload
64
63
  });
65
- return axios.post(this.configuration.url, json, { headers: {
66
- "X-Hocuspocus-Signature-256": this.createSignature(json),
67
- "Content-Type": "application/json"
68
- } });
64
+ const response = await fetch(this.configuration.url, {
65
+ method: "POST",
66
+ body: json,
67
+ headers: {
68
+ "X-Hocuspocus-Signature-256": this.createSignature(json),
69
+ "Content-Type": "application/json"
70
+ }
71
+ });
72
+ if (!response.ok) throw new Error(`Webhook request to ${this.configuration.url} failed with status ${response.status}`);
73
+ const text = await response.text();
74
+ const data = (response.headers.get("content-type") ?? "").includes("application/json") && text ? JSON.parse(text) : text;
75
+ return {
76
+ status: response.status,
77
+ data
78
+ };
69
79
  }
70
80
  /**
71
81
  * onChange hook
@@ -94,13 +104,12 @@ var Webhook = class {
94
104
  async onLoadDocument(data) {
95
105
  if (!this.configuration.events.includes(Events.onCreate)) return;
96
106
  try {
97
- const response = await this.sendRequest(Events.onCreate, {
107
+ const document = (await this.sendRequest(Events.onCreate, {
98
108
  documentName: data.documentName,
99
109
  requestHeaders: data.requestHeaders,
100
110
  requestParameters: Object.fromEntries(data.requestParameters.entries())
101
- });
102
- if (response.status !== 200 || !response.data) return;
103
- const document = typeof response.data === "string" ? JSON.parse(response.data) : response.data;
111
+ })).data;
112
+ if (!document) return;
104
113
  for (const fieldName in document) if (data.document.isEmpty(fieldName)) data.document.merge(this.configuration.transformer.toYdoc(document[fieldName], fieldName));
105
114
  } catch (e) {
106
115
  console.error(`Caught error in extension-webhook: ${e}`);
@@ -112,12 +121,11 @@ var Webhook = class {
112
121
  async onConnect(data) {
113
122
  if (!this.configuration.events.includes(Events.onConnect)) return;
114
123
  try {
115
- const response = await this.sendRequest(Events.onConnect, {
124
+ return (await this.sendRequest(Events.onConnect, {
116
125
  documentName: data.documentName,
117
126
  requestHeaders: data.requestHeaders,
118
127
  requestParameters: Object.fromEntries(data.requestParameters.entries())
119
- });
120
- return typeof response.data === "string" && response.data.length > 0 ? JSON.parse(response.data) : response.data;
128
+ })).data;
121
129
  } catch (e) {
122
130
  console.error(`Caught error in extension-webhook: ${e}`);
123
131
  throw Forbidden;
@@ -1 +1 @@
1
- {"version":3,"file":"hocuspocus-webhook.esm.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\";\nimport { Forbidden } from \"@hocuspocus/common\";\nimport type {\n\tExtension,\n\tonChangePayload,\n\tonConnectPayload,\n\tonDisconnectPayload,\n\tonLoadDocumentPayload,\n} from \"@hocuspocus/server\";\nimport type { Transformer } from \"@hocuspocus/transformer\";\nimport { TiptapTransformer } from \"@hocuspocus/transformer\";\nimport axios from \"axios\";\nimport type { Doc } from \"yjs\";\n\nexport enum Events {\n\tonChange = \"change\",\n\tonConnect = \"connect\",\n\tonCreate = \"create\",\n\tonDisconnect = \"disconnect\",\n}\n\nexport interface Configuration {\n\tdebounce: number | false | null;\n\tdebounceMaxWait: number;\n\tsecret: string;\n\ttransformer:\n\t\t| Transformer\n\t\t| {\n\t\t\t\ttoYdoc: (document: any) => Doc;\n\t\t\t\tfromYdoc: (document: Doc) => any;\n\t\t };\n\turl: string;\n\tevents: Array<Events>;\n}\n\nexport class Webhook implements Extension {\n\tconfiguration: Configuration = {\n\t\tdebounce: 2000,\n\t\tdebounceMaxWait: 10000,\n\t\tsecret: \"\",\n\t\ttransformer: TiptapTransformer,\n\t\turl: \"\",\n\t\tevents: [Events.onChange],\n\t};\n\n\tdebounced: Map<string, { timeout: NodeJS.Timeout; start: number }> =\n\t\tnew Map();\n\n\t/**\n\t * Constructor\n\t */\n\tconstructor(configuration?: Partial<Configuration>) {\n\t\tthis.configuration = {\n\t\t\t...this.configuration,\n\t\t\t...configuration,\n\t\t};\n\n\t\tif (!this.configuration.url) {\n\t\t\tthrow new Error(\"url is required!\");\n\t\t}\n\t}\n\n\t/**\n\t * Create a signature for the response body\n\t */\n\tcreateSignature(body: string): string {\n\t\tconst hmac = createHmac(\"sha256\", this.configuration.secret);\n\n\t\treturn `sha256=${hmac.update(body).digest(\"hex\")}`;\n\t}\n\n\t/**\n\t * debounce the given function, using the given identifier\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tdebounce(id: string, func: Function) {\n\t\tconst old = this.debounced.get(id);\n\t\tconst start = old?.start || Date.now();\n\n\t\tconst run = () => {\n\t\t\tthis.debounced.delete(id);\n\t\t\tfunc();\n\t\t};\n\n\t\tif (old?.timeout) clearTimeout(old.timeout);\n\t\tif (Date.now() - start >= this.configuration.debounceMaxWait) return run();\n\n\t\tthis.debounced.set(id, {\n\t\t\tstart,\n\t\t\ttimeout: setTimeout(run, <number>this.configuration.debounce),\n\t\t});\n\t}\n\n\t/**\n\t * Send a request to the given url containing the given data\n\t */\n\tasync sendRequest(event: Events, payload: any) {\n\t\tconst json = JSON.stringify({ event, payload });\n\n\t\treturn axios.post(this.configuration.url, json, {\n\t\t\theaders: {\n\t\t\t\t\"X-Hocuspocus-Signature-256\": this.createSignature(json),\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * onChange hook\n\t */\n\tasync onChange(data: onChangePayload) {\n\t\tif (!this.configuration.events.includes(Events.onChange)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst save = async () => {\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest(Events.onChange, {\n\t\t\t\t\tdocument: this.configuration.transformer.fromYdoc(data.document),\n\t\t\t\t\tdocumentName: data.documentName,\n\t\t\t\t\tcontext: data.context,\n\t\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\t\trequestParameters: Object.fromEntries(\n\t\t\t\t\t\tdata.requestParameters.entries(),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\t}\n\t\t};\n\n\t\tif (!this.configuration.debounce) {\n\t\t\treturn save();\n\t\t}\n\n\t\tthis.debounce(data.documentName, save);\n\t}\n\n\t/**\n\t * onLoadDocument hook\n\t */\n\tasync onLoadDocument(data: onLoadDocumentPayload) {\n\t\tif (!this.configuration.events.includes(Events.onCreate)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onCreate, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\tif (response.status !== 200 || !response.data) return;\n\n\t\t\tconst document =\n\t\t\t\ttypeof response.data === \"string\"\n\t\t\t\t\t? JSON.parse(response.data)\n\t\t\t\t\t: response.data;\n\n\t\t\t// eslint-disable-next-line guard-for-in,no-restricted-syntax\n\t\t\tfor (const fieldName in document) {\n\t\t\t\tif (data.document.isEmpty(fieldName)) {\n\t\t\t\t\tdata.document.merge(\n\t\t\t\t\t\tthis.configuration.transformer.toYdoc(\n\t\t\t\t\t\t\tdocument[fieldName],\n\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n\n\t/**\n\t * onConnect hook\n\t */\n\tasync onConnect(data: onConnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onConnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onConnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\treturn typeof response.data === \"string\" && response.data.length > 0\n\t\t\t\t? JSON.parse(response.data)\n\t\t\t\t: response.data;\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\tthrow Forbidden;\n\t\t}\n\t}\n\n\tasync onDisconnect(data: onDisconnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onDisconnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.sendRequest(Events.onDisconnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t\tcontext: data.context,\n\t\t\t});\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hocuspocus-webhook.esm.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\";\nimport { Forbidden } from \"@hocuspocus/common\";\nimport type {\n\tExtension,\n\tonChangePayload,\n\tonConnectPayload,\n\tonDisconnectPayload,\n\tonLoadDocumentPayload,\n} from \"@hocuspocus/server\";\nimport type { Transformer } from \"@hocuspocus/transformer\";\nimport { TiptapTransformer } from \"@hocuspocus/transformer\";\nimport type { Doc } from \"yjs\";\n\nexport enum Events {\n\tonChange = \"change\",\n\tonConnect = \"connect\",\n\tonCreate = \"create\",\n\tonDisconnect = \"disconnect\",\n}\n\nexport interface Configuration {\n\tdebounce: number | false | null;\n\tdebounceMaxWait: number;\n\tsecret: string;\n\ttransformer:\n\t\t| Transformer\n\t\t| {\n\t\t\t\ttoYdoc: (document: any) => Doc;\n\t\t\t\tfromYdoc: (document: Doc) => any;\n\t\t };\n\turl: string;\n\tevents: Array<Events>;\n}\n\nexport class Webhook implements Extension {\n\tconfiguration: Configuration = {\n\t\tdebounce: 2000,\n\t\tdebounceMaxWait: 10000,\n\t\tsecret: \"\",\n\t\ttransformer: TiptapTransformer,\n\t\turl: \"\",\n\t\tevents: [Events.onChange],\n\t};\n\n\tdebounced: Map<string, { timeout: NodeJS.Timeout; start: number }> =\n\t\tnew Map();\n\n\t/**\n\t * Constructor\n\t */\n\tconstructor(configuration?: Partial<Configuration>) {\n\t\tthis.configuration = {\n\t\t\t...this.configuration,\n\t\t\t...configuration,\n\t\t};\n\n\t\tif (!this.configuration.url) {\n\t\t\tthrow new Error(\"url is required!\");\n\t\t}\n\t}\n\n\t/**\n\t * Create a signature for the response body\n\t */\n\tcreateSignature(body: string): string {\n\t\tconst hmac = createHmac(\"sha256\", this.configuration.secret);\n\n\t\treturn `sha256=${hmac.update(body).digest(\"hex\")}`;\n\t}\n\n\t/**\n\t * debounce the given function, using the given identifier\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\tdebounce(id: string, func: Function) {\n\t\tconst old = this.debounced.get(id);\n\t\tconst start = old?.start || Date.now();\n\n\t\tconst run = () => {\n\t\t\tthis.debounced.delete(id);\n\t\t\tfunc();\n\t\t};\n\n\t\tif (old?.timeout) clearTimeout(old.timeout);\n\t\tif (Date.now() - start >= this.configuration.debounceMaxWait) return run();\n\n\t\tthis.debounced.set(id, {\n\t\t\tstart,\n\t\t\ttimeout: setTimeout(run, <number>this.configuration.debounce),\n\t\t});\n\t}\n\n\t/**\n\t * Send a request to the given url containing the given data\n\t */\n\tasync sendRequest(event: Events, payload: any) {\n\t\tconst json = JSON.stringify({ event, payload });\n\n\t\tconst response = await fetch(this.configuration.url, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: json,\n\t\t\theaders: {\n\t\t\t\t\"X-Hocuspocus-Signature-256\": this.createSignature(json),\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Webhook request to ${this.configuration.url} failed with status ${response.status}`,\n\t\t\t);\n\t\t}\n\n\t\tconst text = await response.text();\n\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\tconst data =\n\t\t\tcontentType.includes(\"application/json\") && text\n\t\t\t\t? JSON.parse(text)\n\t\t\t\t: text;\n\n\t\treturn { status: response.status, data };\n\t}\n\n\t/**\n\t * onChange hook\n\t */\n\tasync onChange(data: onChangePayload) {\n\t\tif (!this.configuration.events.includes(Events.onChange)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst save = async () => {\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest(Events.onChange, {\n\t\t\t\t\tdocument: this.configuration.transformer.fromYdoc(data.document),\n\t\t\t\t\tdocumentName: data.documentName,\n\t\t\t\t\tcontext: data.context,\n\t\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\t\trequestParameters: Object.fromEntries(\n\t\t\t\t\t\tdata.requestParameters.entries(),\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\t}\n\t\t};\n\n\t\tif (!this.configuration.debounce) {\n\t\t\treturn save();\n\t\t}\n\n\t\tthis.debounce(data.documentName, save);\n\t}\n\n\t/**\n\t * onLoadDocument hook\n\t */\n\tasync onLoadDocument(data: onLoadDocumentPayload) {\n\t\tif (!this.configuration.events.includes(Events.onCreate)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onCreate, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\tconst document = response.data;\n\t\t\tif (!document) return;\n\n\t\t\t// eslint-disable-next-line guard-for-in,no-restricted-syntax\n\t\t\tfor (const fieldName in document) {\n\t\t\t\tif (data.document.isEmpty(fieldName)) {\n\t\t\t\t\tdata.document.merge(\n\t\t\t\t\t\tthis.configuration.transformer.toYdoc(\n\t\t\t\t\t\t\tdocument[fieldName],\n\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n\n\t/**\n\t * onConnect hook\n\t */\n\tasync onConnect(data: onConnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onConnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst response = await this.sendRequest(Events.onConnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t});\n\n\t\t\treturn response.data;\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t\tthrow Forbidden;\n\t\t}\n\t}\n\n\tasync onDisconnect(data: onDisconnectPayload) {\n\t\tif (!this.configuration.events.includes(Events.onDisconnect)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.sendRequest(Events.onDisconnect, {\n\t\t\t\tdocumentName: data.documentName,\n\t\t\t\trequestHeaders: data.requestHeaders,\n\t\t\t\trequestParameters: Object.fromEntries(data.requestParameters.entries()),\n\t\t\t\tcontext: data.context,\n\t\t\t});\n\t\t} catch (e) {\n\t\t\tconsole.error(`Caught error in extension-webhook: ${e}`);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import * as axios from "axios";
2
1
  import { Extension, onChangePayload, onConnectPayload, onDisconnectPayload, onLoadDocumentPayload } from "@hocuspocus/server";
3
2
  import { Transformer } from "@hocuspocus/transformer";
4
3
  import { Doc } from "yjs";
@@ -42,7 +41,10 @@ declare class Webhook implements Extension {
42
41
  /**
43
42
  * Send a request to the given url containing the given data
44
43
  */
45
- sendRequest(event: Events, payload: any): Promise<axios.AxiosResponse<any, any, {}>>;
44
+ sendRequest(event: Events, payload: any): Promise<{
45
+ status: number;
46
+ data: any;
47
+ }>;
46
48
  /**
47
49
  * onChange hook
48
50
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hocuspocus/extension-webhook",
3
- "version": "4.0.0-rc.1",
3
+ "version": "4.0.0-rc.3",
4
4
  "description": "hocuspocus webhook extension",
5
5
  "homepage": "https://hocuspocus.dev",
6
6
  "keywords": [
@@ -30,15 +30,14 @@
30
30
  "dist"
31
31
  ],
32
32
  "dependencies": {
33
- "@hocuspocus/common": "^4.0.0-rc.1",
34
- "@hocuspocus/server": "^4.0.0-rc.1",
35
- "@hocuspocus/transformer": "^4.0.0-rc.1",
36
- "axios": "^1.12.2"
33
+ "@hocuspocus/common": "^4.0.0-rc.3",
34
+ "@hocuspocus/server": "^4.0.0-rc.3",
35
+ "@hocuspocus/transformer": "^4.0.0-rc.3"
37
36
  },
38
37
  "peerDependencies": {
39
38
  "yjs": "^13.6.8"
40
39
  },
41
- "gitHead": "730ac02724fcc44bbbfcc5849df4b0cafb996450",
40
+ "gitHead": "842287de4e4b9ccd87a70942d6b7bfcd4e6ffde7",
42
41
  "repository": {
43
42
  "url": "https://github.com/ueberdosis/hocuspocus"
44
43
  },
package/src/index.ts CHANGED
@@ -9,7 +9,6 @@ import type {
9
9
  } from "@hocuspocus/server";
10
10
  import type { Transformer } from "@hocuspocus/transformer";
11
11
  import { TiptapTransformer } from "@hocuspocus/transformer";
12
- import axios from "axios";
13
12
  import type { Doc } from "yjs";
14
13
 
15
14
  export enum Events {
@@ -97,12 +96,29 @@ export class Webhook implements Extension {
97
96
  async sendRequest(event: Events, payload: any) {
98
97
  const json = JSON.stringify({ event, payload });
99
98
 
100
- return axios.post(this.configuration.url, json, {
99
+ const response = await fetch(this.configuration.url, {
100
+ method: "POST",
101
+ body: json,
101
102
  headers: {
102
103
  "X-Hocuspocus-Signature-256": this.createSignature(json),
103
104
  "Content-Type": "application/json",
104
105
  },
105
106
  });
107
+
108
+ if (!response.ok) {
109
+ throw new Error(
110
+ `Webhook request to ${this.configuration.url} failed with status ${response.status}`,
111
+ );
112
+ }
113
+
114
+ const text = await response.text();
115
+ const contentType = response.headers.get("content-type") ?? "";
116
+ const data =
117
+ contentType.includes("application/json") && text
118
+ ? JSON.parse(text)
119
+ : text;
120
+
121
+ return { status: response.status, data };
106
122
  }
107
123
 
108
124
  /**
@@ -151,12 +167,8 @@ export class Webhook implements Extension {
151
167
  requestParameters: Object.fromEntries(data.requestParameters.entries()),
152
168
  });
153
169
 
154
- if (response.status !== 200 || !response.data) return;
155
-
156
- const document =
157
- typeof response.data === "string"
158
- ? JSON.parse(response.data)
159
- : response.data;
170
+ const document = response.data;
171
+ if (!document) return;
160
172
 
161
173
  // eslint-disable-next-line guard-for-in,no-restricted-syntax
162
174
  for (const fieldName in document) {
@@ -189,9 +201,7 @@ export class Webhook implements Extension {
189
201
  requestParameters: Object.fromEntries(data.requestParameters.entries()),
190
202
  });
191
203
 
192
- return typeof response.data === "string" && response.data.length > 0
193
- ? JSON.parse(response.data)
194
- : response.data;
204
+ return response.data;
195
205
  } catch (e) {
196
206
  console.error(`Caught error in extension-webhook: ${e}`);
197
207
  throw Forbidden;