n8n-nodes-pocketbase 2.3.1 → 2.3.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,239 +1,669 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PocketbaseTrigger = void 0;
4
- const n8n_workflow_1 = require("n8n-workflow");
5
- const eventsource_1 = require("eventsource");
6
- const LoadOptions_1 = require("../Common/LoadOptions");
7
- class PocketbaseTrigger {
8
- constructor() {
9
- this.description = {
10
- displayName: "Pocketbase (Beta) Trigger",
11
- name: "pocketbaseTrigger",
12
- icon: { light: "file:pocketbaseTrigger.svg", dark: "file:pocketbaseTrigger.dark.svg" },
13
- group: ["trigger"],
14
- version: 1,
15
- description: "Handle Pocketbase events via SSE (Beta)",
16
- defaults: {
17
- name: "Pocketbase Trigger",
18
- },
19
- inputs: [],
20
- outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
21
- credentials: [
22
- {
23
- name: "pocketbaseHttpApi",
24
- required: true,
25
- },
26
- ],
27
- properties: [
28
- {
29
- displayName: "Collection Name",
30
- name: "collection",
31
- type: "options",
32
- typeOptions: {
33
- loadOptionsMethod: "getCollections",
34
- },
35
- default: "",
36
- required: true,
37
- description: "The name of the collection to watch for changes",
38
- },
39
- {
40
- displayName: "Events",
41
- name: "events",
42
- type: "multiOptions",
43
- options: [
44
- {
45
- name: "Create",
46
- value: "create",
47
- },
48
- {
49
- name: "Update",
50
- value: "update",
51
- },
52
- {
53
- name: "Delete",
54
- value: "delete",
55
- },
56
- ],
57
- default: ["create", "update", "delete"],
58
- required: true,
59
- description: "The events to trigger the node",
60
- },
61
- ],
62
- };
63
- this.methods = {
64
- loadOptions: LoadOptions_1.LoadOptions,
65
- };
66
- }
67
- async trigger() {
68
- const credentials = await this.getCredentials("pocketbaseHttpApi");
69
- const baseUrl = credentials.url.replace(/\/$/, "");
70
- const collection = this.getNodeParameter("collection");
71
- const events = this.getNodeParameter("events");
72
- const { closeFunction } = subscribeToPocketbaseSSE.call(this, baseUrl, collection, events);
73
- return {
74
- closeFunction,
75
- manualTriggerFunction: async () => {
76
- const sampleData = {
77
- __action: "create",
78
- id: "sample-id",
79
- collectionId: "sample-collection-id",
80
- collectionName: collection,
81
- created: new Date().toISOString(),
82
- updated: new Date().toISOString(),
83
- };
84
- this.emit([this.helpers.returnJsonArray(sampleData)]);
85
- },
86
- };
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __commonJS = (cb, mod) => function __require() {
4
+ try {
5
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
6
+ } catch (e) {
7
+ throw mod = 0, e;
8
+ }
9
+ };
10
+
11
+ // node_modules/eventsource-parser/dist/index.cjs
12
+ var require_dist = __commonJS({
13
+ "node_modules/eventsource-parser/dist/index.cjs"(exports2) {
14
+ "use strict";
15
+ Object.defineProperty(exports2, "__esModule", { value: true });
16
+ var ParseError = class extends Error {
17
+ constructor(message, options) {
18
+ super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
19
+ }
20
+ };
21
+ function noop(_arg) {
87
22
  }
88
- }
89
- exports.PocketbaseTrigger = PocketbaseTrigger;
90
- function subscribeToPocketbaseSSE(baseUrl, collection, events) {
91
- let es = null;
92
- let isClosed = false;
93
- let isReconnecting = false;
94
- let reconnectTimeout = null;
95
- let stabilityTimer = null;
96
- let reconnectAttempts = 0;
97
- const MAX_RECONNECT_ATTEMPTS = 50;
98
- let consecutiveFailures = 0;
99
- const onConnect = async (e) => {
100
- try {
101
- const data = JSON.parse(e.data);
102
- const clientId = data.clientId;
103
- await this.helpers.httpRequestWithAuthentication.call(this, "pocketbaseHttpApi", {
104
- method: "POST",
105
- url: `${baseUrl}/api/realtime`,
106
- body: {
107
- clientId,
108
- subscriptions: [collection],
109
- },
110
- });
111
- if (stabilityTimer)
112
- clearTimeout(stabilityTimer);
113
- stabilityTimer = setTimeout(() => {
114
- reconnectAttempts = 0;
115
- consecutiveFailures = 0;
116
- stabilityTimer = null;
117
- }, 5000);
118
- }
119
- catch (error) {
120
- this.logger.error("Failed to connect to PocketBase SSE", { error, collection });
121
- const normalizedError = error instanceof Error ? error : new Error(String(error || "Unknown error during connect"));
122
- if (consecutiveFailures === 0) {
123
- this.emitError(normalizedError);
124
- }
125
- consecutiveFailures++;
126
- reconnect();
23
+ function createParser(callbacks) {
24
+ if (typeof callbacks == "function")
25
+ throw new TypeError(
26
+ "`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?"
27
+ );
28
+ const { onEvent = noop, onError = noop, onRetry = noop, onComment } = callbacks;
29
+ let incompleteLine = "", isFirstChunk = true, id, data = "", eventType = "";
30
+ function feed(newChunk) {
31
+ const chunk = isFirstChunk ? newChunk.replace(/^\xEF\xBB\xBF/, "") : newChunk, [complete, incomplete] = splitLines(`${incompleteLine}${chunk}`);
32
+ for (const line of complete)
33
+ parseLine(line);
34
+ incompleteLine = incomplete, isFirstChunk = false;
35
+ }
36
+ function parseLine(line) {
37
+ if (line === "") {
38
+ dispatchEvent();
39
+ return;
127
40
  }
128
- };
129
- const onError = (error) => {
130
- if (stabilityTimer) {
131
- clearTimeout(stabilityTimer);
132
- stabilityTimer = null;
41
+ if (line.startsWith(":")) {
42
+ onComment && onComment(line.slice(line.startsWith(": ") ? 2 : 1));
43
+ return;
133
44
  }
134
- this.logger.error("PocketBase SSE connection failure", {
135
- error,
136
- baseUrl,
137
- });
138
- const normalizedError = new Error((error && error.message) || "PocketBase SSE connection failure");
139
- if (error && error.code)
140
- normalizedError.code = error.code;
141
- if (error && error.status)
142
- normalizedError.status = error.status;
143
- normalizedError.originalErrorEvent = error;
144
- if (consecutiveFailures === 0) {
145
- this.emitError(normalizedError);
45
+ const fieldSeparatorIndex = line.indexOf(":");
46
+ if (fieldSeparatorIndex !== -1) {
47
+ const field = line.slice(0, fieldSeparatorIndex), offset = line[fieldSeparatorIndex + 1] === " " ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
48
+ processField(field, value, line);
49
+ return;
146
50
  }
147
- consecutiveFailures++;
148
- reconnect();
149
- };
150
- const onMessage = (e) => {
151
- var _a;
152
- try {
153
- const data = JSON.parse(e.data);
154
- if (events.includes(data.action) && data.record) {
155
- const output = {
156
- ...data.record,
157
- };
158
- if ("action" in output) {
159
- output.__original_action = output.action;
160
- }
161
- output.__action = data.action;
162
- this.emit([this.helpers.returnJsonArray(output)]);
163
- }
51
+ processField(line, "", line);
52
+ }
53
+ function processField(field, value, line) {
54
+ switch (field) {
55
+ case "event":
56
+ eventType = value;
57
+ break;
58
+ case "data":
59
+ data = `${data}${value}
60
+ `;
61
+ break;
62
+ case "id":
63
+ id = value.includes("\0") ? void 0 : value;
64
+ break;
65
+ case "retry":
66
+ /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
67
+ new ParseError(`Invalid \`retry\` value: "${value}"`, {
68
+ type: "invalid-retry",
69
+ value,
70
+ line
71
+ })
72
+ );
73
+ break;
74
+ default:
75
+ onError(
76
+ new ParseError(
77
+ `Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
78
+ { type: "unknown-field", field, value, line }
79
+ )
80
+ );
81
+ break;
164
82
  }
165
- catch (error) {
166
- const rawData = String((_a = e === null || e === void 0 ? void 0 : e.data) !== null && _a !== void 0 ? _a : "");
167
- const redactedPreview = rawData
168
- .replace(/"(password|token|secret|passwordConfirm|apiKey|accessToken|authorization|bearer)":\s*"(?:[^"\\]|\\.)*"/gi, '"$1": "[REDACTED]"')
169
- .replace(/"email":\s*"(?:[^"\\]|\\.)*"/gi, '"email": "[REDACTED]"')
170
- .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[REDACTED]")
171
- .substring(0, 200);
172
- this.logger.error("Failed to parse PocketBase SSE message", {
173
- error,
174
- redactedPreview,
175
- collection,
176
- });
83
+ }
84
+ function dispatchEvent() {
85
+ data.length > 0 && onEvent({
86
+ id,
87
+ event: eventType || void 0,
88
+ // If the data buffer's last character is a U+000A LINE FEED (LF) character,
89
+ // then remove the last character from the data buffer.
90
+ data: data.endsWith(`
91
+ `) ? data.slice(0, -1) : data
92
+ }), id = void 0, data = "", eventType = "";
93
+ }
94
+ function reset(options = {}) {
95
+ incompleteLine && options.consume && parseLine(incompleteLine), isFirstChunk = true, id = void 0, data = "", eventType = "", incompleteLine = "";
96
+ }
97
+ return { feed, reset };
98
+ }
99
+ function splitLines(chunk) {
100
+ const lines = [];
101
+ let incompleteLine = "", searchIndex = 0;
102
+ for (; searchIndex < chunk.length; ) {
103
+ const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
104
+ `, searchIndex);
105
+ let lineEnd = -1;
106
+ if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = Math.min(crIndex, lfIndex) : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) {
107
+ incompleteLine = chunk.slice(searchIndex);
108
+ break;
109
+ } else {
110
+ const line = chunk.slice(searchIndex, lineEnd);
111
+ lines.push(line), searchIndex = lineEnd + 1, chunk[searchIndex - 1] === "\r" && chunk[searchIndex] === `
112
+ ` && searchIndex++;
177
113
  }
114
+ }
115
+ return [lines, incompleteLine];
116
+ }
117
+ exports2.ParseError = ParseError;
118
+ exports2.createParser = createParser;
119
+ }
120
+ });
121
+
122
+ // node_modules/eventsource/dist/index.cjs
123
+ var require_dist2 = __commonJS({
124
+ "node_modules/eventsource/dist/index.cjs"(exports2) {
125
+ "use strict";
126
+ Object.defineProperty(exports2, "__esModule", { value: true });
127
+ var eventsourceParser = require_dist();
128
+ var ErrorEvent = class extends Event {
129
+ /**
130
+ * Constructs a new `ErrorEvent` instance. This is typically not called directly,
131
+ * but rather emitted by the `EventSource` object when an error occurs.
132
+ *
133
+ * @param type - The type of the event (should be "error")
134
+ * @param errorEventInitDict - Optional properties to include in the error event
135
+ */
136
+ constructor(type, errorEventInitDict) {
137
+ var _a, _b;
138
+ super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;
139
+ }
140
+ /**
141
+ * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
142
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
143
+ * we explicitly include the properties in the `inspect` method.
144
+ *
145
+ * This is automatically called by Node.js when you `console.log` an instance of this class.
146
+ *
147
+ * @param _depth - The current depth
148
+ * @param options - The options passed to `util.inspect`
149
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
150
+ * @returns A string representation of the error
151
+ */
152
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
153
+ return inspect(inspectableError(this), options);
154
+ }
155
+ /**
156
+ * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
157
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
158
+ * we explicitly include the properties in the `inspect` method.
159
+ *
160
+ * This is automatically called by Deno when you `console.log` an instance of this class.
161
+ *
162
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
163
+ * @param options - The options passed to `Deno.inspect`
164
+ * @returns A string representation of the error
165
+ */
166
+ [/* @__PURE__ */ Symbol.for("Deno.customInspect")](inspect, options) {
167
+ return inspect(inspectableError(this), options);
168
+ }
178
169
  };
179
- const connect = () => {
180
- if (isClosed)
181
- return;
182
- isReconnecting = false;
183
- es = new eventsource_1.EventSource(`${baseUrl}/api/realtime`);
184
- es.addEventListener("PB_CONNECT", onConnect);
185
- es.addEventListener("error", onError);
186
- es.addEventListener(collection, onMessage);
170
+ function syntaxError(message) {
171
+ const DomException = globalThis.DOMException;
172
+ return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
173
+ }
174
+ function flattenError(err) {
175
+ return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;
176
+ }
177
+ function inspectableError(err) {
178
+ return {
179
+ type: err.type,
180
+ message: err.message,
181
+ code: err.code,
182
+ defaultPrevented: err.defaultPrevented,
183
+ cancelable: err.cancelable,
184
+ timeStamp: err.timeStamp
185
+ };
186
+ }
187
+ var __typeError = (msg) => {
188
+ throw TypeError(msg);
187
189
  };
188
- const reconnect = () => {
189
- if (isClosed || isReconnecting)
190
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
191
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
192
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
193
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
194
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
195
+ var _readyState;
196
+ var _url;
197
+ var _redirectUrl;
198
+ var _withCredentials;
199
+ var _fetch;
200
+ var _reconnectInterval;
201
+ var _reconnectTimer;
202
+ var _lastEventId;
203
+ var _controller;
204
+ var _parser;
205
+ var _onError;
206
+ var _onMessage;
207
+ var _onOpen;
208
+ var _EventSource_instances;
209
+ var connect_fn;
210
+ var _onFetchResponse;
211
+ var _onFetchError;
212
+ var getRequestOptions_fn;
213
+ var _onEvent;
214
+ var _onRetryChange;
215
+ var failConnection_fn;
216
+ var scheduleReconnect_fn;
217
+ var _reconnect;
218
+ var EventSource = class extends EventTarget {
219
+ constructor(url, eventSourceInitDict) {
220
+ var _a, _b;
221
+ super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {
222
+ var _a2;
223
+ __privateGet(this, _parser).reset();
224
+ const { body, redirected, status, headers } = response;
225
+ if (status === 204) {
226
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
227
+ return;
228
+ }
229
+ if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
230
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
231
+ return;
232
+ }
233
+ if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
234
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected "text/event-stream"', status);
235
+ return;
236
+ }
237
+ if (__privateGet(this, _readyState) === this.CLOSED)
238
+ return;
239
+ __privateSet(this, _readyState, this.OPEN);
240
+ const openEvent = new Event("open");
241
+ if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
242
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
190
243
  return;
191
- isReconnecting = true;
192
- if (stabilityTimer) {
193
- clearTimeout(stabilityTimer);
194
- stabilityTimer = null;
244
+ }
245
+ const decoder = new TextDecoder(), reader = body.getReader();
246
+ let open = true;
247
+ do {
248
+ const { done, value } = await reader.read();
249
+ value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
250
+ } while (open);
251
+ }), __privateAdd(this, _onFetchError, (err) => {
252
+ __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
253
+ }), __privateAdd(this, _onEvent, (event) => {
254
+ typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
255
+ const messageEvent = new MessageEvent(event.event || "message", {
256
+ data: event.data,
257
+ origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
258
+ lastEventId: event.id || ""
259
+ });
260
+ __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
261
+ }), __privateAdd(this, _onRetryChange, (value) => {
262
+ __privateSet(this, _reconnectInterval, value);
263
+ }), __privateAdd(this, _reconnect, () => {
264
+ __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
265
+ });
266
+ try {
267
+ if (url instanceof URL)
268
+ __privateSet(this, _url, url);
269
+ else if (typeof url == "string")
270
+ __privateSet(this, _url, new URL(url, getBaseURL()));
271
+ else
272
+ throw new Error("Invalid URL");
273
+ } catch {
274
+ throw syntaxError("An invalid or illegal string was specified");
195
275
  }
196
- if (es) {
197
- es.removeEventListener("PB_CONNECT", onConnect);
198
- es.removeEventListener("error", onError);
199
- es.removeEventListener(collection, onMessage);
200
- es.close();
201
- es = null;
276
+ __privateSet(this, _parser, eventsourceParser.createParser({
277
+ onEvent: __privateGet(this, _onEvent),
278
+ onRetry: __privateGet(this, _onRetryChange)
279
+ })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : false), __privateMethod(this, _EventSource_instances, connect_fn).call(this);
280
+ }
281
+ /**
282
+ * Returns the state of this EventSource object's connection. It can have the values described below.
283
+ *
284
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
285
+ *
286
+ * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
287
+ * defined in the TypeScript `dom` library.
288
+ *
289
+ * @public
290
+ */
291
+ get readyState() {
292
+ return __privateGet(this, _readyState);
293
+ }
294
+ /**
295
+ * Returns the URL providing the event stream.
296
+ *
297
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
298
+ *
299
+ * @public
300
+ */
301
+ get url() {
302
+ return __privateGet(this, _url).href;
303
+ }
304
+ /**
305
+ * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
306
+ *
307
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
308
+ */
309
+ get withCredentials() {
310
+ return __privateGet(this, _withCredentials);
311
+ }
312
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
313
+ get onerror() {
314
+ return __privateGet(this, _onError);
315
+ }
316
+ set onerror(value) {
317
+ __privateSet(this, _onError, value);
318
+ }
319
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
320
+ get onmessage() {
321
+ return __privateGet(this, _onMessage);
322
+ }
323
+ set onmessage(value) {
324
+ __privateSet(this, _onMessage, value);
325
+ }
326
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
327
+ get onopen() {
328
+ return __privateGet(this, _onOpen);
329
+ }
330
+ set onopen(value) {
331
+ __privateSet(this, _onOpen, value);
332
+ }
333
+ addEventListener(type, listener, options) {
334
+ const listen = listener;
335
+ super.addEventListener(type, listen, options);
336
+ }
337
+ removeEventListener(type, listener, options) {
338
+ const listen = listener;
339
+ super.removeEventListener(type, listen, options);
340
+ }
341
+ /**
342
+ * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
343
+ *
344
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
345
+ *
346
+ * @public
347
+ */
348
+ close() {
349
+ __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));
350
+ }
351
+ };
352
+ _readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), /**
353
+ * Connect to the given URL and start receiving events
354
+ *
355
+ * @internal
356
+ */
357
+ connect_fn = function() {
358
+ __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));
359
+ }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
360
+ * Get request options for the `fetch()` request
361
+ *
362
+ * @returns The request options
363
+ * @internal
364
+ */
365
+ getRequestOptions_fn = function() {
366
+ var _a;
367
+ const init = {
368
+ // [spec] Let `corsAttributeState` be `Anonymous`…
369
+ // [spec] …will have their mode set to "cors"…
370
+ mode: "cors",
371
+ redirect: "follow",
372
+ headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
373
+ cache: "no-store",
374
+ signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
375
+ };
376
+ return "window" in globalThis && (init.credentials = this.withCredentials ? "include" : "same-origin"), init;
377
+ }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
378
+ * Handles the process referred to in the EventSource specification as "failing a connection".
379
+ *
380
+ * @param error - The error causing the connection to fail
381
+ * @param code - The HTTP status code, if available
382
+ * @internal
383
+ */
384
+ failConnection_fn = function(message, code) {
385
+ var _a;
386
+ __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
387
+ const errorEvent = new ErrorEvent("error", { code, message });
388
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
389
+ }, /**
390
+ * Schedules a reconnection attempt against the EventSource endpoint.
391
+ *
392
+ * @param message - The error causing the connection to fail
393
+ * @param code - The HTTP status code, if available
394
+ * @internal
395
+ */
396
+ scheduleReconnect_fn = function(message, code) {
397
+ var _a;
398
+ if (__privateGet(this, _readyState) === this.CLOSED)
399
+ return;
400
+ __privateSet(this, _readyState, this.CONNECTING);
401
+ const errorEvent = new ErrorEvent("error", { code, message });
402
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
403
+ }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
404
+ * ReadyState representing an EventSource currently trying to connect
405
+ *
406
+ * @public
407
+ */
408
+ EventSource.CONNECTING = 0, /**
409
+ * ReadyState representing an EventSource connection that is open (eg connected)
410
+ *
411
+ * @public
412
+ */
413
+ EventSource.OPEN = 1, /**
414
+ * ReadyState representing an EventSource connection that is closed (eg disconnected)
415
+ *
416
+ * @public
417
+ */
418
+ EventSource.CLOSED = 2;
419
+ Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
420
+ value: true,
421
+ writable: false,
422
+ configurable: false,
423
+ enumerable: false
424
+ });
425
+ function getBaseURL() {
426
+ const doc = "document" in globalThis ? globalThis.document : void 0;
427
+ return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
428
+ }
429
+ exports2.ErrorEvent = ErrorEvent;
430
+ exports2.EventSource = EventSource;
431
+ }
432
+ });
433
+
434
+ // dist/nodes/PocketbaseTrigger/PocketbaseTrigger.node.js
435
+ Object.defineProperty(exports, "__esModule", { value: true });
436
+ exports.PocketbaseTrigger = void 0;
437
+ var n8n_workflow_1 = require("n8n-workflow");
438
+ var eventsource_1 = require_dist2();
439
+ var LoadOptions_1 = require("../Common/LoadOptions");
440
+ var PocketbaseTrigger = class {
441
+ constructor() {
442
+ this.description = {
443
+ displayName: "Pocketbase (Beta) Trigger",
444
+ name: "pocketbaseTrigger",
445
+ icon: { light: "file:pocketbaseTrigger.svg", dark: "file:pocketbaseTrigger.dark.svg" },
446
+ group: ["trigger"],
447
+ version: 1,
448
+ description: "Handle Pocketbase events via SSE (Beta)",
449
+ subtitle: '={{$parameter["collection"]}}',
450
+ defaults: {
451
+ name: "Pocketbase Trigger"
452
+ },
453
+ inputs: [],
454
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
455
+ credentials: [
456
+ {
457
+ name: "pocketbaseHttpApi",
458
+ required: true
202
459
  }
203
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
204
- this.logger.error("PocketBase trigger permanently closed after max reconnection attempts", {
205
- baseUrl,
206
- collection,
207
- reconnectAttempts,
208
- });
209
- this.emitError(new Error(`PocketBase SSE: Maximum reconnection attempts (${MAX_RECONNECT_ATTEMPTS}) reached`));
210
- isClosed = true;
211
- isReconnecting = false;
212
- return;
460
+ ],
461
+ properties: [
462
+ {
463
+ displayName: "Collection Name or ID",
464
+ name: "collection",
465
+ type: "options",
466
+ typeOptions: {
467
+ loadOptionsMethod: "getCollections"
468
+ },
469
+ default: "",
470
+ required: true,
471
+ description: 'The name of the collection to watch for changes. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.'
472
+ },
473
+ {
474
+ displayName: "Events",
475
+ name: "events",
476
+ type: "multiOptions",
477
+ options: [
478
+ {
479
+ name: "Create",
480
+ value: "create"
481
+ },
482
+ {
483
+ name: "Update",
484
+ value: "update"
485
+ },
486
+ {
487
+ name: "Delete",
488
+ value: "delete"
489
+ }
490
+ ],
491
+ default: ["create", "update", "delete"],
492
+ required: true,
493
+ description: "The events to trigger the node"
213
494
  }
214
- const baseDelay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
215
- const jitter = Math.random() * 1000;
216
- const delay = baseDelay + jitter;
217
- reconnectAttempts++;
218
- reconnectTimeout = setTimeout(connect, delay);
495
+ ],
496
+ usableAsTool: true
497
+ };
498
+ this.methods = {
499
+ loadOptions: LoadOptions_1.LoadOptions
219
500
  };
220
- connect();
501
+ }
502
+ async trigger() {
503
+ const credentials = await this.getCredentials("pocketbaseHttpApi");
504
+ const baseUrl = credentials.url.replace(/\/$/, "");
505
+ const collection = this.getNodeParameter("collection");
506
+ const events = this.getNodeParameter("events");
507
+ const { closeFunction } = subscribeToPocketbaseSSE.call(this, baseUrl, collection, events);
221
508
  return {
222
- closeFunction: async () => {
223
- isClosed = true;
224
- if (reconnectTimeout)
225
- clearTimeout(reconnectTimeout);
226
- if (stabilityTimer) {
227
- clearTimeout(stabilityTimer);
228
- stabilityTimer = null;
229
- }
230
- if (es) {
231
- es.removeEventListener("PB_CONNECT", onConnect);
232
- es.removeEventListener("error", onError);
233
- es.removeEventListener(collection, onMessage);
234
- es.close();
235
- }
236
- },
509
+ closeFunction,
510
+ manualTriggerFunction: async () => {
511
+ const sampleData = {
512
+ __action: "create",
513
+ id: "sample-id",
514
+ collectionId: "sample-collection-id",
515
+ collectionName: collection,
516
+ created: (/* @__PURE__ */ new Date()).toISOString(),
517
+ updated: (/* @__PURE__ */ new Date()).toISOString()
518
+ };
519
+ this.emit([this.helpers.returnJsonArray(sampleData)]);
520
+ }
237
521
  };
522
+ }
523
+ };
524
+ exports.PocketbaseTrigger = PocketbaseTrigger;
525
+ function subscribeToPocketbaseSSE(baseUrl, collection, events) {
526
+ let es = null;
527
+ let isClosed = false;
528
+ let isReconnecting = false;
529
+ let reconnectTimeout = null;
530
+ let stabilityTimer = null;
531
+ let reconnectAttempts = 0;
532
+ const MAX_RECONNECT_ATTEMPTS = 50;
533
+ let consecutiveFailures = 0;
534
+ const onConnect = async (e) => {
535
+ try {
536
+ const data = JSON.parse(e.data);
537
+ const clientId = data.clientId;
538
+ await this.helpers.httpRequestWithAuthentication.call(this, "pocketbaseHttpApi", {
539
+ method: "POST",
540
+ url: `${baseUrl}/api/realtime`,
541
+ body: {
542
+ clientId,
543
+ subscriptions: [collection]
544
+ }
545
+ });
546
+ if (stabilityTimer)
547
+ clearTimeout(stabilityTimer);
548
+ stabilityTimer = setTimeout(() => {
549
+ reconnectAttempts = 0;
550
+ consecutiveFailures = 0;
551
+ stabilityTimer = null;
552
+ }, 5e3);
553
+ } catch (error) {
554
+ this.logger.error("Failed to connect to PocketBase SSE", { error, collection });
555
+ const normalizedError = error instanceof Error ? error : new Error(String(error || "Unknown error during connect"));
556
+ if (consecutiveFailures === 0) {
557
+ this.emitError(normalizedError);
558
+ }
559
+ consecutiveFailures++;
560
+ reconnect();
561
+ }
562
+ };
563
+ const onError = (error) => {
564
+ if (stabilityTimer) {
565
+ clearTimeout(stabilityTimer);
566
+ stabilityTimer = null;
567
+ }
568
+ this.logger.error("PocketBase SSE connection failure", {
569
+ error,
570
+ baseUrl
571
+ });
572
+ const normalizedError = new Error(error.message || "PocketBase SSE connection failure");
573
+ const status = error.status;
574
+ if (error.code)
575
+ normalizedError.code = error.code;
576
+ if (status)
577
+ normalizedError.status = status;
578
+ normalizedError.originalErrorEvent = error;
579
+ if (consecutiveFailures === 0) {
580
+ this.emitError(normalizedError);
581
+ }
582
+ consecutiveFailures++;
583
+ reconnect();
584
+ };
585
+ const onMessage = (e) => {
586
+ var _a;
587
+ try {
588
+ const data = JSON.parse(e.data);
589
+ if (events.includes(data.action) && data.record) {
590
+ const output = {
591
+ ...data.record
592
+ };
593
+ if ("action" in output) {
594
+ output.__original_action = output.action;
595
+ }
596
+ output.__action = data.action;
597
+ this.emit([this.helpers.returnJsonArray(output)]);
598
+ }
599
+ } catch (error) {
600
+ const rawData = String((_a = e === null || e === void 0 ? void 0 : e.data) !== null && _a !== void 0 ? _a : "");
601
+ const redactedPreview = rawData.replace(/"(password|token|secret|passwordConfirm|apiKey|accessToken|authorization|bearer)":\s*"(?:[^"\\]|\\.)*"/gi, '"$1": "[REDACTED]"').replace(/"email":\s*"(?:[^"\\]|\\.)*"/gi, '"email": "[REDACTED]"').replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[REDACTED]").substring(0, 200);
602
+ this.logger.error("Failed to parse PocketBase SSE message", {
603
+ error,
604
+ redactedPreview,
605
+ collection
606
+ });
607
+ }
608
+ };
609
+ const connect = () => {
610
+ if (isClosed)
611
+ return;
612
+ isReconnecting = false;
613
+ es = new eventsource_1.EventSource(`${baseUrl}/api/realtime`);
614
+ es.addEventListener("PB_CONNECT", onConnect);
615
+ es.addEventListener("error", onError);
616
+ es.addEventListener(collection, onMessage);
617
+ };
618
+ const reconnect = () => {
619
+ if (isClosed || isReconnecting)
620
+ return;
621
+ isReconnecting = true;
622
+ if (stabilityTimer) {
623
+ clearTimeout(stabilityTimer);
624
+ stabilityTimer = null;
625
+ }
626
+ if (es) {
627
+ es.removeEventListener("PB_CONNECT", onConnect);
628
+ es.removeEventListener("error", onError);
629
+ es.removeEventListener(collection, onMessage);
630
+ es.close();
631
+ es = null;
632
+ }
633
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
634
+ this.logger.error("PocketBase trigger permanently closed after max reconnection attempts", {
635
+ baseUrl,
636
+ collection,
637
+ reconnectAttempts
638
+ });
639
+ this.emitError(new Error(`PocketBase SSE: Maximum reconnection attempts (${MAX_RECONNECT_ATTEMPTS}) reached`));
640
+ isClosed = true;
641
+ isReconnecting = false;
642
+ return;
643
+ }
644
+ const baseDelay = Math.min(1e3 * Math.pow(2, reconnectAttempts), 3e4);
645
+ const jitter = Math.random() * 1e3;
646
+ const delay = baseDelay + jitter;
647
+ reconnectAttempts++;
648
+ reconnectTimeout = setTimeout(connect, delay);
649
+ };
650
+ connect();
651
+ return {
652
+ closeFunction: async () => {
653
+ isClosed = true;
654
+ if (reconnectTimeout)
655
+ clearTimeout(reconnectTimeout);
656
+ if (stabilityTimer) {
657
+ clearTimeout(stabilityTimer);
658
+ stabilityTimer = null;
659
+ }
660
+ if (es) {
661
+ es.removeEventListener("PB_CONNECT", onConnect);
662
+ es.removeEventListener("error", onError);
663
+ es.removeEventListener(collection, onMessage);
664
+ es.close();
665
+ }
666
+ }
667
+ };
238
668
  }
239
- //# sourceMappingURL=PocketbaseTrigger.node.js.map
669
+ //# sourceMappingURL=PocketbaseTrigger.node.js.map