@rpcbase/server 0.489.0 → 0.490.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.
Files changed (107) hide show
  1. package/package.json +1 -1
  2. package/dist/applyRouteLoaders.d.ts +0 -9
  3. package/dist/applyRouteLoaders.d.ts.map +0 -1
  4. package/dist/checkInitReplicaSet.d.ts +0 -6
  5. package/dist/checkInitReplicaSet.d.ts.map +0 -1
  6. package/dist/dev/coverage.d.ts +0 -3
  7. package/dist/dev/coverage.d.ts.map +0 -1
  8. package/dist/email-DEw8keax.js +0 -8042
  9. package/dist/email-DEw8keax.js.map +0 -1
  10. package/dist/email.d.ts +0 -19
  11. package/dist/email.d.ts.map +0 -1
  12. package/dist/getDerivedKey.d.ts +0 -3
  13. package/dist/getDerivedKey.d.ts.map +0 -1
  14. package/dist/handler-BwK8qxLn.js +0 -438
  15. package/dist/handler-BwK8qxLn.js.map +0 -1
  16. package/dist/handler-CedzJJg0.js +0 -114
  17. package/dist/handler-CedzJJg0.js.map +0 -1
  18. package/dist/handler-Cohj3cz3.js +0 -176
  19. package/dist/handler-Cohj3cz3.js.map +0 -1
  20. package/dist/handler-qCAUmVgd.js +0 -684
  21. package/dist/handler-qCAUmVgd.js.map +0 -1
  22. package/dist/hashPassword.d.ts +0 -2
  23. package/dist/hashPassword.d.ts.map +0 -1
  24. package/dist/index.d.ts +0 -7
  25. package/dist/index.d.ts.map +0 -1
  26. package/dist/index.js +0 -4628
  27. package/dist/index.js.map +0 -1
  28. package/dist/initServer.d.ts +0 -9
  29. package/dist/initServer.d.ts.map +0 -1
  30. package/dist/metricsIngestProxyMiddleware.d.ts +0 -3
  31. package/dist/metricsIngestProxyMiddleware.d.ts.map +0 -1
  32. package/dist/notifications/api/notifications/handler.d.ts +0 -4
  33. package/dist/notifications/api/notifications/handler.d.ts.map +0 -1
  34. package/dist/notifications/api/notifications/index.d.ts +0 -168
  35. package/dist/notifications/api/notifications/index.d.ts.map +0 -1
  36. package/dist/notifications/api/notifications/shared.d.ts +0 -6
  37. package/dist/notifications/api/notifications/shared.d.ts.map +0 -1
  38. package/dist/notifications/createNotification.d.ts +0 -13
  39. package/dist/notifications/createNotification.d.ts.map +0 -1
  40. package/dist/notifications/digest.d.ts +0 -13
  41. package/dist/notifications/digest.d.ts.map +0 -1
  42. package/dist/notifications/routes.d.ts +0 -2
  43. package/dist/notifications/routes.d.ts.map +0 -1
  44. package/dist/notifications.d.ts +0 -4
  45. package/dist/notifications.d.ts.map +0 -1
  46. package/dist/notifications.js +0 -127
  47. package/dist/notifications.js.map +0 -1
  48. package/dist/passwordHashStorage.d.ts +0 -11
  49. package/dist/passwordHashStorage.d.ts.map +0 -1
  50. package/dist/posthog.d.ts +0 -9
  51. package/dist/posthog.d.ts.map +0 -1
  52. package/dist/renderSSR.d.ts +0 -12
  53. package/dist/renderSSR.d.ts.map +0 -1
  54. package/dist/render_resend_false-MiC__Smr.js +0 -6
  55. package/dist/render_resend_false-MiC__Smr.js.map +0 -1
  56. package/dist/rts/api/changes/handler.d.ts +0 -9
  57. package/dist/rts/api/changes/handler.d.ts.map +0 -1
  58. package/dist/rts/api/changes/index.d.ts +0 -25
  59. package/dist/rts/api/changes/index.d.ts.map +0 -1
  60. package/dist/rts/index.d.ts +0 -40
  61. package/dist/rts/index.d.ts.map +0 -1
  62. package/dist/rts/index.js +0 -631
  63. package/dist/rts/index.js.map +0 -1
  64. package/dist/rts/routes.d.ts +0 -2
  65. package/dist/rts/routes.d.ts.map +0 -1
  66. package/dist/schemas-7qqi9OQy.js +0 -4225
  67. package/dist/schemas-7qqi9OQy.js.map +0 -1
  68. package/dist/shared-BJomDDWK.js +0 -107
  69. package/dist/shared-BJomDDWK.js.map +0 -1
  70. package/dist/ssrMiddleware.d.ts +0 -18
  71. package/dist/ssrMiddleware.d.ts.map +0 -1
  72. package/dist/types/index.d.ts +0 -6
  73. package/dist/types/index.d.ts.map +0 -1
  74. package/dist/uploads/api/file-uploads/handler.d.ts +0 -5
  75. package/dist/uploads/api/file-uploads/handler.d.ts.map +0 -1
  76. package/dist/uploads/api/file-uploads/handlers/completeUpload.d.ts +0 -5
  77. package/dist/uploads/api/file-uploads/handlers/completeUpload.d.ts.map +0 -1
  78. package/dist/uploads/api/file-uploads/handlers/getStatus.d.ts +0 -5
  79. package/dist/uploads/api/file-uploads/handlers/getStatus.d.ts.map +0 -1
  80. package/dist/uploads/api/file-uploads/handlers/initUpload.d.ts +0 -5
  81. package/dist/uploads/api/file-uploads/handlers/initUpload.d.ts.map +0 -1
  82. package/dist/uploads/api/file-uploads/handlers/uploadChunk.d.ts +0 -9
  83. package/dist/uploads/api/file-uploads/handlers/uploadChunk.d.ts.map +0 -1
  84. package/dist/uploads/api/file-uploads/index.d.ts +0 -43
  85. package/dist/uploads/api/file-uploads/index.d.ts.map +0 -1
  86. package/dist/uploads/api/file-uploads/middleware/rawBodyParser.d.ts +0 -5
  87. package/dist/uploads/api/file-uploads/middleware/rawBodyParser.d.ts.map +0 -1
  88. package/dist/uploads/api/file-uploads/processors/index.d.ts +0 -25
  89. package/dist/uploads/api/file-uploads/processors/index.d.ts.map +0 -1
  90. package/dist/uploads/api/file-uploads/processors/sanitizeSvg.d.ts +0 -5
  91. package/dist/uploads/api/file-uploads/processors/sanitizeSvg.d.ts.map +0 -1
  92. package/dist/uploads/api/file-uploads/shared.d.ts +0 -32
  93. package/dist/uploads/api/file-uploads/shared.d.ts.map +0 -1
  94. package/dist/uploads/api/files/handler.d.ts +0 -4
  95. package/dist/uploads/api/files/handler.d.ts.map +0 -1
  96. package/dist/uploads/api/files/handlers/deleteFile.d.ts +0 -9
  97. package/dist/uploads/api/files/handlers/deleteFile.d.ts.map +0 -1
  98. package/dist/uploads/api/files/handlers/getFile.d.ts +0 -4
  99. package/dist/uploads/api/files/handlers/getFile.d.ts.map +0 -1
  100. package/dist/uploads/api/files/index.d.ts +0 -4
  101. package/dist/uploads/api/files/index.d.ts.map +0 -1
  102. package/dist/uploads/routes.d.ts +0 -2
  103. package/dist/uploads/routes.d.ts.map +0 -1
  104. package/dist/uploads.d.ts +0 -2
  105. package/dist/uploads.d.ts.map +0 -1
  106. package/dist/uploads.js +0 -10
  107. package/dist/uploads.js.map +0 -1
package/dist/index.js DELETED
@@ -1,4628 +0,0 @@
1
- import session from "express-session";
2
- import { RedisStore } from "connect-redis";
3
- import MongoStore from "connect-mongo";
4
- import { createClient } from "redis";
5
- import { MongoClient } from "mongodb";
6
- import env from "@rpcbase/env";
7
- import { initApiClient, SsrErrorFallback, SSR_ERROR_STATE_GLOBAL_KEY, serializeSsrErrorState } from "@rpcbase/client";
8
- import { dirname, posix, sep } from "path";
9
- import fs, { createReadStream, readFileSync } from "node:fs";
10
- import { createInterface } from "node:readline";
11
- import { AsyncLocalStorage } from "node:async_hooks";
12
- import assert$1 from "assert";
13
- import crypto, { hkdfSync, scrypt } from "crypto";
14
- import httpProxy from "http-proxy-3";
15
- import fsPromises from "node:fs/promises";
16
- import inspector from "node:inspector";
17
- import path from "node:path";
18
- import { fileURLToPath } from "node:url";
19
- import { Transform } from "node:stream";
20
- import { StrictMode, createElement } from "react";
21
- import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
22
- import { jsx } from "react/jsx-runtime";
23
- import { createPath, matchRoutes, parsePath, createStaticRouter, StaticRouterProvider } from "@rpcbase/router";
24
- import { s } from "./email-DEw8keax.js";
25
- function getDefaultExportFromCjs(x) {
26
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
27
- }
28
- var is_1;
29
- var hasRequiredIs;
30
- function requireIs() {
31
- if (hasRequiredIs) return is_1;
32
- hasRequiredIs = 1;
33
- var regexes = {
34
- ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/,
35
- ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i
36
- };
37
- function not(func) {
38
- return function() {
39
- return !func.apply(null, Array.prototype.slice.call(arguments));
40
- };
41
- }
42
- function existy(value) {
43
- return value != null;
44
- }
45
- function ip(value) {
46
- return existy(value) && regexes.ipv4.test(value) || regexes.ipv6.test(value);
47
- }
48
- function object(value) {
49
- return Object(value) === value;
50
- }
51
- function string(value) {
52
- return Object.prototype.toString.call(value) === "[object String]";
53
- }
54
- var is = {
55
- existy,
56
- ip,
57
- object,
58
- string,
59
- not: {
60
- existy: not(existy),
61
- ip: not(ip),
62
- object: not(object),
63
- string: not(string)
64
- }
65
- };
66
- is_1 = is;
67
- return is_1;
68
- }
69
- var lib;
70
- var hasRequiredLib;
71
- function requireLib() {
72
- if (hasRequiredLib) return lib;
73
- hasRequiredLib = 1;
74
- function _typeof(obj) {
75
- "@babel/helpers - typeof";
76
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(obj2) {
77
- return typeof obj2;
78
- } : function(obj2) {
79
- return obj2 && "function" == typeof Symbol && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
80
- }, _typeof(obj);
81
- }
82
- var is = requireIs();
83
- function getClientIpFromXForwardedFor(value) {
84
- if (!is.existy(value)) {
85
- return null;
86
- }
87
- if (is.not.string(value)) {
88
- throw new TypeError('Expected a string, got "'.concat(_typeof(value), '"'));
89
- }
90
- var forwardedIps = value.split(",").map(function(e) {
91
- var ip = e.trim();
92
- if (ip.includes(":")) {
93
- var splitted = ip.split(":");
94
- if (splitted.length === 2) {
95
- return splitted[0];
96
- }
97
- }
98
- return ip;
99
- });
100
- for (var i = 0; i < forwardedIps.length; i++) {
101
- if (is.ip(forwardedIps[i])) {
102
- return forwardedIps[i];
103
- }
104
- }
105
- return null;
106
- }
107
- function getClientIp(req) {
108
- if (req.headers) {
109
- if (is.ip(req.headers["x-client-ip"])) {
110
- return req.headers["x-client-ip"];
111
- }
112
- var xForwardedFor = getClientIpFromXForwardedFor(req.headers["x-forwarded-for"]);
113
- if (is.ip(xForwardedFor)) {
114
- return xForwardedFor;
115
- }
116
- if (is.ip(req.headers["cf-connecting-ip"])) {
117
- return req.headers["cf-connecting-ip"];
118
- }
119
- if (is.ip(req.headers["fastly-client-ip"])) {
120
- return req.headers["fastly-client-ip"];
121
- }
122
- if (is.ip(req.headers["true-client-ip"])) {
123
- return req.headers["true-client-ip"];
124
- }
125
- if (is.ip(req.headers["x-real-ip"])) {
126
- return req.headers["x-real-ip"];
127
- }
128
- if (is.ip(req.headers["x-cluster-client-ip"])) {
129
- return req.headers["x-cluster-client-ip"];
130
- }
131
- if (is.ip(req.headers["x-forwarded"])) {
132
- return req.headers["x-forwarded"];
133
- }
134
- if (is.ip(req.headers["forwarded-for"])) {
135
- return req.headers["forwarded-for"];
136
- }
137
- if (is.ip(req.headers.forwarded)) {
138
- return req.headers.forwarded;
139
- }
140
- if (is.ip(req.headers["x-appengine-user-ip"])) {
141
- return req.headers["x-appengine-user-ip"];
142
- }
143
- }
144
- if (is.existy(req.connection)) {
145
- if (is.ip(req.connection.remoteAddress)) {
146
- return req.connection.remoteAddress;
147
- }
148
- if (is.existy(req.connection.socket) && is.ip(req.connection.socket.remoteAddress)) {
149
- return req.connection.socket.remoteAddress;
150
- }
151
- }
152
- if (is.existy(req.socket) && is.ip(req.socket.remoteAddress)) {
153
- return req.socket.remoteAddress;
154
- }
155
- if (is.existy(req.info) && is.ip(req.info.remoteAddress)) {
156
- return req.info.remoteAddress;
157
- }
158
- if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) {
159
- return req.requestContext.identity.sourceIp;
160
- }
161
- if (req.headers) {
162
- if (is.ip(req.headers["Cf-Pseudo-IPv4"])) {
163
- return req.headers["Cf-Pseudo-IPv4"];
164
- }
165
- }
166
- if (is.existy(req.raw)) {
167
- return getClientIp(req.raw);
168
- }
169
- return null;
170
- }
171
- function mw(options) {
172
- var configuration = is.not.existy(options) ? {} : options;
173
- if (is.not.object(configuration)) {
174
- throw new TypeError("Options must be an object!");
175
- }
176
- var attributeName = configuration.attributeName || "clientIp";
177
- return function(req, res, next) {
178
- var ip = getClientIp(req);
179
- Object.defineProperty(req, attributeName, {
180
- get: function get() {
181
- return ip;
182
- },
183
- configurable: true
184
- });
185
- next();
186
- };
187
- }
188
- lib = {
189
- getClientIpFromXForwardedFor,
190
- getClientIp,
191
- mw
192
- };
193
- return lib;
194
- }
195
- var libExports = requireLib();
196
- const requestIp = /* @__PURE__ */ getDefaultExportFromCjs(libExports);
197
- function createModulerModifier() {
198
- const getModuleFromFileName = createGetModuleFromFilename();
199
- return async (frames) => {
200
- for (const frame of frames) frame.module = getModuleFromFileName(frame.filename);
201
- return frames;
202
- };
203
- }
204
- function createGetModuleFromFilename(basePath = process.argv[1] ? dirname(process.argv[1]) : process.cwd(), isWindows = "\\" === sep) {
205
- const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
206
- return (filename) => {
207
- if (!filename) return;
208
- const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename;
209
- let { dir, base: file, ext } = posix.parse(normalizedFilename);
210
- if (".js" === ext || ".mjs" === ext || ".cjs" === ext) file = file.slice(0, -1 * ext.length);
211
- const decodedFile = decodeURIComponent(file);
212
- if (!dir) dir = ".";
213
- const n = dir.lastIndexOf("/node_modules");
214
- if (n > -1) return `${dir.slice(n + 14).replace(/\//g, ".")}:${decodedFile}`;
215
- if (dir.startsWith(normalizedBase)) {
216
- const moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, ".");
217
- return moduleName ? `${moduleName}:${decodedFile}` : decodedFile;
218
- }
219
- return decodedFile;
220
- };
221
- }
222
- function normalizeWindowsPath(path2) {
223
- return path2.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
224
- }
225
- const normalizeFlagsResponse = (flagsResponse) => {
226
- if ("flags" in flagsResponse) {
227
- const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
228
- const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
229
- return {
230
- ...flagsResponse,
231
- featureFlags,
232
- featureFlagPayloads
233
- };
234
- }
235
- {
236
- const featureFlags = flagsResponse.featureFlags ?? {};
237
- const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [
238
- k,
239
- parsePayload(v)
240
- ]));
241
- const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
242
- key,
243
- getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key])
244
- ]));
245
- return {
246
- ...flagsResponse,
247
- featureFlags,
248
- featureFlagPayloads,
249
- flags
250
- };
251
- }
252
- };
253
- function getFlagDetailFromFlagAndPayload(key, value, payload) {
254
- return {
255
- key,
256
- enabled: "string" == typeof value ? true : value,
257
- variant: "string" == typeof value ? value : void 0,
258
- reason: void 0,
259
- metadata: {
260
- id: void 0,
261
- version: void 0,
262
- payload: payload ? JSON.stringify(payload) : void 0,
263
- description: void 0
264
- }
265
- };
266
- }
267
- const getFlagValuesFromFlags = (flags) => Object.fromEntries(Object.entries(flags ?? {}).map(([key, detail]) => [
268
- key,
269
- getFeatureFlagValue(detail)
270
- ]).filter(([, value]) => void 0 !== value));
271
- const getPayloadsFromFlags = (flags) => {
272
- const safeFlags = flags ?? {};
273
- return Object.fromEntries(Object.keys(safeFlags).filter((flag) => {
274
- const details = safeFlags[flag];
275
- return details.enabled && details.metadata && void 0 !== details.metadata.payload;
276
- }).map((flag) => {
277
- const payload = safeFlags[flag].metadata?.payload;
278
- return [
279
- flag,
280
- payload ? parsePayload(payload) : void 0
281
- ];
282
- }));
283
- };
284
- const getFeatureFlagValue = (detail) => void 0 === detail ? void 0 : detail.variant ?? detail.enabled;
285
- const parsePayload = (response) => {
286
- if ("string" != typeof response) return response;
287
- try {
288
- return JSON.parse(response);
289
- } catch {
290
- return response;
291
- }
292
- };
293
- const DIGITS = "0123456789abcdef";
294
- class UUID {
295
- constructor(bytes) {
296
- this.bytes = bytes;
297
- }
298
- static ofInner(bytes) {
299
- if (16 === bytes.length) return new UUID(bytes);
300
- throw new TypeError("not 128-bit length");
301
- }
302
- static fromFieldsV7(unixTsMs, randA, randBHi, randBLo) {
303
- if (!Number.isInteger(unixTsMs) || !Number.isInteger(randA) || !Number.isInteger(randBHi) || !Number.isInteger(randBLo) || unixTsMs < 0 || randA < 0 || randBHi < 0 || randBLo < 0 || unixTsMs > 281474976710655 || randA > 4095 || randBHi > 1073741823 || randBLo > 4294967295) throw new RangeError("invalid field value");
304
- const bytes = new Uint8Array(16);
305
- bytes[0] = unixTsMs / 2 ** 40;
306
- bytes[1] = unixTsMs / 2 ** 32;
307
- bytes[2] = unixTsMs / 2 ** 24;
308
- bytes[3] = unixTsMs / 2 ** 16;
309
- bytes[4] = unixTsMs / 256;
310
- bytes[5] = unixTsMs;
311
- bytes[6] = 112 | randA >>> 8;
312
- bytes[7] = randA;
313
- bytes[8] = 128 | randBHi >>> 24;
314
- bytes[9] = randBHi >>> 16;
315
- bytes[10] = randBHi >>> 8;
316
- bytes[11] = randBHi;
317
- bytes[12] = randBLo >>> 24;
318
- bytes[13] = randBLo >>> 16;
319
- bytes[14] = randBLo >>> 8;
320
- bytes[15] = randBLo;
321
- return new UUID(bytes);
322
- }
323
- static parse(uuid) {
324
- let hex;
325
- switch (uuid.length) {
326
- case 32:
327
- hex = /^[0-9a-f]{32}$/i.exec(uuid)?.[0];
328
- break;
329
- case 36:
330
- hex = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)?.slice(1, 6).join("");
331
- break;
332
- case 38:
333
- hex = /^\{([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}$/i.exec(uuid)?.slice(1, 6).join("");
334
- break;
335
- case 45:
336
- hex = /^urn:uuid:([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i.exec(uuid)?.slice(1, 6).join("");
337
- break;
338
- }
339
- if (hex) {
340
- const inner = new Uint8Array(16);
341
- for (let i = 0; i < 16; i += 4) {
342
- const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
343
- inner[i + 0] = n >>> 24;
344
- inner[i + 1] = n >>> 16;
345
- inner[i + 2] = n >>> 8;
346
- inner[i + 3] = n;
347
- }
348
- return new UUID(inner);
349
- }
350
- throw new SyntaxError("could not parse UUID string");
351
- }
352
- toString() {
353
- let text = "";
354
- for (let i = 0; i < this.bytes.length; i++) {
355
- text += DIGITS.charAt(this.bytes[i] >>> 4);
356
- text += DIGITS.charAt(15 & this.bytes[i]);
357
- if (3 === i || 5 === i || 7 === i || 9 === i) text += "-";
358
- }
359
- return text;
360
- }
361
- toHex() {
362
- let text = "";
363
- for (let i = 0; i < this.bytes.length; i++) {
364
- text += DIGITS.charAt(this.bytes[i] >>> 4);
365
- text += DIGITS.charAt(15 & this.bytes[i]);
366
- }
367
- return text;
368
- }
369
- toJSON() {
370
- return this.toString();
371
- }
372
- getVariant() {
373
- const n = this.bytes[8] >>> 4;
374
- if (n < 0) throw new Error("unreachable");
375
- if (n <= 7) return this.bytes.every((e) => 0 === e) ? "NIL" : "VAR_0";
376
- if (n <= 11) return "VAR_10";
377
- if (n <= 13) return "VAR_110";
378
- if (n <= 15) return this.bytes.every((e) => 255 === e) ? "MAX" : "VAR_RESERVED";
379
- else throw new Error("unreachable");
380
- }
381
- getVersion() {
382
- return "VAR_10" === this.getVariant() ? this.bytes[6] >>> 4 : void 0;
383
- }
384
- clone() {
385
- return new UUID(this.bytes.slice(0));
386
- }
387
- equals(other) {
388
- return 0 === this.compareTo(other);
389
- }
390
- compareTo(other) {
391
- for (let i = 0; i < 16; i++) {
392
- const diff = this.bytes[i] - other.bytes[i];
393
- if (0 !== diff) return Math.sign(diff);
394
- }
395
- return 0;
396
- }
397
- }
398
- class V7Generator {
399
- constructor(randomNumberGenerator) {
400
- this.timestamp = 0;
401
- this.counter = 0;
402
- this.random = randomNumberGenerator ?? getDefaultRandom();
403
- }
404
- generate() {
405
- return this.generateOrResetCore(Date.now(), 1e4);
406
- }
407
- generateOrAbort() {
408
- return this.generateOrAbortCore(Date.now(), 1e4);
409
- }
410
- generateOrResetCore(unixTsMs, rollbackAllowance) {
411
- let value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
412
- if (void 0 === value) {
413
- this.timestamp = 0;
414
- value = this.generateOrAbortCore(unixTsMs, rollbackAllowance);
415
- }
416
- return value;
417
- }
418
- generateOrAbortCore(unixTsMs, rollbackAllowance) {
419
- const MAX_COUNTER = 4398046511103;
420
- if (!Number.isInteger(unixTsMs) || unixTsMs < 1 || unixTsMs > 281474976710655) throw new RangeError("`unixTsMs` must be a 48-bit positive integer");
421
- if (rollbackAllowance < 0 || rollbackAllowance > 281474976710655) throw new RangeError("`rollbackAllowance` out of reasonable range");
422
- if (unixTsMs > this.timestamp) {
423
- this.timestamp = unixTsMs;
424
- this.resetCounter();
425
- } else {
426
- if (!(unixTsMs + rollbackAllowance >= this.timestamp)) return;
427
- this.counter++;
428
- if (this.counter > MAX_COUNTER) {
429
- this.timestamp++;
430
- this.resetCounter();
431
- }
432
- }
433
- return UUID.fromFieldsV7(this.timestamp, Math.trunc(this.counter / 2 ** 30), this.counter & 2 ** 30 - 1, this.random.nextUint32());
434
- }
435
- resetCounter() {
436
- this.counter = 1024 * this.random.nextUint32() + (1023 & this.random.nextUint32());
437
- }
438
- generateV4() {
439
- const bytes = new Uint8Array(Uint32Array.of(this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32(), this.random.nextUint32()).buffer);
440
- bytes[6] = 64 | bytes[6] >>> 4;
441
- bytes[8] = 128 | bytes[8] >>> 2;
442
- return UUID.ofInner(bytes);
443
- }
444
- }
445
- const getDefaultRandom = () => ({
446
- nextUint32: () => 65536 * Math.trunc(65536 * Math.random()) + Math.trunc(65536 * Math.random())
447
- });
448
- let defaultGenerator;
449
- const uuidv7 = () => uuidv7obj().toString();
450
- const uuidv7obj = () => (defaultGenerator || (defaultGenerator = new V7Generator())).generate();
451
- const DEFAULT_BLOCKED_UA_STRS = [
452
- "amazonbot",
453
- "amazonproductbot",
454
- "app.hypefactors.com",
455
- "applebot",
456
- "archive.org_bot",
457
- "awariobot",
458
- "backlinksextendedbot",
459
- "baiduspider",
460
- "bingbot",
461
- "bingpreview",
462
- "chrome-lighthouse",
463
- "dataforseobot",
464
- "deepscan",
465
- "duckduckbot",
466
- "facebookexternal",
467
- "facebookcatalog",
468
- "http://yandex.com/bots",
469
- "hubspot",
470
- "ia_archiver",
471
- "leikibot",
472
- "linkedinbot",
473
- "meta-externalagent",
474
- "mj12bot",
475
- "msnbot",
476
- "nessus",
477
- "petalbot",
478
- "pinterest",
479
- "prerender",
480
- "rogerbot",
481
- "screaming frog",
482
- "sebot-wa",
483
- "sitebulb",
484
- "slackbot",
485
- "slurp",
486
- "trendictionbot",
487
- "turnitin",
488
- "twitterbot",
489
- "vercel-screenshot",
490
- "vercelbot",
491
- "yahoo! slurp",
492
- "yandexbot",
493
- "zoombot",
494
- "bot.htm",
495
- "bot.php",
496
- "(bot;",
497
- "bot/",
498
- "crawler",
499
- "ahrefsbot",
500
- "ahrefssiteaudit",
501
- "semrushbot",
502
- "siteauditbot",
503
- "splitsignalbot",
504
- "gptbot",
505
- "oai-searchbot",
506
- "chatgpt-user",
507
- "perplexitybot",
508
- "better uptime bot",
509
- "sentryuptimebot",
510
- "uptimerobot",
511
- "headlesschrome",
512
- "cypress",
513
- "google-hoteladsverifier",
514
- "adsbot-google",
515
- "apis-google",
516
- "duplexweb-google",
517
- "feedfetcher-google",
518
- "google favicon",
519
- "google web preview",
520
- "google-read-aloud",
521
- "googlebot",
522
- "googleother",
523
- "google-cloudvertexbot",
524
- "googleweblight",
525
- "mediapartners-google",
526
- "storebot-google",
527
- "google-inspectiontool",
528
- "bytespider"
529
- ];
530
- const isBlockedUA = function(ua, customBlockedUserAgents = []) {
531
- if (!ua) return false;
532
- const uaLower = ua.toLowerCase();
533
- return DEFAULT_BLOCKED_UA_STRS.concat(customBlockedUserAgents).some((blockedUA) => {
534
- const blockedUaLower = blockedUA.toLowerCase();
535
- return -1 !== uaLower.indexOf(blockedUaLower);
536
- });
537
- };
538
- var types_PostHogPersistedProperty = /* @__PURE__ */ (function(PostHogPersistedProperty) {
539
- PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
540
- PostHogPersistedProperty["DistinctId"] = "distinct_id";
541
- PostHogPersistedProperty["Props"] = "props";
542
- PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
543
- PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
544
- PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
545
- PostHogPersistedProperty["BootstrapFeatureFlagDetails"] = "bootstrap_feature_flag_details";
546
- PostHogPersistedProperty["BootstrapFeatureFlags"] = "bootstrap_feature_flags";
547
- PostHogPersistedProperty["BootstrapFeatureFlagPayloads"] = "bootstrap_feature_flag_payloads";
548
- PostHogPersistedProperty["OverrideFeatureFlags"] = "override_feature_flags";
549
- PostHogPersistedProperty["Queue"] = "queue";
550
- PostHogPersistedProperty["OptedOut"] = "opted_out";
551
- PostHogPersistedProperty["SessionId"] = "session_id";
552
- PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
553
- PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
554
- PostHogPersistedProperty["PersonProperties"] = "person_properties";
555
- PostHogPersistedProperty["GroupProperties"] = "group_properties";
556
- PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
557
- PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
558
- PostHogPersistedProperty["SessionReplay"] = "session_replay";
559
- PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
560
- PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
561
- PostHogPersistedProperty["Surveys"] = "surveys";
562
- PostHogPersistedProperty["RemoteConfig"] = "remote_config";
563
- PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
564
- return PostHogPersistedProperty;
565
- })({});
566
- const nativeIsArray = Array.isArray;
567
- const ObjProto = Object.prototype;
568
- const type_utils_toString = ObjProto.toString;
569
- const isArray = nativeIsArray || function(obj) {
570
- return "[object Array]" === type_utils_toString.call(obj);
571
- };
572
- const isObject = (x) => x === Object(x) && !isArray(x);
573
- const isUndefined = (x) => void 0 === x;
574
- const isString = (x) => "[object String]" == type_utils_toString.call(x);
575
- const isEmptyString = (x) => isString(x) && 0 === x.trim().length;
576
- const isNumber = (x) => "[object Number]" == type_utils_toString.call(x);
577
- const isPlainError = (x) => x instanceof Error;
578
- function isPrimitive(value) {
579
- return null === value || "object" != typeof value;
580
- }
581
- function isBuiltin(candidate, className) {
582
- return Object.prototype.toString.call(candidate) === `[object ${className}]`;
583
- }
584
- function isEvent(candidate) {
585
- return !isUndefined(Event) && isInstanceOf(candidate, Event);
586
- }
587
- function isPlainObject(candidate) {
588
- return isBuiltin(candidate, "Object");
589
- }
590
- function isInstanceOf(candidate, base) {
591
- try {
592
- return candidate instanceof base;
593
- } catch {
594
- return false;
595
- }
596
- }
597
- function clampToRange(value, min, max, logger, fallbackValue) {
598
- if (min > max) {
599
- logger.warn("min cannot be greater than max.");
600
- min = max;
601
- }
602
- if (isNumber(value)) if (value > max) {
603
- logger.warn(" cannot be greater than max: " + max + ". Using max value instead.");
604
- return max;
605
- } else {
606
- if (!(value < min)) return value;
607
- logger.warn(" cannot be less than min: " + min + ". Using min value instead.");
608
- return min;
609
- }
610
- logger.warn(" must be a number. using max or fallback. max: " + max + ", fallback: " + fallbackValue);
611
- return clampToRange(max, min, max, logger);
612
- }
613
- const ONE_DAY_IN_MS = 864e5;
614
- class BucketedRateLimiter {
615
- constructor(options) {
616
- this._buckets = {};
617
- this._onBucketRateLimited = options._onBucketRateLimited;
618
- this._bucketSize = clampToRange(options.bucketSize, 0, 100, options._logger);
619
- this._refillRate = clampToRange(options.refillRate, 0, this._bucketSize, options._logger);
620
- this._refillInterval = clampToRange(options.refillInterval, 0, ONE_DAY_IN_MS, options._logger);
621
- }
622
- _applyRefill(bucket, now) {
623
- const elapsedMs = now - bucket.lastAccess;
624
- const refillIntervals = Math.floor(elapsedMs / this._refillInterval);
625
- if (refillIntervals > 0) {
626
- const tokensToAdd = refillIntervals * this._refillRate;
627
- bucket.tokens = Math.min(bucket.tokens + tokensToAdd, this._bucketSize);
628
- bucket.lastAccess = bucket.lastAccess + refillIntervals * this._refillInterval;
629
- }
630
- }
631
- consumeRateLimit(key) {
632
- const now = Date.now();
633
- const keyStr = String(key);
634
- let bucket = this._buckets[keyStr];
635
- if (bucket) this._applyRefill(bucket, now);
636
- else {
637
- bucket = {
638
- tokens: this._bucketSize,
639
- lastAccess: now
640
- };
641
- this._buckets[keyStr] = bucket;
642
- }
643
- if (0 === bucket.tokens) return true;
644
- bucket.tokens--;
645
- if (0 === bucket.tokens) this._onBucketRateLimited?.(key);
646
- return 0 === bucket.tokens;
647
- }
648
- stop() {
649
- this._buckets = {};
650
- }
651
- }
652
- class PromiseQueue {
653
- add(promise) {
654
- const promiseUUID = uuidv7();
655
- this.promiseByIds[promiseUUID] = promise;
656
- promise.catch(() => {
657
- }).finally(() => {
658
- delete this.promiseByIds[promiseUUID];
659
- });
660
- return promise;
661
- }
662
- async join() {
663
- let promises = Object.values(this.promiseByIds);
664
- let length = promises.length;
665
- while (length > 0) {
666
- await Promise.all(promises);
667
- promises = Object.values(this.promiseByIds);
668
- length = promises.length;
669
- }
670
- }
671
- get length() {
672
- return Object.keys(this.promiseByIds).length;
673
- }
674
- constructor() {
675
- this.promiseByIds = {};
676
- }
677
- }
678
- function createConsole(consoleLike = console) {
679
- const lockedMethods = {
680
- log: consoleLike.log.bind(consoleLike),
681
- warn: consoleLike.warn.bind(consoleLike),
682
- error: consoleLike.error.bind(consoleLike),
683
- debug: consoleLike.debug.bind(consoleLike)
684
- };
685
- return lockedMethods;
686
- }
687
- const _createLogger = (prefix, maybeCall, consoleLike) => {
688
- function _log(level, ...args) {
689
- maybeCall(() => {
690
- const consoleMethod = consoleLike[level];
691
- consoleMethod(prefix, ...args);
692
- });
693
- }
694
- const logger = {
695
- info: (...args) => {
696
- _log("log", ...args);
697
- },
698
- warn: (...args) => {
699
- _log("warn", ...args);
700
- },
701
- error: (...args) => {
702
- _log("error", ...args);
703
- },
704
- critical: (...args) => {
705
- consoleLike["error"](prefix, ...args);
706
- },
707
- createLogger: (additionalPrefix) => _createLogger(`${prefix} ${additionalPrefix}`, maybeCall, consoleLike)
708
- };
709
- return logger;
710
- };
711
- const passThrough = (fn) => fn();
712
- function createLogger(prefix, maybeCall = passThrough) {
713
- return _createLogger(prefix, maybeCall, createConsole());
714
- }
715
- const STRING_FORMAT = "utf8";
716
- function assert(truthyValue, message) {
717
- if (!truthyValue || "string" != typeof truthyValue || isEmpty(truthyValue)) throw new Error(message);
718
- }
719
- function isEmpty(truthyValue) {
720
- if (0 === truthyValue.trim().length) return true;
721
- return false;
722
- }
723
- function removeTrailingSlash(url) {
724
- return url?.replace(/\/+$/, "");
725
- }
726
- async function retriable(fn, props) {
727
- let lastError = null;
728
- for (let i = 0; i < props.retryCount + 1; i++) {
729
- if (i > 0) await new Promise((r) => setTimeout(r, props.retryDelay));
730
- try {
731
- const res = await fn();
732
- return res;
733
- } catch (e) {
734
- lastError = e;
735
- if (!props.retryCheck(e)) throw e;
736
- }
737
- }
738
- throw lastError;
739
- }
740
- function currentISOTime() {
741
- return (/* @__PURE__ */ new Date()).toISOString();
742
- }
743
- function safeSetTimeout(fn, timeout) {
744
- const t = setTimeout(fn, timeout);
745
- t?.unref && t?.unref();
746
- return t;
747
- }
748
- const isError = (x) => x instanceof Error;
749
- function allSettled(promises) {
750
- return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({
751
- status: "fulfilled",
752
- value
753
- }), (reason) => ({
754
- status: "rejected",
755
- reason
756
- }))));
757
- }
758
- class SimpleEventEmitter {
759
- constructor() {
760
- this.events = {};
761
- this.events = {};
762
- }
763
- on(event, listener) {
764
- if (!this.events[event]) this.events[event] = [];
765
- this.events[event].push(listener);
766
- return () => {
767
- this.events[event] = this.events[event].filter((x) => x !== listener);
768
- };
769
- }
770
- emit(event, payload) {
771
- for (const listener of this.events[event] || []) listener(payload);
772
- for (const listener of this.events["*"] || []) listener(event, payload);
773
- }
774
- }
775
- function isGzipSupported() {
776
- return "CompressionStream" in globalThis;
777
- }
778
- async function gzipCompress(input, isDebug = true) {
779
- try {
780
- const dataStream = new Blob([
781
- input
782
- ], {
783
- type: "text/plain"
784
- }).stream();
785
- const compressedStream = dataStream.pipeThrough(new CompressionStream("gzip"));
786
- return await new Response(compressedStream).blob();
787
- } catch (error) {
788
- if (isDebug) console.error("Failed to gzip compress data", error);
789
- return null;
790
- }
791
- }
792
- class PostHogFetchHttpError extends Error {
793
- constructor(response, reqByteLength) {
794
- super("HTTP error while fetching PostHog: status=" + response.status + ", reqByteLength=" + reqByteLength), this.response = response, this.reqByteLength = reqByteLength, this.name = "PostHogFetchHttpError";
795
- }
796
- get status() {
797
- return this.response.status;
798
- }
799
- get text() {
800
- return this.response.text();
801
- }
802
- get json() {
803
- return this.response.json();
804
- }
805
- }
806
- class PostHogFetchNetworkError extends Error {
807
- constructor(error) {
808
- super("Network error while fetching PostHog", error instanceof Error ? {
809
- cause: error
810
- } : {}), this.error = error, this.name = "PostHogFetchNetworkError";
811
- }
812
- }
813
- async function logFlushError(err) {
814
- if (err instanceof PostHogFetchHttpError) {
815
- let text = "";
816
- try {
817
- text = await err.text;
818
- } catch {
819
- }
820
- console.error(`Error while flushing PostHog: message=${err.message}, response body=${text}`, err);
821
- } else console.error("Error while flushing PostHog", err);
822
- return Promise.resolve();
823
- }
824
- function isPostHogFetchError(err) {
825
- return "object" == typeof err && (err instanceof PostHogFetchHttpError || err instanceof PostHogFetchNetworkError);
826
- }
827
- function isPostHogFetchContentTooLargeError(err) {
828
- return "object" == typeof err && err instanceof PostHogFetchHttpError && 413 === err.status;
829
- }
830
- class PostHogCoreStateless {
831
- constructor(apiKey, options = {}) {
832
- this.flushPromise = null;
833
- this.shutdownPromise = null;
834
- this.promiseQueue = new PromiseQueue();
835
- this._events = new SimpleEventEmitter();
836
- this._isInitialized = false;
837
- assert(apiKey, "You must pass your PostHog project's api key.");
838
- this.apiKey = apiKey;
839
- this.host = removeTrailingSlash(options.host || "https://us.i.posthog.com");
840
- this.flushAt = options.flushAt ? Math.max(options.flushAt, 1) : 20;
841
- this.maxBatchSize = Math.max(this.flushAt, options.maxBatchSize ?? 100);
842
- this.maxQueueSize = Math.max(this.flushAt, options.maxQueueSize ?? 1e3);
843
- this.flushInterval = options.flushInterval ?? 1e4;
844
- this.preloadFeatureFlags = options.preloadFeatureFlags ?? true;
845
- this.defaultOptIn = options.defaultOptIn ?? true;
846
- this.disableSurveys = options.disableSurveys ?? false;
847
- this._retryOptions = {
848
- retryCount: options.fetchRetryCount ?? 3,
849
- retryDelay: options.fetchRetryDelay ?? 3e3,
850
- retryCheck: isPostHogFetchError
851
- };
852
- this.requestTimeout = options.requestTimeout ?? 1e4;
853
- this.featureFlagsRequestTimeoutMs = options.featureFlagsRequestTimeoutMs ?? 3e3;
854
- this.remoteConfigRequestTimeoutMs = options.remoteConfigRequestTimeoutMs ?? 3e3;
855
- this.disableGeoip = options.disableGeoip ?? true;
856
- this.disabled = options.disabled ?? false;
857
- this.historicalMigration = options?.historicalMigration ?? false;
858
- this.evaluationEnvironments = options?.evaluationEnvironments;
859
- this._initPromise = Promise.resolve();
860
- this._isInitialized = true;
861
- this._logger = createLogger("[PostHog]", this.logMsgIfDebug.bind(this));
862
- this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
863
- }
864
- logMsgIfDebug(fn) {
865
- if (this.isDebug) fn();
866
- }
867
- wrap(fn) {
868
- if (this.disabled) return void this._logger.warn("The client is disabled");
869
- if (this._isInitialized) return fn();
870
- this._initPromise.then(() => fn());
871
- }
872
- getCommonEventProperties() {
873
- return {
874
- $lib: this.getLibraryId(),
875
- $lib_version: this.getLibraryVersion()
876
- };
877
- }
878
- get optedOut() {
879
- return this.getPersistedProperty(types_PostHogPersistedProperty.OptedOut) ?? !this.defaultOptIn;
880
- }
881
- async optIn() {
882
- this.wrap(() => {
883
- this.setPersistedProperty(types_PostHogPersistedProperty.OptedOut, false);
884
- });
885
- }
886
- async optOut() {
887
- this.wrap(() => {
888
- this.setPersistedProperty(types_PostHogPersistedProperty.OptedOut, true);
889
- });
890
- }
891
- on(event, cb) {
892
- return this._events.on(event, cb);
893
- }
894
- debug(enabled = true) {
895
- this.removeDebugCallback?.();
896
- if (enabled) {
897
- const removeDebugCallback = this.on("*", (event, payload) => this._logger.info(event, payload));
898
- this.removeDebugCallback = () => {
899
- removeDebugCallback();
900
- this.removeDebugCallback = void 0;
901
- };
902
- }
903
- }
904
- get isDebug() {
905
- return !!this.removeDebugCallback;
906
- }
907
- get isDisabled() {
908
- return this.disabled;
909
- }
910
- buildPayload(payload) {
911
- return {
912
- distinct_id: payload.distinct_id,
913
- event: payload.event,
914
- properties: {
915
- ...payload.properties || {},
916
- ...this.getCommonEventProperties()
917
- }
918
- };
919
- }
920
- addPendingPromise(promise) {
921
- return this.promiseQueue.add(promise);
922
- }
923
- identifyStateless(distinctId, properties, options) {
924
- this.wrap(() => {
925
- const payload = {
926
- ...this.buildPayload({
927
- distinct_id: distinctId,
928
- event: "$identify",
929
- properties
930
- })
931
- };
932
- this.enqueue("identify", payload, options);
933
- });
934
- }
935
- async identifyStatelessImmediate(distinctId, properties, options) {
936
- const payload = {
937
- ...this.buildPayload({
938
- distinct_id: distinctId,
939
- event: "$identify",
940
- properties
941
- })
942
- };
943
- await this.sendImmediate("identify", payload, options);
944
- }
945
- captureStateless(distinctId, event, properties, options) {
946
- this.wrap(() => {
947
- const payload = this.buildPayload({
948
- distinct_id: distinctId,
949
- event,
950
- properties
951
- });
952
- this.enqueue("capture", payload, options);
953
- });
954
- }
955
- async captureStatelessImmediate(distinctId, event, properties, options) {
956
- const payload = this.buildPayload({
957
- distinct_id: distinctId,
958
- event,
959
- properties
960
- });
961
- await this.sendImmediate("capture", payload, options);
962
- }
963
- aliasStateless(alias, distinctId, properties, options) {
964
- this.wrap(() => {
965
- const payload = this.buildPayload({
966
- event: "$create_alias",
967
- distinct_id: distinctId,
968
- properties: {
969
- ...properties || {},
970
- distinct_id: distinctId,
971
- alias
972
- }
973
- });
974
- this.enqueue("alias", payload, options);
975
- });
976
- }
977
- async aliasStatelessImmediate(alias, distinctId, properties, options) {
978
- const payload = this.buildPayload({
979
- event: "$create_alias",
980
- distinct_id: distinctId,
981
- properties: {
982
- ...properties || {},
983
- distinct_id: distinctId,
984
- alias
985
- }
986
- });
987
- await this.sendImmediate("alias", payload, options);
988
- }
989
- groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties) {
990
- this.wrap(() => {
991
- const payload = this.buildPayload({
992
- distinct_id: distinctId || `$${groupType}_${groupKey}`,
993
- event: "$groupidentify",
994
- properties: {
995
- $group_type: groupType,
996
- $group_key: groupKey,
997
- $group_set: groupProperties || {},
998
- ...eventProperties || {}
999
- }
1000
- });
1001
- this.enqueue("capture", payload, options);
1002
- });
1003
- }
1004
- async getRemoteConfig() {
1005
- await this._initPromise;
1006
- let host = this.host;
1007
- if ("https://us.i.posthog.com" === host) host = "https://us-assets.i.posthog.com";
1008
- else if ("https://eu.i.posthog.com" === host) host = "https://eu-assets.i.posthog.com";
1009
- const url = `${host}/array/${this.apiKey}/config`;
1010
- const fetchOptions = {
1011
- method: "GET",
1012
- headers: {
1013
- ...this.getCustomHeaders(),
1014
- "Content-Type": "application/json"
1015
- }
1016
- };
1017
- return this.fetchWithRetry(url, fetchOptions, {
1018
- retryCount: 0
1019
- }, this.remoteConfigRequestTimeoutMs).then((response) => response.json()).catch((error) => {
1020
- this._logger.error("Remote config could not be loaded", error);
1021
- this._events.emit("error", error);
1022
- });
1023
- }
1024
- async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}, fetchConfig = true) {
1025
- await this._initPromise;
1026
- const configParam = fetchConfig ? "&config=true" : "";
1027
- const url = `${this.host}/flags/?v=2${configParam}`;
1028
- const requestData = {
1029
- token: this.apiKey,
1030
- distinct_id: distinctId,
1031
- groups,
1032
- person_properties: personProperties,
1033
- group_properties: groupProperties,
1034
- ...extraPayload
1035
- };
1036
- if (this.evaluationEnvironments && this.evaluationEnvironments.length > 0) requestData.evaluation_environments = this.evaluationEnvironments;
1037
- const fetchOptions = {
1038
- method: "POST",
1039
- headers: {
1040
- ...this.getCustomHeaders(),
1041
- "Content-Type": "application/json"
1042
- },
1043
- body: JSON.stringify(requestData)
1044
- };
1045
- this._logger.info("Flags URL", url);
1046
- return this.fetchWithRetry(url, fetchOptions, {
1047
- retryCount: 0
1048
- }, this.featureFlagsRequestTimeoutMs).then((response) => response.json()).then((response) => normalizeFlagsResponse(response)).catch((error) => {
1049
- this._events.emit("error", error);
1050
- });
1051
- }
1052
- async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1053
- await this._initPromise;
1054
- const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
1055
- if (void 0 === flagDetailResponse) return {
1056
- response: void 0,
1057
- requestId: void 0
1058
- };
1059
- let response = getFeatureFlagValue(flagDetailResponse.response);
1060
- if (void 0 === response) response = false;
1061
- return {
1062
- response,
1063
- requestId: flagDetailResponse.requestId
1064
- };
1065
- }
1066
- async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1067
- await this._initPromise;
1068
- const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [
1069
- key
1070
- ]);
1071
- if (void 0 === flagsResponse) return;
1072
- const featureFlags = flagsResponse.flags;
1073
- const flagDetail = featureFlags[key];
1074
- return {
1075
- response: flagDetail,
1076
- requestId: flagsResponse.requestId,
1077
- evaluatedAt: flagsResponse.evaluatedAt
1078
- };
1079
- }
1080
- async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1081
- await this._initPromise;
1082
- const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [
1083
- key
1084
- ]);
1085
- if (!payloads) return;
1086
- const response = payloads[key];
1087
- if (void 0 === response) return null;
1088
- return response;
1089
- }
1090
- async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1091
- await this._initPromise;
1092
- const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate)).payloads;
1093
- return payloads;
1094
- }
1095
- async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1096
- await this._initPromise;
1097
- return await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1098
- }
1099
- async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1100
- await this._initPromise;
1101
- const featureFlagDetails = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1102
- if (!featureFlagDetails) return {
1103
- flags: void 0,
1104
- payloads: void 0,
1105
- requestId: void 0
1106
- };
1107
- return {
1108
- flags: featureFlagDetails.featureFlags,
1109
- payloads: featureFlagDetails.featureFlagPayloads,
1110
- requestId: featureFlagDetails.requestId
1111
- };
1112
- }
1113
- async getFeatureFlagDetailsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1114
- await this._initPromise;
1115
- const extraPayload = {};
1116
- if (disableGeoip ?? this.disableGeoip) extraPayload["geoip_disable"] = true;
1117
- if (flagKeysToEvaluate) extraPayload["flag_keys_to_evaluate"] = flagKeysToEvaluate;
1118
- const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
1119
- if (void 0 === flagsResponse) return;
1120
- if (flagsResponse.errorsWhileComputingFlags) console.error("[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices");
1121
- if (flagsResponse.quotaLimited?.includes("feature_flags")) {
1122
- console.warn("[FEATURE FLAGS] Feature flags quota limit exceeded - feature flags unavailable. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts");
1123
- return {
1124
- flags: {},
1125
- featureFlags: {},
1126
- featureFlagPayloads: {},
1127
- requestId: flagsResponse?.requestId,
1128
- quotaLimited: flagsResponse.quotaLimited
1129
- };
1130
- }
1131
- return flagsResponse;
1132
- }
1133
- async getSurveysStateless() {
1134
- await this._initPromise;
1135
- if (true === this.disableSurveys) {
1136
- this._logger.info("Loading surveys is disabled.");
1137
- return [];
1138
- }
1139
- const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
1140
- const fetchOptions = {
1141
- method: "GET",
1142
- headers: {
1143
- ...this.getCustomHeaders(),
1144
- "Content-Type": "application/json"
1145
- }
1146
- };
1147
- const response = await this.fetchWithRetry(url, fetchOptions).then((response2) => {
1148
- if (200 !== response2.status || !response2.json) {
1149
- const msg = `Surveys API could not be loaded: ${response2.status}`;
1150
- const error = new Error(msg);
1151
- this._logger.error(error);
1152
- this._events.emit("error", new Error(msg));
1153
- return;
1154
- }
1155
- return response2.json();
1156
- }).catch((error) => {
1157
- this._logger.error("Surveys API could not be loaded", error);
1158
- this._events.emit("error", error);
1159
- });
1160
- const newSurveys = response?.surveys;
1161
- if (newSurveys) this._logger.info("Surveys fetched from API: ", JSON.stringify(newSurveys));
1162
- return newSurveys ?? [];
1163
- }
1164
- get props() {
1165
- if (!this._props) this._props = this.getPersistedProperty(types_PostHogPersistedProperty.Props);
1166
- return this._props || {};
1167
- }
1168
- set props(val) {
1169
- this._props = val;
1170
- }
1171
- async register(properties) {
1172
- this.wrap(() => {
1173
- this.props = {
1174
- ...this.props,
1175
- ...properties
1176
- };
1177
- this.setPersistedProperty(types_PostHogPersistedProperty.Props, this.props);
1178
- });
1179
- }
1180
- async unregister(property) {
1181
- this.wrap(() => {
1182
- delete this.props[property];
1183
- this.setPersistedProperty(types_PostHogPersistedProperty.Props, this.props);
1184
- });
1185
- }
1186
- enqueue(type, _message, options) {
1187
- this.wrap(() => {
1188
- if (this.optedOut) return void this._events.emit(type, "Library is disabled. Not sending event. To re-enable, call posthog.optIn()");
1189
- const message = this.prepareMessage(type, _message, options);
1190
- const queue = this.getPersistedProperty(types_PostHogPersistedProperty.Queue) || [];
1191
- if (queue.length >= this.maxQueueSize) {
1192
- queue.shift();
1193
- this._logger.info("Queue is full, the oldest event is dropped.");
1194
- }
1195
- queue.push({
1196
- message
1197
- });
1198
- this.setPersistedProperty(types_PostHogPersistedProperty.Queue, queue);
1199
- this._events.emit(type, message);
1200
- if (queue.length >= this.flushAt) this.flushBackground();
1201
- if (this.flushInterval && !this._flushTimer) this._flushTimer = safeSetTimeout(() => this.flushBackground(), this.flushInterval);
1202
- });
1203
- }
1204
- async sendImmediate(type, _message, options) {
1205
- if (this.disabled) return void this._logger.warn("The client is disabled");
1206
- if (!this._isInitialized) await this._initPromise;
1207
- if (this.optedOut) return void this._events.emit(type, "Library is disabled. Not sending event. To re-enable, call posthog.optIn()");
1208
- const data = {
1209
- api_key: this.apiKey,
1210
- batch: [
1211
- this.prepareMessage(type, _message, options)
1212
- ],
1213
- sent_at: currentISOTime()
1214
- };
1215
- if (this.historicalMigration) data.historical_migration = true;
1216
- const payload = JSON.stringify(data);
1217
- const url = `${this.host}/batch/`;
1218
- const gzippedPayload = this.disableCompression ? null : await gzipCompress(payload, this.isDebug);
1219
- const fetchOptions = {
1220
- method: "POST",
1221
- headers: {
1222
- ...this.getCustomHeaders(),
1223
- "Content-Type": "application/json",
1224
- ...null !== gzippedPayload && {
1225
- "Content-Encoding": "gzip"
1226
- }
1227
- },
1228
- body: gzippedPayload || payload
1229
- };
1230
- try {
1231
- await this.fetchWithRetry(url, fetchOptions);
1232
- } catch (err) {
1233
- this._events.emit("error", err);
1234
- }
1235
- }
1236
- prepareMessage(type, _message, options) {
1237
- const message = {
1238
- ..._message,
1239
- type,
1240
- library: this.getLibraryId(),
1241
- library_version: this.getLibraryVersion(),
1242
- timestamp: options?.timestamp ? options?.timestamp : currentISOTime(),
1243
- uuid: options?.uuid ? options.uuid : uuidv7()
1244
- };
1245
- const addGeoipDisableProperty = options?.disableGeoip ?? this.disableGeoip;
1246
- if (addGeoipDisableProperty) {
1247
- if (!message.properties) message.properties = {};
1248
- message["properties"]["$geoip_disable"] = true;
1249
- }
1250
- if (message.distinctId) {
1251
- message.distinct_id = message.distinctId;
1252
- delete message.distinctId;
1253
- }
1254
- return message;
1255
- }
1256
- clearFlushTimer() {
1257
- if (this._flushTimer) {
1258
- clearTimeout(this._flushTimer);
1259
- this._flushTimer = void 0;
1260
- }
1261
- }
1262
- flushBackground() {
1263
- this.flush().catch(async (err) => {
1264
- await logFlushError(err);
1265
- });
1266
- }
1267
- async flush() {
1268
- const nextFlushPromise = allSettled([
1269
- this.flushPromise
1270
- ]).then(() => this._flush());
1271
- this.flushPromise = nextFlushPromise;
1272
- this.addPendingPromise(nextFlushPromise);
1273
- allSettled([
1274
- nextFlushPromise
1275
- ]).then(() => {
1276
- if (this.flushPromise === nextFlushPromise) this.flushPromise = null;
1277
- });
1278
- return nextFlushPromise;
1279
- }
1280
- getCustomHeaders() {
1281
- const customUserAgent = this.getCustomUserAgent();
1282
- const headers = {};
1283
- if (customUserAgent && "" !== customUserAgent) headers["User-Agent"] = customUserAgent;
1284
- return headers;
1285
- }
1286
- async _flush() {
1287
- this.clearFlushTimer();
1288
- await this._initPromise;
1289
- let queue = this.getPersistedProperty(types_PostHogPersistedProperty.Queue) || [];
1290
- if (!queue.length) return;
1291
- const sentMessages = [];
1292
- const originalQueueLength = queue.length;
1293
- while (queue.length > 0 && sentMessages.length < originalQueueLength) {
1294
- const batchItems = queue.slice(0, this.maxBatchSize);
1295
- const batchMessages = batchItems.map((item) => item.message);
1296
- const persistQueueChange = () => {
1297
- const refreshedQueue = this.getPersistedProperty(types_PostHogPersistedProperty.Queue) || [];
1298
- const newQueue = refreshedQueue.slice(batchItems.length);
1299
- this.setPersistedProperty(types_PostHogPersistedProperty.Queue, newQueue);
1300
- queue = newQueue;
1301
- };
1302
- const data = {
1303
- api_key: this.apiKey,
1304
- batch: batchMessages,
1305
- sent_at: currentISOTime()
1306
- };
1307
- if (this.historicalMigration) data.historical_migration = true;
1308
- const payload = JSON.stringify(data);
1309
- const url = `${this.host}/batch/`;
1310
- const gzippedPayload = this.disableCompression ? null : await gzipCompress(payload, this.isDebug);
1311
- const fetchOptions = {
1312
- method: "POST",
1313
- headers: {
1314
- ...this.getCustomHeaders(),
1315
- "Content-Type": "application/json",
1316
- ...null !== gzippedPayload && {
1317
- "Content-Encoding": "gzip"
1318
- }
1319
- },
1320
- body: gzippedPayload || payload
1321
- };
1322
- const retryOptions = {
1323
- retryCheck: (err) => {
1324
- if (isPostHogFetchContentTooLargeError(err)) return false;
1325
- return isPostHogFetchError(err);
1326
- }
1327
- };
1328
- try {
1329
- await this.fetchWithRetry(url, fetchOptions, retryOptions);
1330
- } catch (err) {
1331
- if (isPostHogFetchContentTooLargeError(err) && batchMessages.length > 1) {
1332
- this.maxBatchSize = Math.max(1, Math.floor(batchMessages.length / 2));
1333
- this._logger.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`);
1334
- continue;
1335
- }
1336
- if (!(err instanceof PostHogFetchNetworkError)) persistQueueChange();
1337
- this._events.emit("error", err);
1338
- throw err;
1339
- }
1340
- persistQueueChange();
1341
- sentMessages.push(...batchMessages);
1342
- }
1343
- this._events.emit("flush", sentMessages);
1344
- }
1345
- async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1346
- AbortSignal.timeout ??= function(ms) {
1347
- const ctrl = new AbortController();
1348
- setTimeout(() => ctrl.abort(), ms);
1349
- return ctrl.signal;
1350
- };
1351
- const body = options.body ? options.body : "";
1352
- let reqByteLength = -1;
1353
- try {
1354
- reqByteLength = body instanceof Blob ? body.size : Buffer.byteLength(body, STRING_FORMAT);
1355
- } catch {
1356
- if (body instanceof Blob) reqByteLength = body.size;
1357
- else {
1358
- const encoded = new TextEncoder().encode(body);
1359
- reqByteLength = encoded.length;
1360
- }
1361
- }
1362
- return await retriable(async () => {
1363
- let res = null;
1364
- try {
1365
- res = await this.fetch(url, {
1366
- signal: AbortSignal.timeout(requestTimeout ?? this.requestTimeout),
1367
- ...options
1368
- });
1369
- } catch (e) {
1370
- throw new PostHogFetchNetworkError(e);
1371
- }
1372
- const isNoCors = "no-cors" === options.mode;
1373
- if (!isNoCors && (res.status < 200 || res.status >= 400)) throw new PostHogFetchHttpError(res, reqByteLength);
1374
- return res;
1375
- }, {
1376
- ...this._retryOptions,
1377
- ...retryOptions
1378
- });
1379
- }
1380
- async _shutdown(shutdownTimeoutMs = 3e4) {
1381
- await this._initPromise;
1382
- let hasTimedOut = false;
1383
- this.clearFlushTimer();
1384
- const doShutdown = async () => {
1385
- try {
1386
- await this.promiseQueue.join();
1387
- while (true) {
1388
- const queue = this.getPersistedProperty(types_PostHogPersistedProperty.Queue) || [];
1389
- if (0 === queue.length) break;
1390
- await this.flush();
1391
- if (hasTimedOut) break;
1392
- }
1393
- } catch (e) {
1394
- if (!isPostHogFetchError(e)) throw e;
1395
- await logFlushError(e);
1396
- }
1397
- };
1398
- return Promise.race([
1399
- new Promise((_, reject) => {
1400
- safeSetTimeout(() => {
1401
- this._logger.error("Timed out while shutting down PostHog");
1402
- hasTimedOut = true;
1403
- reject("Timeout while shutting down PostHog. Some events may not have been sent.");
1404
- }, shutdownTimeoutMs);
1405
- }),
1406
- doShutdown()
1407
- ]);
1408
- }
1409
- async shutdown(shutdownTimeoutMs = 3e4) {
1410
- if (this.shutdownPromise) this._logger.warn("shutdown() called while already shutting down. shutdown() is meant to be called once before process exit - use flush() for per-request cleanup");
1411
- else this.shutdownPromise = this._shutdown(shutdownTimeoutMs).finally(() => {
1412
- this.shutdownPromise = null;
1413
- });
1414
- return this.shutdownPromise;
1415
- }
1416
- }
1417
- let parsedStackResults;
1418
- let lastKeysCount;
1419
- let cachedFilenameChunkIds;
1420
- function getFilenameToChunkIdMap(stackParser) {
1421
- const chunkIdMap = globalThis._posthogChunkIds;
1422
- if (!chunkIdMap) return;
1423
- const chunkIdKeys = Object.keys(chunkIdMap);
1424
- if (cachedFilenameChunkIds && chunkIdKeys.length === lastKeysCount) return cachedFilenameChunkIds;
1425
- lastKeysCount = chunkIdKeys.length;
1426
- cachedFilenameChunkIds = chunkIdKeys.reduce((acc, stackKey) => {
1427
- if (!parsedStackResults) parsedStackResults = {};
1428
- const result = parsedStackResults[stackKey];
1429
- if (result) acc[result[0]] = result[1];
1430
- else {
1431
- const parsedStack = stackParser(stackKey);
1432
- for (let i = parsedStack.length - 1; i >= 0; i--) {
1433
- const stackFrame = parsedStack[i];
1434
- const filename = stackFrame?.filename;
1435
- const chunkId = chunkIdMap[stackKey];
1436
- if (filename && chunkId) {
1437
- acc[filename] = chunkId;
1438
- parsedStackResults[stackKey] = [
1439
- filename,
1440
- chunkId
1441
- ];
1442
- break;
1443
- }
1444
- }
1445
- }
1446
- return acc;
1447
- }, {});
1448
- return cachedFilenameChunkIds;
1449
- }
1450
- const MAX_CAUSE_RECURSION = 4;
1451
- class ErrorPropertiesBuilder {
1452
- constructor(coercers, stackParser, modifiers = []) {
1453
- this.coercers = coercers;
1454
- this.stackParser = stackParser;
1455
- this.modifiers = modifiers;
1456
- }
1457
- buildFromUnknown(input, hint = {}) {
1458
- const providedMechanism = hint && hint.mechanism;
1459
- const mechanism = providedMechanism || {
1460
- handled: true,
1461
- type: "generic"
1462
- };
1463
- const coercingContext = this.buildCoercingContext(mechanism, hint, 0);
1464
- const exceptionWithCause = coercingContext.apply(input);
1465
- const parsingContext = this.buildParsingContext();
1466
- const exceptionWithStack = this.parseStacktrace(exceptionWithCause, parsingContext);
1467
- const exceptionList = this.convertToExceptionList(exceptionWithStack, mechanism);
1468
- return {
1469
- $exception_list: exceptionList,
1470
- $exception_level: "error"
1471
- };
1472
- }
1473
- async modifyFrames(exceptionList) {
1474
- for (const exc of exceptionList) if (exc.stacktrace && exc.stacktrace.frames && isArray(exc.stacktrace.frames)) exc.stacktrace.frames = await this.applyModifiers(exc.stacktrace.frames);
1475
- return exceptionList;
1476
- }
1477
- coerceFallback(ctx) {
1478
- return {
1479
- type: "Error",
1480
- value: "Unknown error",
1481
- stack: ctx.syntheticException?.stack,
1482
- synthetic: true
1483
- };
1484
- }
1485
- parseStacktrace(err, ctx) {
1486
- let cause;
1487
- if (null != err.cause) cause = this.parseStacktrace(err.cause, ctx);
1488
- let stack;
1489
- if ("" != err.stack && null != err.stack) stack = this.applyChunkIds(this.stackParser(err.stack, err.synthetic ? 1 : 0), ctx.chunkIdMap);
1490
- return {
1491
- ...err,
1492
- cause,
1493
- stack
1494
- };
1495
- }
1496
- applyChunkIds(frames, chunkIdMap) {
1497
- return frames.map((frame) => {
1498
- if (frame.filename && chunkIdMap) frame.chunk_id = chunkIdMap[frame.filename];
1499
- return frame;
1500
- });
1501
- }
1502
- applyCoercers(input, ctx) {
1503
- for (const adapter of this.coercers) if (adapter.match(input)) return adapter.coerce(input, ctx);
1504
- return this.coerceFallback(ctx);
1505
- }
1506
- async applyModifiers(frames) {
1507
- let newFrames = frames;
1508
- for (const modifier of this.modifiers) newFrames = await modifier(newFrames);
1509
- return newFrames;
1510
- }
1511
- convertToExceptionList(exceptionWithStack, mechanism) {
1512
- const currentException = {
1513
- type: exceptionWithStack.type,
1514
- value: exceptionWithStack.value,
1515
- mechanism: {
1516
- type: mechanism.type ?? "generic",
1517
- handled: mechanism.handled ?? true,
1518
- synthetic: exceptionWithStack.synthetic ?? false
1519
- }
1520
- };
1521
- if (exceptionWithStack.stack) currentException.stacktrace = {
1522
- type: "raw",
1523
- frames: exceptionWithStack.stack
1524
- };
1525
- const exceptionList = [
1526
- currentException
1527
- ];
1528
- if (null != exceptionWithStack.cause) exceptionList.push(...this.convertToExceptionList(exceptionWithStack.cause, {
1529
- ...mechanism,
1530
- handled: true
1531
- }));
1532
- return exceptionList;
1533
- }
1534
- buildParsingContext() {
1535
- const context = {
1536
- chunkIdMap: getFilenameToChunkIdMap(this.stackParser)
1537
- };
1538
- return context;
1539
- }
1540
- buildCoercingContext(mechanism, hint, depth = 0) {
1541
- const coerce = (input, depth2) => {
1542
- if (!(depth2 <= MAX_CAUSE_RECURSION)) return;
1543
- {
1544
- const ctx = this.buildCoercingContext(mechanism, hint, depth2);
1545
- return this.applyCoercers(input, ctx);
1546
- }
1547
- };
1548
- const context = {
1549
- ...hint,
1550
- syntheticException: 0 == depth ? hint.syntheticException : void 0,
1551
- mechanism,
1552
- apply: (input) => coerce(input, depth),
1553
- next: (input) => coerce(input, depth + 1)
1554
- };
1555
- return context;
1556
- }
1557
- }
1558
- const UNKNOWN_FUNCTION = "?";
1559
- const FILENAME_MATCH = /^\s*[-]{4,}$/;
1560
- const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/;
1561
- const nodeStackLineParser = (line, platform) => {
1562
- const lineMatch = line.match(FULL_MATCH);
1563
- if (lineMatch) {
1564
- let object;
1565
- let method;
1566
- let functionName;
1567
- let typeName;
1568
- let methodName;
1569
- if (lineMatch[1]) {
1570
- functionName = lineMatch[1];
1571
- let methodStart = functionName.lastIndexOf(".");
1572
- if ("." === functionName[methodStart - 1]) methodStart--;
1573
- if (methodStart > 0) {
1574
- object = functionName.slice(0, methodStart);
1575
- method = functionName.slice(methodStart + 1);
1576
- const objectEnd = object.indexOf(".Module");
1577
- if (objectEnd > 0) {
1578
- functionName = functionName.slice(objectEnd + 1);
1579
- object = object.slice(0, objectEnd);
1580
- }
1581
- }
1582
- typeName = void 0;
1583
- }
1584
- if (method) {
1585
- typeName = object;
1586
- methodName = method;
1587
- }
1588
- if ("<anonymous>" === method) {
1589
- methodName = void 0;
1590
- functionName = void 0;
1591
- }
1592
- if (void 0 === functionName) {
1593
- methodName = methodName || UNKNOWN_FUNCTION;
1594
- functionName = typeName ? `${typeName}.${methodName}` : methodName;
1595
- }
1596
- let filename = lineMatch[2]?.startsWith("file://") ? lineMatch[2].slice(7) : lineMatch[2];
1597
- const isNative = "native" === lineMatch[5];
1598
- if (filename?.match(/\/[A-Z]:/)) filename = filename.slice(1);
1599
- if (!filename && lineMatch[5] && !isNative) filename = lineMatch[5];
1600
- return {
1601
- filename: filename ? decodeURI(filename) : void 0,
1602
- module: void 0,
1603
- function: functionName,
1604
- lineno: _parseIntOrUndefined(lineMatch[3]),
1605
- colno: _parseIntOrUndefined(lineMatch[4]),
1606
- in_app: filenameIsInApp(filename || "", isNative),
1607
- platform
1608
- };
1609
- }
1610
- if (line.match(FILENAME_MATCH)) return {
1611
- filename: line,
1612
- platform
1613
- };
1614
- };
1615
- function filenameIsInApp(filename, isNative = false) {
1616
- const isInternal = isNative || filename && !filename.startsWith("/") && !filename.match(/^[A-Z]:/) && !filename.startsWith(".") && !filename.match(/^[a-zA-Z]([a-zA-Z0-9.\-+])*:\/\//);
1617
- return !isInternal && void 0 !== filename && !filename.includes("node_modules/");
1618
- }
1619
- function _parseIntOrUndefined(input) {
1620
- return parseInt(input || "", 10) || void 0;
1621
- }
1622
- const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/;
1623
- const STACKTRACE_FRAME_LIMIT = 50;
1624
- function reverseAndStripFrames(stack) {
1625
- if (!stack.length) return [];
1626
- const localStack = Array.from(stack);
1627
- localStack.reverse();
1628
- return localStack.slice(0, STACKTRACE_FRAME_LIMIT).map((frame) => ({
1629
- ...frame,
1630
- filename: frame.filename || getLastStackFrame(localStack).filename,
1631
- function: frame.function || UNKNOWN_FUNCTION
1632
- }));
1633
- }
1634
- function getLastStackFrame(arr) {
1635
- return arr[arr.length - 1] || {};
1636
- }
1637
- function createStackParser(platform, ...parsers) {
1638
- return (stack, skipFirstLines = 0) => {
1639
- const frames = [];
1640
- const lines = stack.split("\n");
1641
- for (let i = skipFirstLines; i < lines.length; i++) {
1642
- const line = lines[i];
1643
- if (line.length > 1024) continue;
1644
- const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, "$1") : line;
1645
- if (!cleanedLine.match(/\S*Error: /)) {
1646
- for (const parser of parsers) {
1647
- const frame = parser(cleanedLine, platform);
1648
- if (frame) {
1649
- frames.push(frame);
1650
- break;
1651
- }
1652
- }
1653
- if (frames.length >= STACKTRACE_FRAME_LIMIT) break;
1654
- }
1655
- }
1656
- return reverseAndStripFrames(frames);
1657
- };
1658
- }
1659
- class ErrorCoercer {
1660
- match(err) {
1661
- return isPlainError(err);
1662
- }
1663
- coerce(err, ctx) {
1664
- return {
1665
- type: this.getType(err),
1666
- value: this.getMessage(err, ctx),
1667
- stack: this.getStack(err),
1668
- cause: err.cause ? ctx.next(err.cause) : void 0,
1669
- synthetic: false
1670
- };
1671
- }
1672
- getType(err) {
1673
- return err.name || err.constructor.name;
1674
- }
1675
- getMessage(err, _ctx) {
1676
- const message = err.message;
1677
- if (message.error && "string" == typeof message.error.message) return String(message.error.message);
1678
- return String(message);
1679
- }
1680
- getStack(err) {
1681
- return err.stacktrace || err.stack || void 0;
1682
- }
1683
- }
1684
- const ERROR_TYPES_PATTERN = /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i;
1685
- class StringCoercer {
1686
- match(input) {
1687
- return "string" == typeof input;
1688
- }
1689
- coerce(input, ctx) {
1690
- const [type, value] = this.getInfos(input);
1691
- return {
1692
- type: type ?? "Error",
1693
- value: value ?? input,
1694
- stack: ctx.syntheticException?.stack,
1695
- synthetic: true
1696
- };
1697
- }
1698
- getInfos(candidate) {
1699
- let type = "Error";
1700
- let value = candidate;
1701
- const groups = candidate.match(ERROR_TYPES_PATTERN);
1702
- if (groups) {
1703
- type = groups[1];
1704
- value = groups[2];
1705
- }
1706
- return [
1707
- type,
1708
- value
1709
- ];
1710
- }
1711
- }
1712
- const severityLevels = [
1713
- "fatal",
1714
- "error",
1715
- "warning",
1716
- "log",
1717
- "info",
1718
- "debug"
1719
- ];
1720
- function extractExceptionKeysForMessage(err, maxLength = 40) {
1721
- const keys = Object.keys(err);
1722
- keys.sort();
1723
- if (!keys.length) return "[object has no keys]";
1724
- for (let i = keys.length; i > 0; i--) {
1725
- const serialized = keys.slice(0, i).join(", ");
1726
- if (!(serialized.length > maxLength)) {
1727
- if (i === keys.length) return serialized;
1728
- return serialized.length <= maxLength ? serialized : `${serialized.slice(0, maxLength)}...`;
1729
- }
1730
- }
1731
- return "";
1732
- }
1733
- class ObjectCoercer {
1734
- match(candidate) {
1735
- return "object" == typeof candidate && null !== candidate;
1736
- }
1737
- coerce(candidate, ctx) {
1738
- const errorProperty = this.getErrorPropertyFromObject(candidate);
1739
- if (errorProperty) return ctx.apply(errorProperty);
1740
- return {
1741
- type: this.getType(candidate),
1742
- value: this.getValue(candidate),
1743
- stack: ctx.syntheticException?.stack,
1744
- level: this.isSeverityLevel(candidate.level) ? candidate.level : "error",
1745
- synthetic: true
1746
- };
1747
- }
1748
- getType(err) {
1749
- return isEvent(err) ? err.constructor.name : "Error";
1750
- }
1751
- getValue(err) {
1752
- if ("name" in err && "string" == typeof err.name) {
1753
- let message = `'${err.name}' captured as exception`;
1754
- if ("message" in err && "string" == typeof err.message) message += ` with message: '${err.message}'`;
1755
- return message;
1756
- }
1757
- if ("message" in err && "string" == typeof err.message) return err.message;
1758
- const className = this.getObjectClassName(err);
1759
- const keys = extractExceptionKeysForMessage(err);
1760
- return `${className && "Object" !== className ? `'${className}'` : "Object"} captured as exception with keys: ${keys}`;
1761
- }
1762
- isSeverityLevel(x) {
1763
- return isString(x) && !isEmptyString(x) && severityLevels.indexOf(x) >= 0;
1764
- }
1765
- getErrorPropertyFromObject(obj) {
1766
- for (const prop in obj) if (Object.prototype.hasOwnProperty.call(obj, prop)) {
1767
- const value = obj[prop];
1768
- if (isError(value)) return value;
1769
- }
1770
- }
1771
- getObjectClassName(obj) {
1772
- try {
1773
- const prototype = Object.getPrototypeOf(obj);
1774
- return prototype ? prototype.constructor.name : void 0;
1775
- } catch (e) {
1776
- return;
1777
- }
1778
- }
1779
- }
1780
- class EventCoercer {
1781
- match(err) {
1782
- return isEvent(err);
1783
- }
1784
- coerce(evt, ctx) {
1785
- const constructorName = evt.constructor.name;
1786
- return {
1787
- type: constructorName,
1788
- value: `${constructorName} captured as exception with keys: ${extractExceptionKeysForMessage(evt)}`,
1789
- stack: ctx.syntheticException?.stack,
1790
- synthetic: true
1791
- };
1792
- }
1793
- }
1794
- class PrimitiveCoercer {
1795
- match(candidate) {
1796
- return isPrimitive(candidate);
1797
- }
1798
- coerce(value, ctx) {
1799
- return {
1800
- type: "Error",
1801
- value: `Primitive value captured as exception: ${String(value)}`,
1802
- stack: ctx.syntheticException?.stack,
1803
- synthetic: true
1804
- };
1805
- }
1806
- }
1807
- class ReduceableCache {
1808
- constructor(_maxSize) {
1809
- this._maxSize = _maxSize;
1810
- this._cache = /* @__PURE__ */ new Map();
1811
- }
1812
- get(key) {
1813
- const value = this._cache.get(key);
1814
- if (void 0 === value) return;
1815
- this._cache.delete(key);
1816
- this._cache.set(key, value);
1817
- return value;
1818
- }
1819
- set(key, value) {
1820
- this._cache.set(key, value);
1821
- }
1822
- reduce() {
1823
- while (this._cache.size >= this._maxSize) {
1824
- const value = this._cache.keys().next().value;
1825
- if (value) this._cache.delete(value);
1826
- }
1827
- }
1828
- }
1829
- const LRU_FILE_CONTENTS_CACHE = new ReduceableCache(25);
1830
- const LRU_FILE_CONTENTS_FS_READ_FAILED = new ReduceableCache(20);
1831
- const DEFAULT_LINES_OF_CONTEXT = 7;
1832
- const MAX_CONTEXTLINES_COLNO = 1e3;
1833
- const MAX_CONTEXTLINES_LINENO = 1e4;
1834
- async function addSourceContext(frames) {
1835
- const filesToLines = {};
1836
- for (let i = frames.length - 1; i >= 0; i--) {
1837
- const frame = frames[i];
1838
- const filename = frame?.filename;
1839
- if (!frame || "string" != typeof filename || "number" != typeof frame.lineno || shouldSkipContextLinesForFile(filename) || shouldSkipContextLinesForFrame(frame)) continue;
1840
- const filesToLinesOutput = filesToLines[filename];
1841
- if (!filesToLinesOutput) filesToLines[filename] = [];
1842
- filesToLines[filename].push(frame.lineno);
1843
- }
1844
- const files = Object.keys(filesToLines);
1845
- if (0 == files.length) return frames;
1846
- const readlinePromises = [];
1847
- for (const file of files) {
1848
- if (LRU_FILE_CONTENTS_FS_READ_FAILED.get(file)) continue;
1849
- const filesToLineRanges = filesToLines[file];
1850
- if (!filesToLineRanges) continue;
1851
- filesToLineRanges.sort((a, b) => a - b);
1852
- const ranges = makeLineReaderRanges(filesToLineRanges);
1853
- if (ranges.every((r) => rangeExistsInContentCache(file, r))) continue;
1854
- const cache = emplace(LRU_FILE_CONTENTS_CACHE, file, {});
1855
- readlinePromises.push(getContextLinesFromFile(file, ranges, cache));
1856
- }
1857
- await Promise.all(readlinePromises).catch(() => {
1858
- });
1859
- if (frames && frames.length > 0) addSourceContextToFrames(frames, LRU_FILE_CONTENTS_CACHE);
1860
- LRU_FILE_CONTENTS_CACHE.reduce();
1861
- return frames;
1862
- }
1863
- function getContextLinesFromFile(path2, ranges, output) {
1864
- return new Promise((resolve) => {
1865
- const stream = createReadStream(path2);
1866
- const lineReaded = createInterface({
1867
- input: stream
1868
- });
1869
- function destroyStreamAndResolve() {
1870
- stream.destroy();
1871
- resolve();
1872
- }
1873
- let lineNumber = 0;
1874
- let currentRangeIndex = 0;
1875
- const range = ranges[currentRangeIndex];
1876
- if (void 0 === range) return void destroyStreamAndResolve();
1877
- let rangeStart = range[0];
1878
- let rangeEnd = range[1];
1879
- function onStreamError() {
1880
- LRU_FILE_CONTENTS_FS_READ_FAILED.set(path2, 1);
1881
- lineReaded.close();
1882
- lineReaded.removeAllListeners();
1883
- destroyStreamAndResolve();
1884
- }
1885
- stream.on("error", onStreamError);
1886
- lineReaded.on("error", onStreamError);
1887
- lineReaded.on("close", destroyStreamAndResolve);
1888
- lineReaded.on("line", (line) => {
1889
- lineNumber++;
1890
- if (lineNumber < rangeStart) return;
1891
- output[lineNumber] = snipLine(line, 0);
1892
- if (lineNumber >= rangeEnd) {
1893
- if (currentRangeIndex === ranges.length - 1) {
1894
- lineReaded.close();
1895
- lineReaded.removeAllListeners();
1896
- return;
1897
- }
1898
- currentRangeIndex++;
1899
- const range2 = ranges[currentRangeIndex];
1900
- if (void 0 === range2) {
1901
- lineReaded.close();
1902
- lineReaded.removeAllListeners();
1903
- return;
1904
- }
1905
- rangeStart = range2[0];
1906
- rangeEnd = range2[1];
1907
- }
1908
- });
1909
- });
1910
- }
1911
- function addSourceContextToFrames(frames, cache) {
1912
- for (const frame of frames) if (frame.filename && void 0 === frame.context_line && "number" == typeof frame.lineno) {
1913
- const contents = cache.get(frame.filename);
1914
- if (void 0 === contents) continue;
1915
- addContextToFrame(frame.lineno, frame, contents);
1916
- }
1917
- }
1918
- function addContextToFrame(lineno, frame, contents) {
1919
- if (void 0 === frame.lineno || void 0 === contents) return;
1920
- frame.pre_context = [];
1921
- for (let i = makeRangeStart(lineno); i < lineno; i++) {
1922
- const line = contents[i];
1923
- if (void 0 === line) return void clearLineContext(frame);
1924
- frame.pre_context.push(line);
1925
- }
1926
- if (void 0 === contents[lineno]) return void clearLineContext(frame);
1927
- frame.context_line = contents[lineno];
1928
- const end = makeRangeEnd(lineno);
1929
- frame.post_context = [];
1930
- for (let i = lineno + 1; i <= end; i++) {
1931
- const line = contents[i];
1932
- if (void 0 === line) break;
1933
- frame.post_context.push(line);
1934
- }
1935
- }
1936
- function clearLineContext(frame) {
1937
- delete frame.pre_context;
1938
- delete frame.context_line;
1939
- delete frame.post_context;
1940
- }
1941
- function shouldSkipContextLinesForFile(path2) {
1942
- return path2.startsWith("node:") || path2.endsWith(".min.js") || path2.endsWith(".min.cjs") || path2.endsWith(".min.mjs") || path2.startsWith("data:");
1943
- }
1944
- function shouldSkipContextLinesForFrame(frame) {
1945
- if (void 0 !== frame.lineno && frame.lineno > MAX_CONTEXTLINES_LINENO) return true;
1946
- if (void 0 !== frame.colno && frame.colno > MAX_CONTEXTLINES_COLNO) return true;
1947
- return false;
1948
- }
1949
- function rangeExistsInContentCache(file, range) {
1950
- const contents = LRU_FILE_CONTENTS_CACHE.get(file);
1951
- if (void 0 === contents) return false;
1952
- for (let i = range[0]; i <= range[1]; i++) if (void 0 === contents[i]) return false;
1953
- return true;
1954
- }
1955
- function makeLineReaderRanges(lines) {
1956
- if (!lines.length) return [];
1957
- let i = 0;
1958
- const line = lines[0];
1959
- if ("number" != typeof line) return [];
1960
- let current = makeContextRange(line);
1961
- const out = [];
1962
- while (true) {
1963
- if (i === lines.length - 1) {
1964
- out.push(current);
1965
- break;
1966
- }
1967
- const next = lines[i + 1];
1968
- if ("number" != typeof next) break;
1969
- if (next <= current[1]) current[1] = next + DEFAULT_LINES_OF_CONTEXT;
1970
- else {
1971
- out.push(current);
1972
- current = makeContextRange(next);
1973
- }
1974
- i++;
1975
- }
1976
- return out;
1977
- }
1978
- function makeContextRange(line) {
1979
- return [
1980
- makeRangeStart(line),
1981
- makeRangeEnd(line)
1982
- ];
1983
- }
1984
- function makeRangeStart(line) {
1985
- return Math.max(1, line - DEFAULT_LINES_OF_CONTEXT);
1986
- }
1987
- function makeRangeEnd(line) {
1988
- return line + DEFAULT_LINES_OF_CONTEXT;
1989
- }
1990
- function emplace(map, key, contents) {
1991
- const value = map.get(key);
1992
- if (void 0 === value) {
1993
- map.set(key, contents);
1994
- return contents;
1995
- }
1996
- return value;
1997
- }
1998
- function snipLine(line, colno) {
1999
- let newLine = line;
2000
- const lineLength = newLine.length;
2001
- if (lineLength <= 150) return newLine;
2002
- if (colno > lineLength) colno = lineLength;
2003
- let start = Math.max(colno - 60, 0);
2004
- if (start < 5) start = 0;
2005
- let end = Math.min(start + 140, lineLength);
2006
- if (end > lineLength - 5) end = lineLength;
2007
- if (end === lineLength) start = Math.max(end - 140, 0);
2008
- newLine = newLine.slice(start, end);
2009
- if (start > 0) newLine = `...${newLine}`;
2010
- if (end < lineLength) newLine += "...";
2011
- return newLine;
2012
- }
2013
- function makeUncaughtExceptionHandler(captureFn, onFatalFn) {
2014
- let calledFatalError = false;
2015
- return Object.assign((error) => {
2016
- const userProvidedListenersCount = global.process.listeners("uncaughtException").filter((listener) => "domainUncaughtExceptionClear" !== listener.name && true !== listener._posthogErrorHandler).length;
2017
- const processWouldExit = 0 === userProvidedListenersCount;
2018
- captureFn(error, {
2019
- mechanism: {
2020
- type: "onuncaughtexception",
2021
- handled: false
2022
- }
2023
- });
2024
- if (!calledFatalError && processWouldExit) {
2025
- calledFatalError = true;
2026
- onFatalFn(error);
2027
- }
2028
- }, {
2029
- _posthogErrorHandler: true
2030
- });
2031
- }
2032
- function addUncaughtExceptionListener(captureFn, onFatalFn) {
2033
- globalThis.process?.on("uncaughtException", makeUncaughtExceptionHandler(captureFn, onFatalFn));
2034
- }
2035
- function addUnhandledRejectionListener(captureFn) {
2036
- globalThis.process?.on("unhandledRejection", (reason) => captureFn(reason, {
2037
- mechanism: {
2038
- type: "onunhandledrejection",
2039
- handled: false
2040
- }
2041
- }));
2042
- }
2043
- const SHUTDOWN_TIMEOUT = 2e3;
2044
- class ErrorTracking {
2045
- constructor(client2, options, _logger) {
2046
- this.client = client2;
2047
- this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false;
2048
- this._logger = _logger;
2049
- this._rateLimiter = new BucketedRateLimiter({
2050
- refillRate: 1,
2051
- bucketSize: 10,
2052
- refillInterval: 1e4,
2053
- _logger: this._logger
2054
- });
2055
- this.startAutocaptureIfEnabled();
2056
- }
2057
- static isPreviouslyCapturedError(x) {
2058
- return isObject(x) && "__posthog_previously_captured_error" in x && true === x.__posthog_previously_captured_error;
2059
- }
2060
- static async buildEventMessage(error, hint, distinctId, additionalProperties) {
2061
- const properties = {
2062
- ...additionalProperties
2063
- };
2064
- if (!distinctId) properties.$process_person_profile = false;
2065
- const exceptionProperties = this.errorPropertiesBuilder.buildFromUnknown(error, hint);
2066
- exceptionProperties.$exception_list = await this.errorPropertiesBuilder.modifyFrames(exceptionProperties.$exception_list);
2067
- return {
2068
- event: "$exception",
2069
- distinctId: distinctId || uuidv7(),
2070
- properties: {
2071
- ...exceptionProperties,
2072
- ...properties
2073
- }
2074
- };
2075
- }
2076
- startAutocaptureIfEnabled() {
2077
- if (this.isEnabled()) {
2078
- addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this));
2079
- addUnhandledRejectionListener(this.onException.bind(this));
2080
- }
2081
- }
2082
- onException(exception, hint) {
2083
- this.client.addPendingPromise((async () => {
2084
- if (!ErrorTracking.isPreviouslyCapturedError(exception)) {
2085
- const eventMessage = await ErrorTracking.buildEventMessage(exception, hint);
2086
- const exceptionProperties = eventMessage.properties;
2087
- const exceptionType = exceptionProperties?.$exception_list[0]?.type ?? "Exception";
2088
- const isRateLimited = this._rateLimiter.consumeRateLimit(exceptionType);
2089
- if (isRateLimited) return void this._logger.info("Skipping exception capture because of client rate limiting.", {
2090
- exception: exceptionType
2091
- });
2092
- return this.client.capture(eventMessage);
2093
- }
2094
- })());
2095
- }
2096
- async onFatalError(exception) {
2097
- console.error(exception);
2098
- await this.client.shutdown(SHUTDOWN_TIMEOUT);
2099
- process.exit(1);
2100
- }
2101
- isEnabled() {
2102
- return !this.client.isDisabled && this._exceptionAutocaptureEnabled;
2103
- }
2104
- shutdown() {
2105
- this._rateLimiter.stop();
2106
- }
2107
- }
2108
- const version = "5.20.0";
2109
- const FeatureFlagError = {
2110
- ERRORS_WHILE_COMPUTING: "errors_while_computing_flags",
2111
- FLAG_MISSING: "flag_missing",
2112
- QUOTA_LIMITED: "quota_limited",
2113
- UNKNOWN_ERROR: "unknown_error"
2114
- };
2115
- async function hashSHA1(text) {
2116
- const subtle = globalThis.crypto?.subtle;
2117
- if (!subtle) throw new Error("SubtleCrypto API not available");
2118
- const hashBuffer = await subtle.digest("SHA-1", new TextEncoder().encode(text));
2119
- const hashArray = Array.from(new Uint8Array(hashBuffer));
2120
- return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
2121
- }
2122
- const SIXTY_SECONDS = 6e4;
2123
- const LONG_SCALE = 1152921504606847e3;
2124
- const NULL_VALUES_ALLOWED_OPERATORS = [
2125
- "is_not"
2126
- ];
2127
- class ClientError extends Error {
2128
- constructor(message) {
2129
- super();
2130
- Error.captureStackTrace(this, this.constructor);
2131
- this.name = "ClientError";
2132
- this.message = message;
2133
- Object.setPrototypeOf(this, ClientError.prototype);
2134
- }
2135
- }
2136
- class InconclusiveMatchError extends Error {
2137
- constructor(message) {
2138
- super(message);
2139
- this.name = this.constructor.name;
2140
- Error.captureStackTrace(this, this.constructor);
2141
- Object.setPrototypeOf(this, InconclusiveMatchError.prototype);
2142
- }
2143
- }
2144
- class RequiresServerEvaluation extends Error {
2145
- constructor(message) {
2146
- super(message);
2147
- this.name = this.constructor.name;
2148
- Error.captureStackTrace(this, this.constructor);
2149
- Object.setPrototypeOf(this, RequiresServerEvaluation.prototype);
2150
- }
2151
- }
2152
- class FeatureFlagsPoller {
2153
- constructor({ pollingInterval, personalApiKey, projectApiKey, timeout, host, customHeaders, ...options }) {
2154
- this.debugMode = false;
2155
- this.shouldBeginExponentialBackoff = false;
2156
- this.backOffCount = 0;
2157
- this.pollingInterval = pollingInterval;
2158
- this.personalApiKey = personalApiKey;
2159
- this.featureFlags = [];
2160
- this.featureFlagsByKey = {};
2161
- this.groupTypeMapping = {};
2162
- this.cohorts = {};
2163
- this.loadedSuccessfullyOnce = false;
2164
- this.timeout = timeout;
2165
- this.projectApiKey = projectApiKey;
2166
- this.host = host;
2167
- this.poller = void 0;
2168
- this.fetch = options.fetch || fetch;
2169
- this.onError = options.onError;
2170
- this.customHeaders = customHeaders;
2171
- this.onLoad = options.onLoad;
2172
- this.cacheProvider = options.cacheProvider;
2173
- this.loadFeatureFlags();
2174
- }
2175
- debug(enabled = true) {
2176
- this.debugMode = enabled;
2177
- }
2178
- logMsgIfDebug(fn) {
2179
- if (this.debugMode) fn();
2180
- }
2181
- async getFeatureFlag(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}) {
2182
- await this.loadFeatureFlags();
2183
- let response;
2184
- let featureFlag;
2185
- if (!this.loadedSuccessfullyOnce) return response;
2186
- featureFlag = this.featureFlagsByKey[key];
2187
- if (void 0 !== featureFlag) try {
2188
- const result = await this.computeFlagAndPayloadLocally(featureFlag, distinctId, groups, personProperties, groupProperties);
2189
- response = result.value;
2190
- this.logMsgIfDebug(() => console.debug(`Successfully computed flag locally: ${key} -> ${response}`));
2191
- } catch (e) {
2192
- if (e instanceof RequiresServerEvaluation || e instanceof InconclusiveMatchError) this.logMsgIfDebug(() => console.debug(`${e.name} when computing flag locally: ${key}: ${e.message}`));
2193
- else if (e instanceof Error) this.onError?.(new Error(`Error computing flag locally: ${key}: ${e}`));
2194
- }
2195
- return response;
2196
- }
2197
- async getAllFlagsAndPayloads(distinctId, groups = {}, personProperties = {}, groupProperties = {}, flagKeysToExplicitlyEvaluate) {
2198
- await this.loadFeatureFlags();
2199
- const response = {};
2200
- const payloads = {};
2201
- let fallbackToFlags = 0 == this.featureFlags.length;
2202
- const flagsToEvaluate = flagKeysToExplicitlyEvaluate ? flagKeysToExplicitlyEvaluate.map((key) => this.featureFlagsByKey[key]).filter(Boolean) : this.featureFlags;
2203
- const sharedEvaluationCache = {};
2204
- await Promise.all(flagsToEvaluate.map(async (flag) => {
2205
- try {
2206
- const { value: matchValue, payload: matchPayload } = await this.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties, void 0, sharedEvaluationCache);
2207
- response[flag.key] = matchValue;
2208
- if (matchPayload) payloads[flag.key] = matchPayload;
2209
- } catch (e) {
2210
- if (e instanceof RequiresServerEvaluation || e instanceof InconclusiveMatchError) this.logMsgIfDebug(() => console.debug(`${e.name} when computing flag locally: ${flag.key}: ${e.message}`));
2211
- else if (e instanceof Error) this.onError?.(new Error(`Error computing flag locally: ${flag.key}: ${e}`));
2212
- fallbackToFlags = true;
2213
- }
2214
- }));
2215
- return {
2216
- response,
2217
- payloads,
2218
- fallbackToFlags
2219
- };
2220
- }
2221
- async computeFlagAndPayloadLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}, matchValue, evaluationCache, skipLoadCheck = false) {
2222
- if (!skipLoadCheck) await this.loadFeatureFlags();
2223
- if (!this.loadedSuccessfullyOnce) return {
2224
- value: false,
2225
- payload: null
2226
- };
2227
- let flagValue;
2228
- flagValue = void 0 !== matchValue ? matchValue : await this.computeFlagValueLocally(flag, distinctId, groups, personProperties, groupProperties, evaluationCache);
2229
- const payload = this.getFeatureFlagPayload(flag.key, flagValue);
2230
- return {
2231
- value: flagValue,
2232
- payload
2233
- };
2234
- }
2235
- async computeFlagValueLocally(flag, distinctId, groups = {}, personProperties = {}, groupProperties = {}, evaluationCache = {}) {
2236
- if (flag.ensure_experience_continuity) throw new InconclusiveMatchError("Flag has experience continuity enabled");
2237
- if (!flag.active) return false;
2238
- const flagFilters = flag.filters || {};
2239
- const aggregation_group_type_index = flagFilters.aggregation_group_type_index;
2240
- if (void 0 == aggregation_group_type_index) return await this.matchFeatureFlagProperties(flag, distinctId, personProperties, evaluationCache);
2241
- {
2242
- const groupName = this.groupTypeMapping[String(aggregation_group_type_index)];
2243
- if (!groupName) {
2244
- this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Unknown group type index ${aggregation_group_type_index} for feature flag ${flag.key}`));
2245
- throw new InconclusiveMatchError("Flag has unknown group type index");
2246
- }
2247
- if (!(groupName in groups)) {
2248
- this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Can't compute group feature flag: ${flag.key} without group names passed in`));
2249
- return false;
2250
- }
2251
- const focusedGroupProperties = groupProperties[groupName];
2252
- return await this.matchFeatureFlagProperties(flag, groups[groupName], focusedGroupProperties, evaluationCache);
2253
- }
2254
- }
2255
- getFeatureFlagPayload(key, flagValue) {
2256
- let payload = null;
2257
- if (false !== flagValue && null != flagValue) {
2258
- if ("boolean" == typeof flagValue) payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue.toString()] || null;
2259
- else if ("string" == typeof flagValue) payload = this.featureFlagsByKey?.[key]?.filters?.payloads?.[flagValue] || null;
2260
- if (null != payload) {
2261
- if ("object" == typeof payload) return payload;
2262
- if ("string" == typeof payload) try {
2263
- return JSON.parse(payload);
2264
- } catch {
2265
- }
2266
- return payload;
2267
- }
2268
- }
2269
- return null;
2270
- }
2271
- async evaluateFlagDependency(property, distinctId, properties, evaluationCache) {
2272
- const targetFlagKey = property.key;
2273
- if (!this.featureFlagsByKey) throw new InconclusiveMatchError("Feature flags not available for dependency evaluation");
2274
- if (!("dependency_chain" in property)) throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' is missing required 'dependency_chain' field`);
2275
- const dependencyChain = property.dependency_chain;
2276
- if (!Array.isArray(dependencyChain)) throw new InconclusiveMatchError(`Flag dependency property for '${targetFlagKey}' has an invalid 'dependency_chain' (expected array, got ${typeof dependencyChain})`);
2277
- if (0 === dependencyChain.length) throw new InconclusiveMatchError(`Circular dependency detected for flag '${targetFlagKey}' (empty dependency chain)`);
2278
- for (const depFlagKey of dependencyChain) {
2279
- if (!(depFlagKey in evaluationCache)) {
2280
- const depFlag = this.featureFlagsByKey[depFlagKey];
2281
- if (depFlag) if (depFlag.active) try {
2282
- const depResult = await this.matchFeatureFlagProperties(depFlag, distinctId, properties, evaluationCache);
2283
- evaluationCache[depFlagKey] = depResult;
2284
- } catch (error) {
2285
- throw new InconclusiveMatchError(`Error evaluating flag dependency '${depFlagKey}' for flag '${targetFlagKey}': ${error}`);
2286
- }
2287
- else evaluationCache[depFlagKey] = false;
2288
- else throw new InconclusiveMatchError(`Missing flag dependency '${depFlagKey}' for flag '${targetFlagKey}'`);
2289
- }
2290
- const cachedResult = evaluationCache[depFlagKey];
2291
- if (null == cachedResult) throw new InconclusiveMatchError(`Dependency '${depFlagKey}' could not be evaluated`);
2292
- }
2293
- const targetFlagValue = evaluationCache[targetFlagKey];
2294
- return this.flagEvaluatesToExpectedValue(property.value, targetFlagValue);
2295
- }
2296
- flagEvaluatesToExpectedValue(expectedValue, flagValue) {
2297
- if ("boolean" == typeof expectedValue) return expectedValue === flagValue || "string" == typeof flagValue && "" !== flagValue && true === expectedValue;
2298
- if ("string" == typeof expectedValue) return flagValue === expectedValue;
2299
- return false;
2300
- }
2301
- async matchFeatureFlagProperties(flag, distinctId, properties, evaluationCache = {}) {
2302
- const flagFilters = flag.filters || {};
2303
- const flagConditions = flagFilters.groups || [];
2304
- let isInconclusive = false;
2305
- let result;
2306
- for (const condition of flagConditions) try {
2307
- if (await this.isConditionMatch(flag, distinctId, condition, properties, evaluationCache)) {
2308
- const variantOverride = condition.variant;
2309
- const flagVariants = flagFilters.multivariate?.variants || [];
2310
- result = variantOverride && flagVariants.some((variant) => variant.key === variantOverride) ? variantOverride : await this.getMatchingVariant(flag, distinctId) || true;
2311
- break;
2312
- }
2313
- } catch (e) {
2314
- if (e instanceof RequiresServerEvaluation) throw e;
2315
- if (e instanceof InconclusiveMatchError) isInconclusive = true;
2316
- else throw e;
2317
- }
2318
- if (void 0 !== result) return result;
2319
- if (isInconclusive) throw new InconclusiveMatchError("Can't determine if feature flag is enabled or not with given properties");
2320
- return false;
2321
- }
2322
- async isConditionMatch(flag, distinctId, condition, properties, evaluationCache = {}) {
2323
- const rolloutPercentage = condition.rollout_percentage;
2324
- const warnFunction = (msg) => {
2325
- this.logMsgIfDebug(() => console.warn(msg));
2326
- };
2327
- if ((condition.properties || []).length > 0) {
2328
- for (const prop of condition.properties) {
2329
- const propertyType = prop.type;
2330
- let matches = false;
2331
- matches = "cohort" === propertyType ? matchCohort(prop, properties, this.cohorts, this.debugMode) : "flag" === propertyType ? await this.evaluateFlagDependency(prop, distinctId, properties, evaluationCache) : matchProperty(prop, properties, warnFunction);
2332
- if (!matches) return false;
2333
- }
2334
- if (void 0 == rolloutPercentage) return true;
2335
- }
2336
- if (void 0 != rolloutPercentage && await _hash(flag.key, distinctId) > rolloutPercentage / 100) return false;
2337
- return true;
2338
- }
2339
- async getMatchingVariant(flag, distinctId) {
2340
- const hashValue = await _hash(flag.key, distinctId, "variant");
2341
- const matchingVariant = this.variantLookupTable(flag).find((variant) => hashValue >= variant.valueMin && hashValue < variant.valueMax);
2342
- if (matchingVariant) return matchingVariant.key;
2343
- }
2344
- variantLookupTable(flag) {
2345
- const lookupTable = [];
2346
- let valueMin = 0;
2347
- let valueMax = 0;
2348
- const flagFilters = flag.filters || {};
2349
- const multivariates = flagFilters.multivariate?.variants || [];
2350
- multivariates.forEach((variant) => {
2351
- valueMax = valueMin + variant.rollout_percentage / 100;
2352
- lookupTable.push({
2353
- valueMin,
2354
- valueMax,
2355
- key: variant.key
2356
- });
2357
- valueMin = valueMax;
2358
- });
2359
- return lookupTable;
2360
- }
2361
- updateFlagState(flagData) {
2362
- this.featureFlags = flagData.flags;
2363
- this.featureFlagsByKey = flagData.flags.reduce((acc, curr) => (acc[curr.key] = curr, acc), {});
2364
- this.groupTypeMapping = flagData.groupTypeMapping;
2365
- this.cohorts = flagData.cohorts;
2366
- this.loadedSuccessfullyOnce = true;
2367
- }
2368
- async loadFromCache(debugMessage) {
2369
- if (!this.cacheProvider) return false;
2370
- try {
2371
- const cached = await this.cacheProvider.getFlagDefinitions();
2372
- if (cached) {
2373
- this.updateFlagState(cached);
2374
- this.logMsgIfDebug(() => console.debug(`[FEATURE FLAGS] ${debugMessage} (${cached.flags.length} flags)`));
2375
- this.onLoad?.(this.featureFlags.length);
2376
- return true;
2377
- }
2378
- return false;
2379
- } catch (err) {
2380
- this.onError?.(new Error(`Failed to load from cache: ${err}`));
2381
- return false;
2382
- }
2383
- }
2384
- async loadFeatureFlags(forceReload = false) {
2385
- if (this.loadedSuccessfullyOnce && !forceReload) return;
2386
- if (!forceReload && this.nextFetchAllowedAt && Date.now() < this.nextFetchAllowedAt) return void this.logMsgIfDebug(() => console.debug("[FEATURE FLAGS] Skipping fetch, in backoff period"));
2387
- if (!this.loadingPromise) this.loadingPromise = this._loadFeatureFlags().catch((err) => this.logMsgIfDebug(() => console.debug(`[FEATURE FLAGS] Failed to load feature flags: ${err}`))).finally(() => {
2388
- this.loadingPromise = void 0;
2389
- });
2390
- return this.loadingPromise;
2391
- }
2392
- isLocalEvaluationReady() {
2393
- return (this.loadedSuccessfullyOnce ?? false) && (this.featureFlags?.length ?? 0) > 0;
2394
- }
2395
- getPollingInterval() {
2396
- if (!this.shouldBeginExponentialBackoff) return this.pollingInterval;
2397
- return Math.min(SIXTY_SECONDS, this.pollingInterval * 2 ** this.backOffCount);
2398
- }
2399
- beginBackoff() {
2400
- this.shouldBeginExponentialBackoff = true;
2401
- this.backOffCount += 1;
2402
- this.nextFetchAllowedAt = Date.now() + this.getPollingInterval();
2403
- }
2404
- clearBackoff() {
2405
- this.shouldBeginExponentialBackoff = false;
2406
- this.backOffCount = 0;
2407
- this.nextFetchAllowedAt = void 0;
2408
- }
2409
- async _loadFeatureFlags() {
2410
- if (this.poller) {
2411
- clearTimeout(this.poller);
2412
- this.poller = void 0;
2413
- }
2414
- this.poller = setTimeout(() => this.loadFeatureFlags(true), this.getPollingInterval());
2415
- try {
2416
- let shouldFetch = true;
2417
- if (this.cacheProvider) try {
2418
- shouldFetch = await this.cacheProvider.shouldFetchFlagDefinitions();
2419
- } catch (err) {
2420
- this.onError?.(new Error(`Error in shouldFetchFlagDefinitions: ${err}`));
2421
- }
2422
- if (!shouldFetch) {
2423
- const loaded = await this.loadFromCache("Loaded flags from cache (skipped fetch)");
2424
- if (loaded) return;
2425
- if (this.loadedSuccessfullyOnce) return;
2426
- }
2427
- const res = await this._requestFeatureFlagDefinitions();
2428
- if (!res) return;
2429
- switch (res.status) {
2430
- case 304:
2431
- this.logMsgIfDebug(() => console.debug("[FEATURE FLAGS] Flags not modified (304), using cached data"));
2432
- this.flagsEtag = res.headers?.get("ETag") ?? this.flagsEtag;
2433
- this.loadedSuccessfullyOnce = true;
2434
- this.clearBackoff();
2435
- return;
2436
- case 401:
2437
- this.beginBackoff();
2438
- throw new ClientError(`Your project key or personal API key is invalid. Setting next polling interval to ${this.getPollingInterval()}ms. More information: https://posthog.com/docs/api#rate-limiting`);
2439
- case 402:
2440
- console.warn("[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all local flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts");
2441
- this.featureFlags = [];
2442
- this.featureFlagsByKey = {};
2443
- this.groupTypeMapping = {};
2444
- this.cohorts = {};
2445
- return;
2446
- case 403:
2447
- this.beginBackoff();
2448
- throw new ClientError(`Your personal API key does not have permission to fetch feature flag definitions for local evaluation. Setting next polling interval to ${this.getPollingInterval()}ms. Are you sure you're using the correct personal and Project API key pair? More information: https://posthog.com/docs/api/overview`);
2449
- case 429:
2450
- this.beginBackoff();
2451
- throw new ClientError(`You are being rate limited. Setting next polling interval to ${this.getPollingInterval()}ms. More information: https://posthog.com/docs/api#rate-limiting`);
2452
- case 200: {
2453
- const responseJson = await res.json() ?? {};
2454
- if (!("flags" in responseJson)) return void this.onError?.(new Error(`Invalid response when getting feature flags: ${JSON.stringify(responseJson)}`));
2455
- this.flagsEtag = res.headers?.get("ETag") ?? void 0;
2456
- const flagData = {
2457
- flags: responseJson.flags ?? [],
2458
- groupTypeMapping: responseJson.group_type_mapping || {},
2459
- cohorts: responseJson.cohorts || {}
2460
- };
2461
- this.updateFlagState(flagData);
2462
- this.clearBackoff();
2463
- if (this.cacheProvider && shouldFetch) try {
2464
- await this.cacheProvider.onFlagDefinitionsReceived(flagData);
2465
- } catch (err) {
2466
- this.onError?.(new Error(`Failed to store in cache: ${err}`));
2467
- }
2468
- this.onLoad?.(this.featureFlags.length);
2469
- break;
2470
- }
2471
- default:
2472
- return;
2473
- }
2474
- } catch (err) {
2475
- if (err instanceof ClientError) this.onError?.(err);
2476
- }
2477
- }
2478
- getPersonalApiKeyRequestOptions(method = "GET", etag) {
2479
- const headers = {
2480
- ...this.customHeaders,
2481
- "Content-Type": "application/json",
2482
- Authorization: `Bearer ${this.personalApiKey}`
2483
- };
2484
- if (etag) headers["If-None-Match"] = etag;
2485
- return {
2486
- method,
2487
- headers
2488
- };
2489
- }
2490
- _requestFeatureFlagDefinitions() {
2491
- const url = `${this.host}/api/feature_flag/local_evaluation?token=${this.projectApiKey}&send_cohorts`;
2492
- const options = this.getPersonalApiKeyRequestOptions("GET", this.flagsEtag);
2493
- let abortTimeout = null;
2494
- if (this.timeout && "number" == typeof this.timeout) {
2495
- const controller = new AbortController();
2496
- abortTimeout = safeSetTimeout(() => {
2497
- controller.abort();
2498
- }, this.timeout);
2499
- options.signal = controller.signal;
2500
- }
2501
- try {
2502
- const fetch1 = this.fetch;
2503
- return fetch1(url, options);
2504
- } finally {
2505
- clearTimeout(abortTimeout);
2506
- }
2507
- }
2508
- async stopPoller(timeoutMs = 3e4) {
2509
- clearTimeout(this.poller);
2510
- if (this.cacheProvider) try {
2511
- const shutdownResult = this.cacheProvider.shutdown();
2512
- if (shutdownResult instanceof Promise) await Promise.race([
2513
- shutdownResult,
2514
- new Promise((_, reject) => setTimeout(() => reject(new Error(`Cache shutdown timeout after ${timeoutMs}ms`)), timeoutMs))
2515
- ]);
2516
- } catch (err) {
2517
- this.onError?.(new Error(`Error during cache shutdown: ${err}`));
2518
- }
2519
- }
2520
- }
2521
- async function _hash(key, distinctId, salt = "") {
2522
- const hashString = await hashSHA1(`${key}.${distinctId}${salt}`);
2523
- return parseInt(hashString.slice(0, 15), 16) / LONG_SCALE;
2524
- }
2525
- function matchProperty(property, propertyValues, warnFunction) {
2526
- const key = property.key;
2527
- const value = property.value;
2528
- const operator = property.operator || "exact";
2529
- if (key in propertyValues) {
2530
- if ("is_not_set" === operator) throw new InconclusiveMatchError("Operator is_not_set is not supported");
2531
- } else throw new InconclusiveMatchError(`Property ${key} not found in propertyValues`);
2532
- const overrideValue = propertyValues[key];
2533
- if (null == overrideValue && !NULL_VALUES_ALLOWED_OPERATORS.includes(operator)) {
2534
- if (warnFunction) warnFunction(`Property ${key} cannot have a value of null/undefined with the ${operator} operator`);
2535
- return false;
2536
- }
2537
- function computeExactMatch(value2, overrideValue2) {
2538
- if (Array.isArray(value2)) return value2.map((val) => String(val).toLowerCase()).includes(String(overrideValue2).toLowerCase());
2539
- return String(value2).toLowerCase() === String(overrideValue2).toLowerCase();
2540
- }
2541
- function compare(lhs, rhs, operator2) {
2542
- if ("gt" === operator2) return lhs > rhs;
2543
- if ("gte" === operator2) return lhs >= rhs;
2544
- if ("lt" === operator2) return lhs < rhs;
2545
- if ("lte" === operator2) return lhs <= rhs;
2546
- throw new Error(`Invalid operator: ${operator2}`);
2547
- }
2548
- switch (operator) {
2549
- case "exact":
2550
- return computeExactMatch(value, overrideValue);
2551
- case "is_not":
2552
- return !computeExactMatch(value, overrideValue);
2553
- case "is_set":
2554
- return key in propertyValues;
2555
- case "icontains":
2556
- return String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
2557
- case "not_icontains":
2558
- return !String(overrideValue).toLowerCase().includes(String(value).toLowerCase());
2559
- case "regex":
2560
- return isValidRegex(String(value)) && null !== String(overrideValue).match(String(value));
2561
- case "not_regex":
2562
- return isValidRegex(String(value)) && null === String(overrideValue).match(String(value));
2563
- case "gt":
2564
- case "gte":
2565
- case "lt":
2566
- case "lte": {
2567
- let parsedValue = "number" == typeof value ? value : null;
2568
- if ("string" == typeof value) try {
2569
- parsedValue = parseFloat(value);
2570
- } catch (err) {
2571
- }
2572
- if (null == parsedValue || null == overrideValue) return compare(String(overrideValue), String(value), operator);
2573
- if ("string" == typeof overrideValue) return compare(overrideValue, String(value), operator);
2574
- return compare(overrideValue, parsedValue, operator);
2575
- }
2576
- case "is_date_after":
2577
- case "is_date_before": {
2578
- if ("boolean" == typeof value) throw new InconclusiveMatchError("Date operations cannot be performed on boolean values");
2579
- let parsedDate = relativeDateParseForFeatureFlagMatching(String(value));
2580
- if (null == parsedDate) parsedDate = convertToDateTime(value);
2581
- if (null == parsedDate) throw new InconclusiveMatchError(`Invalid date: ${value}`);
2582
- const overrideDate = convertToDateTime(overrideValue);
2583
- if ([
2584
- "is_date_before"
2585
- ].includes(operator)) return overrideDate < parsedDate;
2586
- return overrideDate > parsedDate;
2587
- }
2588
- default:
2589
- throw new InconclusiveMatchError(`Unknown operator: ${operator}`);
2590
- }
2591
- }
2592
- function checkCohortExists(cohortId, cohortProperties) {
2593
- if (!(cohortId in cohortProperties)) throw new RequiresServerEvaluation(`cohort ${cohortId} not found in local cohorts - likely a static cohort that requires server evaluation`);
2594
- }
2595
- function matchCohort(property, propertyValues, cohortProperties, debugMode = false) {
2596
- const cohortId = String(property.value);
2597
- checkCohortExists(cohortId, cohortProperties);
2598
- const propertyGroup = cohortProperties[cohortId];
2599
- return matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, debugMode);
2600
- }
2601
- function matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, debugMode = false) {
2602
- if (!propertyGroup) return true;
2603
- const propertyGroupType = propertyGroup.type;
2604
- const properties = propertyGroup.values;
2605
- if (!properties || 0 === properties.length) return true;
2606
- let errorMatchingLocally = false;
2607
- if ("values" in properties[0]) {
2608
- for (const prop of properties) try {
2609
- const matches = matchPropertyGroup(prop, propertyValues, cohortProperties, debugMode);
2610
- if ("AND" === propertyGroupType) {
2611
- if (!matches) return false;
2612
- } else if (matches) return true;
2613
- } catch (err) {
2614
- if (err instanceof RequiresServerEvaluation) throw err;
2615
- if (err instanceof InconclusiveMatchError) {
2616
- if (debugMode) console.debug(`Failed to compute property ${prop} locally: ${err}`);
2617
- errorMatchingLocally = true;
2618
- } else throw err;
2619
- }
2620
- if (errorMatchingLocally) throw new InconclusiveMatchError("Can't match cohort without a given cohort property value");
2621
- return "AND" === propertyGroupType;
2622
- }
2623
- for (const prop of properties) try {
2624
- let matches;
2625
- if ("cohort" === prop.type) matches = matchCohort(prop, propertyValues, cohortProperties, debugMode);
2626
- else if ("flag" === prop.type) {
2627
- if (debugMode) console.warn(`[FEATURE FLAGS] Flag dependency filters are not supported in local evaluation. Skipping condition with dependency on flag '${prop.key || "unknown"}'`);
2628
- continue;
2629
- } else matches = matchProperty(prop, propertyValues);
2630
- const negation = prop.negation || false;
2631
- if ("AND" === propertyGroupType) {
2632
- if (!matches && !negation) return false;
2633
- if (matches && negation) return false;
2634
- } else {
2635
- if (matches && !negation) return true;
2636
- if (!matches && negation) return true;
2637
- }
2638
- } catch (err) {
2639
- if (err instanceof RequiresServerEvaluation) throw err;
2640
- if (err instanceof InconclusiveMatchError) {
2641
- if (debugMode) console.debug(`Failed to compute property ${prop} locally: ${err}`);
2642
- errorMatchingLocally = true;
2643
- } else throw err;
2644
- }
2645
- if (errorMatchingLocally) throw new InconclusiveMatchError("can't match cohort without a given cohort property value");
2646
- return "AND" === propertyGroupType;
2647
- }
2648
- function isValidRegex(regex) {
2649
- try {
2650
- new RegExp(regex);
2651
- return true;
2652
- } catch (err) {
2653
- return false;
2654
- }
2655
- }
2656
- function convertToDateTime(value) {
2657
- if (value instanceof Date) return value;
2658
- if ("string" == typeof value || "number" == typeof value) {
2659
- const date = new Date(value);
2660
- if (!isNaN(date.valueOf())) return date;
2661
- throw new InconclusiveMatchError(`${value} is in an invalid date format`);
2662
- }
2663
- throw new InconclusiveMatchError(`The date provided ${value} must be a string, number, or date object`);
2664
- }
2665
- function relativeDateParseForFeatureFlagMatching(value) {
2666
- const regex = /^-?(?<number>[0-9]+)(?<interval>[a-z])$/;
2667
- const match = value.match(regex);
2668
- const parsedDt = new Date((/* @__PURE__ */ new Date()).toISOString());
2669
- if (!match) return null;
2670
- {
2671
- if (!match.groups) return null;
2672
- const number = parseInt(match.groups["number"]);
2673
- if (number >= 1e4) return null;
2674
- const interval = match.groups["interval"];
2675
- if ("h" == interval) parsedDt.setUTCHours(parsedDt.getUTCHours() - number);
2676
- else if ("d" == interval) parsedDt.setUTCDate(parsedDt.getUTCDate() - number);
2677
- else if ("w" == interval) parsedDt.setUTCDate(parsedDt.getUTCDate() - 7 * number);
2678
- else if ("m" == interval) parsedDt.setUTCMonth(parsedDt.getUTCMonth() - number);
2679
- else {
2680
- if ("y" != interval) return null;
2681
- parsedDt.setUTCFullYear(parsedDt.getUTCFullYear() - number);
2682
- }
2683
- return parsedDt;
2684
- }
2685
- }
2686
- class PostHogMemoryStorage {
2687
- getProperty(key) {
2688
- return this._memoryStorage[key];
2689
- }
2690
- setProperty(key, value) {
2691
- this._memoryStorage[key] = null !== value ? value : void 0;
2692
- }
2693
- constructor() {
2694
- this._memoryStorage = {};
2695
- }
2696
- }
2697
- const MINIMUM_POLLING_INTERVAL = 100;
2698
- const THIRTY_SECONDS = 3e4;
2699
- const MAX_CACHE_SIZE = 5e4;
2700
- class PostHogBackendClient extends PostHogCoreStateless {
2701
- constructor(apiKey, options = {}) {
2702
- super(apiKey, options), this._memoryStorage = new PostHogMemoryStorage();
2703
- this.options = options;
2704
- this.context = this.initializeContext();
2705
- this.options.featureFlagsPollingInterval = "number" == typeof options.featureFlagsPollingInterval ? Math.max(options.featureFlagsPollingInterval, MINIMUM_POLLING_INTERVAL) : THIRTY_SECONDS;
2706
- if (options.personalApiKey) {
2707
- if (options.personalApiKey.includes("phc_")) throw new Error('Your Personal API key is invalid. These keys are prefixed with "phx_" and can be created in PostHog project settings.');
2708
- const shouldEnableLocalEvaluation = false !== options.enableLocalEvaluation;
2709
- if (shouldEnableLocalEvaluation) this.featureFlagsPoller = new FeatureFlagsPoller({
2710
- pollingInterval: this.options.featureFlagsPollingInterval,
2711
- personalApiKey: options.personalApiKey,
2712
- projectApiKey: apiKey,
2713
- timeout: options.requestTimeout ?? 1e4,
2714
- host: this.host,
2715
- fetch: options.fetch,
2716
- onError: (err) => {
2717
- this._events.emit("error", err);
2718
- },
2719
- onLoad: (count) => {
2720
- this._events.emit("localEvaluationFlagsLoaded", count);
2721
- },
2722
- customHeaders: this.getCustomHeaders(),
2723
- cacheProvider: options.flagDefinitionCacheProvider
2724
- });
2725
- }
2726
- this.errorTracking = new ErrorTracking(this, options, this._logger);
2727
- this.distinctIdHasSentFlagCalls = {};
2728
- this.maxCacheSize = options.maxCacheSize || MAX_CACHE_SIZE;
2729
- }
2730
- getPersistedProperty(key) {
2731
- return this._memoryStorage.getProperty(key);
2732
- }
2733
- setPersistedProperty(key, value) {
2734
- return this._memoryStorage.setProperty(key, value);
2735
- }
2736
- fetch(url, options) {
2737
- return this.options.fetch ? this.options.fetch(url, options) : fetch(url, options);
2738
- }
2739
- getLibraryVersion() {
2740
- return version;
2741
- }
2742
- getCustomUserAgent() {
2743
- return `${this.getLibraryId()}/${this.getLibraryVersion()}`;
2744
- }
2745
- enable() {
2746
- return super.optIn();
2747
- }
2748
- disable() {
2749
- return super.optOut();
2750
- }
2751
- debug(enabled = true) {
2752
- super.debug(enabled);
2753
- this.featureFlagsPoller?.debug(enabled);
2754
- }
2755
- capture(props) {
2756
- if ("string" == typeof props) this._logger.warn("Called capture() with a string as the first argument when an object was expected.");
2757
- this.addPendingPromise(this.prepareEventMessage(props).then(({ distinctId, event, properties, options }) => super.captureStateless(distinctId, event, properties, {
2758
- timestamp: options.timestamp,
2759
- disableGeoip: options.disableGeoip,
2760
- uuid: options.uuid
2761
- })).catch((err) => {
2762
- if (err) console.error(err);
2763
- }));
2764
- }
2765
- async captureImmediate(props) {
2766
- if ("string" == typeof props) this._logger.warn("Called captureImmediate() with a string as the first argument when an object was expected.");
2767
- return this.addPendingPromise(this.prepareEventMessage(props).then(({ distinctId, event, properties, options }) => super.captureStatelessImmediate(distinctId, event, properties, {
2768
- timestamp: options.timestamp,
2769
- disableGeoip: options.disableGeoip,
2770
- uuid: options.uuid
2771
- })).catch((err) => {
2772
- if (err) console.error(err);
2773
- }));
2774
- }
2775
- identify({ distinctId, properties = {}, disableGeoip }) {
2776
- const { $set, $set_once, $anon_distinct_id, ...rest } = properties;
2777
- const setProps = $set || rest;
2778
- const setOnceProps = $set_once || {};
2779
- const eventProperties = {
2780
- $set: setProps,
2781
- $set_once: setOnceProps,
2782
- $anon_distinct_id: $anon_distinct_id ?? void 0
2783
- };
2784
- super.identifyStateless(distinctId, eventProperties, {
2785
- disableGeoip
2786
- });
2787
- }
2788
- async identifyImmediate({ distinctId, properties = {}, disableGeoip }) {
2789
- const { $set, $set_once, $anon_distinct_id, ...rest } = properties;
2790
- const setProps = $set || rest;
2791
- const setOnceProps = $set_once || {};
2792
- const eventProperties = {
2793
- $set: setProps,
2794
- $set_once: setOnceProps,
2795
- $anon_distinct_id: $anon_distinct_id ?? void 0
2796
- };
2797
- super.identifyStatelessImmediate(distinctId, eventProperties, {
2798
- disableGeoip
2799
- });
2800
- }
2801
- alias(data) {
2802
- super.aliasStateless(data.alias, data.distinctId, void 0, {
2803
- disableGeoip: data.disableGeoip
2804
- });
2805
- }
2806
- async aliasImmediate(data) {
2807
- await super.aliasStatelessImmediate(data.alias, data.distinctId, void 0, {
2808
- disableGeoip: data.disableGeoip
2809
- });
2810
- }
2811
- isLocalEvaluationReady() {
2812
- return this.featureFlagsPoller?.isLocalEvaluationReady() ?? false;
2813
- }
2814
- async waitForLocalEvaluationReady(timeoutMs = THIRTY_SECONDS) {
2815
- if (this.isLocalEvaluationReady()) return true;
2816
- if (void 0 === this.featureFlagsPoller) return false;
2817
- return new Promise((resolve) => {
2818
- const timeout = setTimeout(() => {
2819
- cleanup();
2820
- resolve(false);
2821
- }, timeoutMs);
2822
- const cleanup = this._events.on("localEvaluationFlagsLoaded", (count) => {
2823
- clearTimeout(timeout);
2824
- cleanup();
2825
- resolve(count > 0);
2826
- });
2827
- });
2828
- }
2829
- async getFeatureFlag(key, distinctId, options) {
2830
- if (void 0 !== this._flagOverrides && key in this._flagOverrides) return this._flagOverrides[key];
2831
- const { groups, disableGeoip } = options || {};
2832
- let { onlyEvaluateLocally, sendFeatureFlagEvents, personProperties, groupProperties } = options || {};
2833
- const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2834
- personProperties = adjustedProperties.allPersonProperties;
2835
- groupProperties = adjustedProperties.allGroupProperties;
2836
- if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = false;
2837
- if (void 0 == sendFeatureFlagEvents) sendFeatureFlagEvents = this.options.sendFeatureFlagEvent ?? true;
2838
- let response = await this.featureFlagsPoller?.getFeatureFlag(key, distinctId, groups, personProperties, groupProperties);
2839
- const flagWasLocallyEvaluated = void 0 !== response;
2840
- let requestId;
2841
- let evaluatedAt;
2842
- let flagDetail;
2843
- let featureFlagError;
2844
- if (!flagWasLocallyEvaluated && !onlyEvaluateLocally) {
2845
- const flagsResponse = await super.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [
2846
- key
2847
- ]);
2848
- if (void 0 === flagsResponse) featureFlagError = FeatureFlagError.UNKNOWN_ERROR;
2849
- else {
2850
- requestId = flagsResponse.requestId;
2851
- evaluatedAt = flagsResponse.evaluatedAt;
2852
- const errors = [];
2853
- if (flagsResponse.errorsWhileComputingFlags) errors.push(FeatureFlagError.ERRORS_WHILE_COMPUTING);
2854
- if (flagsResponse.quotaLimited?.includes("feature_flags")) errors.push(FeatureFlagError.QUOTA_LIMITED);
2855
- flagDetail = flagsResponse.flags[key];
2856
- if (void 0 === flagDetail) errors.push(FeatureFlagError.FLAG_MISSING);
2857
- if (errors.length > 0) featureFlagError = errors.join(",");
2858
- response = getFeatureFlagValue(flagDetail);
2859
- }
2860
- }
2861
- const featureFlagReportedKey = `${key}_${response}`;
2862
- if (sendFeatureFlagEvents && (!(distinctId in this.distinctIdHasSentFlagCalls) || !this.distinctIdHasSentFlagCalls[distinctId].includes(featureFlagReportedKey))) {
2863
- if (Object.keys(this.distinctIdHasSentFlagCalls).length >= this.maxCacheSize) this.distinctIdHasSentFlagCalls = {};
2864
- if (Array.isArray(this.distinctIdHasSentFlagCalls[distinctId])) this.distinctIdHasSentFlagCalls[distinctId].push(featureFlagReportedKey);
2865
- else this.distinctIdHasSentFlagCalls[distinctId] = [
2866
- featureFlagReportedKey
2867
- ];
2868
- const properties = {
2869
- $feature_flag: key,
2870
- $feature_flag_response: response,
2871
- $feature_flag_id: flagDetail?.metadata?.id,
2872
- $feature_flag_version: flagDetail?.metadata?.version,
2873
- $feature_flag_reason: flagDetail?.reason?.description ?? flagDetail?.reason?.code,
2874
- locally_evaluated: flagWasLocallyEvaluated,
2875
- [`$feature/${key}`]: response,
2876
- $feature_flag_request_id: requestId,
2877
- $feature_flag_evaluated_at: evaluatedAt
2878
- };
2879
- if (featureFlagError) properties.$feature_flag_error = featureFlagError;
2880
- this.capture({
2881
- distinctId,
2882
- event: "$feature_flag_called",
2883
- properties,
2884
- groups,
2885
- disableGeoip
2886
- });
2887
- }
2888
- return response;
2889
- }
2890
- async getFeatureFlagPayload(key, distinctId, matchValue, options) {
2891
- if (void 0 !== this._payloadOverrides && key in this._payloadOverrides) return this._payloadOverrides[key];
2892
- const { groups, disableGeoip } = options || {};
2893
- let { onlyEvaluateLocally, personProperties, groupProperties } = options || {};
2894
- const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2895
- personProperties = adjustedProperties.allPersonProperties;
2896
- groupProperties = adjustedProperties.allGroupProperties;
2897
- let response;
2898
- const localEvaluationEnabled = void 0 !== this.featureFlagsPoller;
2899
- if (localEvaluationEnabled) {
2900
- await this.featureFlagsPoller?.loadFeatureFlags();
2901
- const flag = this.featureFlagsPoller?.featureFlagsByKey[key];
2902
- if (flag) try {
2903
- const result = await this.featureFlagsPoller?.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties, matchValue);
2904
- if (result) {
2905
- matchValue = result.value;
2906
- response = result.payload;
2907
- }
2908
- } catch (e) {
2909
- if (e instanceof RequiresServerEvaluation || e instanceof InconclusiveMatchError) this._logger?.info(`${e.name} when computing flag locally: ${flag.key}: ${e.message}`);
2910
- else throw e;
2911
- }
2912
- }
2913
- if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = false;
2914
- const payloadWasLocallyEvaluated = void 0 !== response;
2915
- if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
2916
- return response;
2917
- }
2918
- async getRemoteConfigPayload(flagKey) {
2919
- if (!this.options.personalApiKey) throw new Error("Personal API key is required for remote config payload decryption");
2920
- const response = await this._requestRemoteConfigPayload(flagKey);
2921
- if (!response) return;
2922
- const parsed = await response.json();
2923
- if ("string" == typeof parsed) try {
2924
- return JSON.parse(parsed);
2925
- } catch (e) {
2926
- }
2927
- return parsed;
2928
- }
2929
- async isFeatureEnabled(key, distinctId, options) {
2930
- const feat = await this.getFeatureFlag(key, distinctId, options);
2931
- if (void 0 === feat) return;
2932
- return !!feat || false;
2933
- }
2934
- async getAllFlags(distinctId, options) {
2935
- const response = await this.getAllFlagsAndPayloads(distinctId, options);
2936
- return response.featureFlags || {};
2937
- }
2938
- async getAllFlagsAndPayloads(distinctId, options) {
2939
- const { groups, disableGeoip, flagKeys } = options || {};
2940
- let { onlyEvaluateLocally, personProperties, groupProperties } = options || {};
2941
- const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2942
- personProperties = adjustedProperties.allPersonProperties;
2943
- groupProperties = adjustedProperties.allGroupProperties;
2944
- if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = false;
2945
- const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties, flagKeys);
2946
- let featureFlags = {};
2947
- let featureFlagPayloads = {};
2948
- let fallbackToFlags = true;
2949
- if (localEvaluationResult) {
2950
- featureFlags = localEvaluationResult.response;
2951
- featureFlagPayloads = localEvaluationResult.payloads;
2952
- fallbackToFlags = localEvaluationResult.fallbackToFlags;
2953
- }
2954
- if (fallbackToFlags && !onlyEvaluateLocally) {
2955
- const remoteEvaluationResult = await super.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeys);
2956
- featureFlags = {
2957
- ...featureFlags,
2958
- ...remoteEvaluationResult.flags || {}
2959
- };
2960
- featureFlagPayloads = {
2961
- ...featureFlagPayloads,
2962
- ...remoteEvaluationResult.payloads || {}
2963
- };
2964
- }
2965
- if (void 0 !== this._flagOverrides) featureFlags = {
2966
- ...featureFlags,
2967
- ...this._flagOverrides
2968
- };
2969
- if (void 0 !== this._payloadOverrides) featureFlagPayloads = {
2970
- ...featureFlagPayloads,
2971
- ...this._payloadOverrides
2972
- };
2973
- return {
2974
- featureFlags,
2975
- featureFlagPayloads
2976
- };
2977
- }
2978
- groupIdentify({ groupType, groupKey, properties, distinctId, disableGeoip }) {
2979
- super.groupIdentifyStateless(groupType, groupKey, properties, {
2980
- disableGeoip
2981
- }, distinctId);
2982
- }
2983
- async reloadFeatureFlags() {
2984
- await this.featureFlagsPoller?.loadFeatureFlags(true);
2985
- }
2986
- overrideFeatureFlags(overrides) {
2987
- const flagArrayToRecord = (flags) => Object.fromEntries(flags.map((f) => [
2988
- f,
2989
- true
2990
- ]));
2991
- if (false === overrides) {
2992
- this._flagOverrides = void 0;
2993
- this._payloadOverrides = void 0;
2994
- return;
2995
- }
2996
- if (Array.isArray(overrides)) {
2997
- this._flagOverrides = flagArrayToRecord(overrides);
2998
- return;
2999
- }
3000
- if (this._isFeatureFlagOverrideOptions(overrides)) {
3001
- if ("flags" in overrides) {
3002
- if (false === overrides.flags) this._flagOverrides = void 0;
3003
- else if (Array.isArray(overrides.flags)) this._flagOverrides = flagArrayToRecord(overrides.flags);
3004
- else if (void 0 !== overrides.flags) this._flagOverrides = {
3005
- ...overrides.flags
3006
- };
3007
- }
3008
- if ("payloads" in overrides) {
3009
- if (false === overrides.payloads) this._payloadOverrides = void 0;
3010
- else if (void 0 !== overrides.payloads) this._payloadOverrides = {
3011
- ...overrides.payloads
3012
- };
3013
- }
3014
- return;
3015
- }
3016
- this._flagOverrides = {
3017
- ...overrides
3018
- };
3019
- }
3020
- _isFeatureFlagOverrideOptions(overrides) {
3021
- if ("object" != typeof overrides || null === overrides || Array.isArray(overrides)) return false;
3022
- const obj = overrides;
3023
- if ("flags" in obj) {
3024
- const flagsValue = obj["flags"];
3025
- if (false === flagsValue || Array.isArray(flagsValue) || "object" == typeof flagsValue && null !== flagsValue) return true;
3026
- }
3027
- if ("payloads" in obj) {
3028
- const payloadsValue = obj["payloads"];
3029
- if (false === payloadsValue || "object" == typeof payloadsValue && null !== payloadsValue) return true;
3030
- }
3031
- return false;
3032
- }
3033
- withContext(data, fn, options) {
3034
- if (!this.context) return fn();
3035
- return this.context.run(data, fn, options);
3036
- }
3037
- getContext() {
3038
- return this.context?.get();
3039
- }
3040
- async _shutdown(shutdownTimeoutMs) {
3041
- this.featureFlagsPoller?.stopPoller(shutdownTimeoutMs);
3042
- this.errorTracking.shutdown();
3043
- return super._shutdown(shutdownTimeoutMs);
3044
- }
3045
- async _requestRemoteConfigPayload(flagKey) {
3046
- if (!this.options.personalApiKey) return;
3047
- const url = `${this.host}/api/projects/@current/feature_flags/${flagKey}/remote_config?token=${encodeURIComponent(this.apiKey)}`;
3048
- const options = {
3049
- method: "GET",
3050
- headers: {
3051
- ...this.getCustomHeaders(),
3052
- "Content-Type": "application/json",
3053
- Authorization: `Bearer ${this.options.personalApiKey}`
3054
- }
3055
- };
3056
- let abortTimeout = null;
3057
- if (this.options.requestTimeout && "number" == typeof this.options.requestTimeout) {
3058
- const controller = new AbortController();
3059
- abortTimeout = safeSetTimeout(() => {
3060
- controller.abort();
3061
- }, this.options.requestTimeout);
3062
- options.signal = controller.signal;
3063
- }
3064
- try {
3065
- return await this.fetch(url, options);
3066
- } catch (error) {
3067
- this._events.emit("error", error);
3068
- return;
3069
- } finally {
3070
- if (abortTimeout) clearTimeout(abortTimeout);
3071
- }
3072
- }
3073
- extractPropertiesFromEvent(eventProperties, groups) {
3074
- if (!eventProperties) return {
3075
- personProperties: {},
3076
- groupProperties: {}
3077
- };
3078
- const personProperties = {};
3079
- const groupProperties = {};
3080
- for (const [key, value] of Object.entries(eventProperties)) if (isPlainObject(value) && groups && key in groups) {
3081
- const groupProps = {};
3082
- for (const [groupKey, groupValue] of Object.entries(value)) groupProps[String(groupKey)] = String(groupValue);
3083
- groupProperties[String(key)] = groupProps;
3084
- } else personProperties[String(key)] = String(value);
3085
- return {
3086
- personProperties,
3087
- groupProperties
3088
- };
3089
- }
3090
- async getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions) {
3091
- const finalPersonProperties = sendFeatureFlagsOptions?.personProperties || {};
3092
- const finalGroupProperties = sendFeatureFlagsOptions?.groupProperties || {};
3093
- const flagKeys = sendFeatureFlagsOptions?.flagKeys;
3094
- const onlyEvaluateLocally = sendFeatureFlagsOptions?.onlyEvaluateLocally ?? false;
3095
- if (onlyEvaluateLocally) if (!((this.featureFlagsPoller?.featureFlags?.length || 0) > 0)) return {};
3096
- else {
3097
- const groupsWithStringValues = {};
3098
- for (const [key, value] of Object.entries(groups || {})) groupsWithStringValues[key] = String(value);
3099
- return await this.getAllFlags(distinctId, {
3100
- groups: groupsWithStringValues,
3101
- personProperties: finalPersonProperties,
3102
- groupProperties: finalGroupProperties,
3103
- disableGeoip,
3104
- onlyEvaluateLocally: true,
3105
- flagKeys
3106
- });
3107
- }
3108
- if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
3109
- const groupsWithStringValues = {};
3110
- for (const [key, value] of Object.entries(groups || {})) groupsWithStringValues[key] = String(value);
3111
- return await this.getAllFlags(distinctId, {
3112
- groups: groupsWithStringValues,
3113
- personProperties: finalPersonProperties,
3114
- groupProperties: finalGroupProperties,
3115
- disableGeoip,
3116
- onlyEvaluateLocally: true,
3117
- flagKeys
3118
- });
3119
- }
3120
- return (await super.getFeatureFlagsStateless(distinctId, groups, finalPersonProperties, finalGroupProperties, disableGeoip)).flags;
3121
- }
3122
- addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties) {
3123
- const allPersonProperties = {
3124
- distinct_id: distinctId,
3125
- ...personProperties || {}
3126
- };
3127
- const allGroupProperties = {};
3128
- if (groups) for (const groupName of Object.keys(groups)) allGroupProperties[groupName] = {
3129
- $group_key: groups[groupName],
3130
- ...groupProperties?.[groupName] || {}
3131
- };
3132
- return {
3133
- allPersonProperties,
3134
- allGroupProperties
3135
- };
3136
- }
3137
- captureException(error, distinctId, additionalProperties, uuid) {
3138
- if (!ErrorTracking.isPreviouslyCapturedError(error)) {
3139
- const syntheticException = new Error("PostHog syntheticException");
3140
- this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
3141
- syntheticException
3142
- }, distinctId, additionalProperties).then((msg) => this.capture({
3143
- ...msg,
3144
- uuid
3145
- })));
3146
- }
3147
- }
3148
- async captureExceptionImmediate(error, distinctId, additionalProperties) {
3149
- if (!ErrorTracking.isPreviouslyCapturedError(error)) {
3150
- const syntheticException = new Error("PostHog syntheticException");
3151
- this.addPendingPromise(ErrorTracking.buildEventMessage(error, {
3152
- syntheticException
3153
- }, distinctId, additionalProperties).then((msg) => this.captureImmediate(msg)));
3154
- }
3155
- }
3156
- async prepareEventMessage(props) {
3157
- const { distinctId, event, properties, groups, sendFeatureFlags, timestamp, disableGeoip, uuid } = props;
3158
- const contextData = this.context?.get();
3159
- let mergedDistinctId = distinctId || contextData?.distinctId;
3160
- const mergedProperties = {
3161
- ...contextData?.properties || {},
3162
- ...properties || {}
3163
- };
3164
- if (!mergedDistinctId) {
3165
- mergedDistinctId = uuidv7();
3166
- mergedProperties.$process_person_profile = false;
3167
- }
3168
- if (contextData?.sessionId && !mergedProperties.$session_id) mergedProperties.$session_id = contextData.sessionId;
3169
- const eventMessage = this._runBeforeSend({
3170
- distinctId: mergedDistinctId,
3171
- event,
3172
- properties: mergedProperties,
3173
- groups,
3174
- sendFeatureFlags,
3175
- timestamp,
3176
- disableGeoip,
3177
- uuid
3178
- });
3179
- if (!eventMessage) return Promise.reject(null);
3180
- const eventProperties = await Promise.resolve().then(async () => {
3181
- if (sendFeatureFlags) {
3182
- const sendFeatureFlagsOptions = "object" == typeof sendFeatureFlags ? sendFeatureFlags : void 0;
3183
- return await this.getFeatureFlagsForEvent(eventMessage.distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
3184
- }
3185
- eventMessage.event;
3186
- return {};
3187
- }).then((flags) => {
3188
- const additionalProperties = {};
3189
- if (flags) for (const [feature, variant] of Object.entries(flags)) additionalProperties[`$feature/${feature}`] = variant;
3190
- const activeFlags = Object.keys(flags || {}).filter((flag) => flags?.[flag] !== false).sort();
3191
- if (activeFlags.length > 0) additionalProperties["$active_feature_flags"] = activeFlags;
3192
- return additionalProperties;
3193
- }).catch(() => ({})).then((additionalProperties) => {
3194
- const props2 = {
3195
- ...additionalProperties,
3196
- ...eventMessage.properties || {},
3197
- $groups: eventMessage.groups || groups
3198
- };
3199
- return props2;
3200
- });
3201
- if ("$pageview" === eventMessage.event && this.options.__preview_capture_bot_pageviews && "string" == typeof eventProperties.$raw_user_agent) {
3202
- if (isBlockedUA(eventProperties.$raw_user_agent, this.options.custom_blocked_useragents || [])) {
3203
- eventMessage.event = "$bot_pageview";
3204
- eventProperties.$browser_type = "bot";
3205
- }
3206
- }
3207
- return {
3208
- distinctId: eventMessage.distinctId,
3209
- event: eventMessage.event,
3210
- properties: eventProperties,
3211
- options: {
3212
- timestamp: eventMessage.timestamp,
3213
- disableGeoip: eventMessage.disableGeoip,
3214
- uuid: eventMessage.uuid
3215
- }
3216
- };
3217
- }
3218
- _runBeforeSend(eventMessage) {
3219
- const beforeSend = this.options.before_send;
3220
- if (!beforeSend) return eventMessage;
3221
- const fns = Array.isArray(beforeSend) ? beforeSend : [
3222
- beforeSend
3223
- ];
3224
- let result = eventMessage;
3225
- for (const fn of fns) {
3226
- result = fn(result);
3227
- if (!result) {
3228
- this._logger.info(`Event '${eventMessage.event}' was rejected in beforeSend function`);
3229
- return null;
3230
- }
3231
- if (!result.properties || 0 === Object.keys(result.properties).length) {
3232
- const message = `Event '${result.event}' has no properties after beforeSend function, this is likely an error.`;
3233
- this._logger.warn(message);
3234
- }
3235
- }
3236
- return result;
3237
- }
3238
- }
3239
- class PostHogContext {
3240
- constructor() {
3241
- this.storage = new AsyncLocalStorage();
3242
- }
3243
- get() {
3244
- return this.storage.getStore();
3245
- }
3246
- run(context, fn, options) {
3247
- const fresh = options?.fresh === true;
3248
- if (fresh) return this.storage.run(context, fn);
3249
- {
3250
- const currentContext = this.get() || {};
3251
- const mergedContext = {
3252
- distinctId: context.distinctId ?? currentContext.distinctId,
3253
- sessionId: context.sessionId ?? currentContext.sessionId,
3254
- properties: {
3255
- ...currentContext.properties || {},
3256
- ...context.properties || {}
3257
- }
3258
- };
3259
- return this.storage.run(mergedContext, fn);
3260
- }
3261
- }
3262
- }
3263
- function setupExpressErrorHandler(_posthog, app) {
3264
- app.use(posthogErrorHandler(_posthog));
3265
- }
3266
- function posthogErrorHandler(posthog) {
3267
- return (error, req, res, next) => {
3268
- if (ErrorTracking.isPreviouslyCapturedError(error)) return void next(error);
3269
- const sessionId = req.headers["x-posthog-session-id"];
3270
- const distinctId = req.headers["x-posthog-distinct-id"];
3271
- const syntheticException = new Error("Synthetic exception");
3272
- const hint = {
3273
- mechanism: {
3274
- type: "middleware",
3275
- handled: false
3276
- },
3277
- syntheticException
3278
- };
3279
- posthog.addPendingPromise(ErrorTracking.buildEventMessage(error, hint, distinctId, {
3280
- $session_id: sessionId,
3281
- $current_url: req.url,
3282
- $request_method: req.method,
3283
- $request_path: req.path,
3284
- $user_agent: req.headers["user-agent"],
3285
- $response_status_code: res.statusCode,
3286
- $ip: req.headers["x-forwarded-for"] || req?.socket?.remoteAddress
3287
- }).then((msg) => {
3288
- posthog.capture(msg);
3289
- }));
3290
- next(error);
3291
- };
3292
- }
3293
- ErrorTracking.errorPropertiesBuilder = new ErrorPropertiesBuilder([
3294
- new EventCoercer(),
3295
- new ErrorCoercer(),
3296
- new ObjectCoercer(),
3297
- new StringCoercer(),
3298
- new PrimitiveCoercer()
3299
- ], createStackParser("node:javascript", nodeStackLineParser), [
3300
- createModulerModifier(),
3301
- addSourceContext
3302
- ]);
3303
- class PostHog extends PostHogBackendClient {
3304
- getLibraryId() {
3305
- return "posthog-node";
3306
- }
3307
- initializeContext() {
3308
- return new PostHogContext();
3309
- }
3310
- }
3311
- let client;
3312
- let shutdownHookBound = false;
3313
- const resolveApiKey = (env2) => env2.POSTHOG_API_KEY || env2.POSTHOG_KEY || env2.POSTHOG_SERVER_KEY;
3314
- const resolveHost = (env2) => env2.POSTHOG_HOST || env2.RB_POSTHOG_HOST || "https://eu.i.posthog.com";
3315
- const getPosthogClient = (env2) => {
3316
- if (client !== void 0) return client;
3317
- const apiKey = resolveApiKey(env2);
3318
- if (!apiKey) {
3319
- client = null;
3320
- return client;
3321
- }
3322
- client = new PostHog(apiKey, {
3323
- host: resolveHost(env2),
3324
- enableExceptionAutocapture: true,
3325
- flushAt: 1,
3326
- flushInterval: 1e3
3327
- });
3328
- return client;
3329
- };
3330
- const bindExpressErrorHandler = (app, posthog) => {
3331
- if (!posthog) return;
3332
- setupExpressErrorHandler(posthog, app);
3333
- };
3334
- const captureServerException = (error, posthog, properties) => {
3335
- const clientToUse = posthog ?? client ?? null;
3336
- if (!clientToUse || typeof clientToUse.captureException !== "function") return;
3337
- clientToUse.captureException(error, "server", properties);
3338
- };
3339
- const shutdown = async () => {
3340
- if (!client) return;
3341
- try {
3342
- await client.shutdown();
3343
- } catch (err) {
3344
- console.error("posthog shutdown error", err);
3345
- }
3346
- };
3347
- const ensurePosthogShutdown = () => {
3348
- if (shutdownHookBound || !client) return;
3349
- shutdownHookBound = true;
3350
- const stop = () => {
3351
- void shutdown();
3352
- };
3353
- process.on("SIGTERM", stop);
3354
- process.on("SIGINT", stop);
3355
- process.on("beforeExit", shutdown);
3356
- };
3357
- const getDerivedKey = (masterKey, info, length = 32, salt = "") => {
3358
- assert$1(masterKey?.length >= 32, "MASTER_KEY must be 32 chars or longer.");
3359
- return Buffer.from(hkdfSync(
3360
- "sha256",
3361
- masterKey,
3362
- Buffer.from(salt),
3363
- Buffer.from(info),
3364
- length
3365
- )).toString("hex");
3366
- };
3367
- const POSTHOG_INGEST_TARGET = "https://eu.i.posthog.com";
3368
- const getStatusCode = (errorCode) => {
3369
- if (typeof errorCode === "string" && /HPE_INVALID/.test(errorCode)) {
3370
- return 502;
3371
- }
3372
- switch (errorCode) {
3373
- case "ECONNRESET":
3374
- case "ENOTFOUND":
3375
- case "ECONNREFUSED":
3376
- case "ETIMEDOUT":
3377
- return 504;
3378
- default:
3379
- return 500;
3380
- }
3381
- };
3382
- const proxy = httpProxy.createProxyServer({
3383
- changeOrigin: true
3384
- });
3385
- proxy.on("error", (err, req, res) => {
3386
- console.error(`Proxy error: ${err?.message ?? String(err)}`);
3387
- if (!res || typeof res.writeHead !== "function") return;
3388
- if (res.headersSent) return;
3389
- const statusCode = getStatusCode(err?.code);
3390
- res.writeHead(statusCode);
3391
- const host = req?.headers?.host ?? "";
3392
- const url = req?.url ?? "";
3393
- res.end(`Error occurred while trying to proxy: ${host}${url}`);
3394
- });
3395
- const metricsIngestProxyMiddleware = (app) => {
3396
- app.use("/ingest", (req, res) => {
3397
- if (!req.url) {
3398
- req.url = "/";
3399
- }
3400
- proxy.web(req, res, { target: POSTHOG_INGEST_TARGET });
3401
- });
3402
- };
3403
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3404
- let hasWarnedNoReplicationEnabled = false;
3405
- const checkInitReplicaSet = async (serverEnv) => {
3406
- const port = serverEnv.DB_PORT?.trim();
3407
- if (!port) return;
3408
- const host = (serverEnv.DB_HOST ?? "localhost").trim();
3409
- const replSetName = "rs0";
3410
- const memberHost = `${host}:${port}`;
3411
- const maxAttempts = 10;
3412
- const serverSelectionTimeoutMs = 2e3;
3413
- const uri = `mongodb://${host}:${port}/?directConnection=true`;
3414
- const waitForReplicaSetReady = async (admin) => {
3415
- const maxWaitAttempts = 10;
3416
- for (let attempt = 1; attempt <= maxWaitAttempts; attempt++) {
3417
- try {
3418
- const status = await admin.command({ replSetGetStatus: 1 });
3419
- const state = Number(status?.myState ?? 0);
3420
- if (status?.ok && (state === 1 || state === 2)) return;
3421
- } catch {
3422
- }
3423
- await sleep(250 * attempt);
3424
- }
3425
- };
3426
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
3427
- let client2 = null;
3428
- try {
3429
- client2 = new MongoClient(uri, {
3430
- family: 4,
3431
- serverSelectionTimeoutMS: serverSelectionTimeoutMs,
3432
- connectTimeoutMS: serverSelectionTimeoutMs
3433
- });
3434
- await client2.connect();
3435
- const admin = client2.db("admin").admin();
3436
- await admin.command({ ping: 1 });
3437
- try {
3438
- await admin.command({ replSetGetStatus: 1 });
3439
- return;
3440
- } catch (error) {
3441
- const codeName = error?.codeName;
3442
- if (codeName === "NotYetInitialized") {
3443
- try {
3444
- const res = await admin.command({
3445
- replSetInitiate: {
3446
- _id: replSetName,
3447
- members: [{ _id: 0, host: memberHost }]
3448
- }
3449
- });
3450
- if (res?.ok) {
3451
- console.warn(`[rb/server] MongoDB replica set '${replSetName}' initiated (${memberHost}).`);
3452
- } else {
3453
- console.warn(`[rb/server] MongoDB replica set initiation returned ok=${String(res?.ok)}.`);
3454
- }
3455
- } catch (initError) {
3456
- const initCodeName = initError?.codeName;
3457
- if (initCodeName !== "AlreadyInitialized") {
3458
- const message2 = initError instanceof Error ? initError.message : String(initError);
3459
- console.warn(`[rb/server] MongoDB replica set initiation failed: ${message2}`);
3460
- if (initCodeName === "InvalidReplicaSetConfig") {
3461
- console.warn(
3462
- `[rb/server] Hint: the replica set member host must match the mongod address/port. If MongoDB runs in Docker with port mapping, ensure the container listens on ${port} (e.g. mongod --port ${port}) instead of the default 27017.`
3463
- );
3464
- }
3465
- }
3466
- }
3467
- await waitForReplicaSetReady(admin);
3468
- return;
3469
- }
3470
- if (codeName === "NoReplicationEnabled") {
3471
- if (!hasWarnedNoReplicationEnabled) {
3472
- hasWarnedNoReplicationEnabled = true;
3473
- console.warn(
3474
- `[rb/server] MongoDB is not started with --replSet ${replSetName} (replication disabled). Change streams require a replica set; start mongod with --replSet.`
3475
- );
3476
- }
3477
- return;
3478
- }
3479
- const message = error instanceof Error ? error.message : String(error);
3480
- console.warn(`[rb/server] MongoDB replica set check failed: ${message}`);
3481
- return;
3482
- }
3483
- } catch (error) {
3484
- if (attempt === maxAttempts) {
3485
- const message = error instanceof Error ? error.message : String(error);
3486
- console.warn(`[rb/server] MongoDB replica set auto-init skipped (unable to connect to ${host}:${port}): ${message}`);
3487
- return;
3488
- }
3489
- await sleep(250 * attempt);
3490
- } finally {
3491
- try {
3492
- await client2?.close();
3493
- } catch {
3494
- }
3495
- }
3496
- }
3497
- };
3498
- const VITE_FS_PREFIX = "/@fs/";
3499
- let activeRun = null;
3500
- const post = async (session2, method, params) => {
3501
- return await new Promise((resolve, reject) => {
3502
- session2.post(method, params, (error, result) => {
3503
- if (error) {
3504
- reject(error);
3505
- return;
3506
- }
3507
- resolve(result);
3508
- });
3509
- });
3510
- };
3511
- const describeError = (error) => {
3512
- return error instanceof Error ? error.message : String(error);
3513
- };
3514
- const findWorkspaceRoot = (projectRoot) => {
3515
- let dir = path.resolve(projectRoot);
3516
- while (true) {
3517
- const pkgPath = path.join(dir, "package.json");
3518
- try {
3519
- if (fs.existsSync(pkgPath)) {
3520
- const parsed = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
3521
- if (parsed && typeof parsed === "object" && parsed.workspaces) {
3522
- return dir;
3523
- }
3524
- }
3525
- } catch {
3526
- }
3527
- const parent = path.dirname(dir);
3528
- if (parent === dir) {
3529
- return path.resolve(projectRoot);
3530
- }
3531
- dir = parent;
3532
- }
3533
- };
3534
- const stripQuery = (url) => {
3535
- const queryIndex = url.indexOf("?");
3536
- const hashIndex = url.indexOf("#");
3537
- const endIndex = Math.min(
3538
- queryIndex === -1 ? Number.POSITIVE_INFINITY : queryIndex,
3539
- hashIndex === -1 ? Number.POSITIVE_INFINITY : hashIndex
3540
- );
3541
- if (!Number.isFinite(endIndex)) {
3542
- return url;
3543
- }
3544
- return url.slice(0, endIndex);
3545
- };
3546
- const resolveAbsoluteFromUrl = (rawUrl, roots) => {
3547
- const cleaned = stripQuery(rawUrl);
3548
- try {
3549
- if (cleaned.startsWith("file://")) {
3550
- return path.normalize(fileURLToPath(cleaned));
3551
- }
3552
- } catch {
3553
- }
3554
- let pathname = cleaned;
3555
- try {
3556
- const parsed = new URL(cleaned);
3557
- pathname = parsed.pathname;
3558
- } catch {
3559
- }
3560
- const decoded = decodeURIComponent(pathname);
3561
- if (decoded.startsWith(VITE_FS_PREFIX)) {
3562
- return path.normalize(decoded.slice(VITE_FS_PREFIX.length));
3563
- }
3564
- if (!path.isAbsolute(decoded)) {
3565
- return null;
3566
- }
3567
- if (fs.existsSync(decoded)) {
3568
- return path.normalize(decoded);
3569
- }
3570
- const projectRoot = path.resolve(roots.projectRoot);
3571
- const workspaceRoot = path.resolve(roots.workspaceRoot);
3572
- if (decoded.startsWith(projectRoot + path.sep) || decoded.startsWith(workspaceRoot + path.sep)) {
3573
- return path.normalize(decoded);
3574
- }
3575
- return path.resolve(projectRoot, `.${decoded}`);
3576
- };
3577
- const normalizeScriptUrl = async (rawUrl, roots) => {
3578
- if (!rawUrl || rawUrl.startsWith("node:")) {
3579
- return null;
3580
- }
3581
- const absolute = resolveAbsoluteFromUrl(rawUrl, roots);
3582
- if (!absolute) {
3583
- return null;
3584
- }
3585
- let realPath = absolute;
3586
- try {
3587
- realPath = await fsPromises.realpath(absolute);
3588
- } catch {
3589
- }
3590
- const normalized = path.normalize(realPath);
3591
- if (normalized.includes(`${path.sep}node_modules${path.sep}`)) {
3592
- return null;
3593
- }
3594
- const workspaceRoot = path.resolve(roots.workspaceRoot);
3595
- const projectRoot = path.resolve(roots.projectRoot);
3596
- if (normalized.startsWith(workspaceRoot + path.sep) || normalized === workspaceRoot) {
3597
- return {
3598
- absolutePath: normalized,
3599
- relativePath: path.relative(workspaceRoot, normalized)
3600
- };
3601
- }
3602
- if (normalized.startsWith(projectRoot + path.sep) || normalized === projectRoot) {
3603
- return {
3604
- absolutePath: normalized,
3605
- relativePath: path.relative(projectRoot, normalized)
3606
- };
3607
- }
3608
- return null;
3609
- };
3610
- const fetchScriptSource = async (session2, scriptId) => {
3611
- try {
3612
- const response = await post(session2, "Debugger.getScriptSource", { scriptId });
3613
- return typeof response?.scriptSource === "string" ? response.scriptSource : "";
3614
- } catch {
3615
- return "";
3616
- }
3617
- };
3618
- const resolveScriptSource = async (session2, cache, scriptId) => {
3619
- const cached = cache.get(scriptId);
3620
- if (cached) {
3621
- return await cached;
3622
- }
3623
- const promise = fetchScriptSource(session2, scriptId);
3624
- cache.set(scriptId, promise);
3625
- return await promise;
3626
- };
3627
- const shutdownSession = async (session2) => {
3628
- await Promise.allSettled([
3629
- post(session2, "Profiler.stopPreciseCoverage").catch(() => void 0),
3630
- post(session2, "Profiler.disable").catch(() => void 0),
3631
- post(session2, "Debugger.disable").catch(() => void 0)
3632
- ]);
3633
- try {
3634
- session2.disconnect();
3635
- } catch {
3636
- }
3637
- };
3638
- const toPosixPath = (input) => input.split(path.sep).join("/");
3639
- const isSubpath = (candidate, root) => {
3640
- const relative = path.relative(root, candidate);
3641
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
3642
- };
3643
- const classifyServerTarget = (absolutePath, projectRoot) => {
3644
- if (!isSubpath(absolutePath, projectRoot)) {
3645
- return "ssr";
3646
- }
3647
- const relative = toPosixPath(path.relative(projectRoot, absolutePath));
3648
- if (relative.startsWith("src/api/") || relative.includes("/src/api/")) {
3649
- return "api";
3650
- }
3651
- if (relative.startsWith("src/worker/") || relative.includes("/src/worker/")) {
3652
- return "worker";
3653
- }
3654
- if (relative.startsWith("src/server/") || relative.includes("/src/server/")) {
3655
- return "ssr";
3656
- }
3657
- if (relative.startsWith("build/dist/api/") || relative.includes("/build/dist/api/")) {
3658
- return "api";
3659
- }
3660
- if (relative.startsWith("build/dist/worker/") || relative.includes("/build/dist/worker/")) {
3661
- return "worker";
3662
- }
3663
- if (relative.startsWith("build/dist/ssr/") || relative.includes("/build/dist/ssr/")) {
3664
- return "ssr";
3665
- }
3666
- return "ssr";
3667
- };
3668
- function registerDevCoverageEndpoints(app) {
3669
- app.post("/api/dev/coverage/start", async (_req, res) => {
3670
- if (process.env.NODE_ENV === "production") {
3671
- res.status(404).json({ error: "Not Found" });
3672
- return;
3673
- }
3674
- if (activeRun) {
3675
- res.json({ ok: true, status: "already_started", startedAt: activeRun.startedAt });
3676
- return;
3677
- }
3678
- const roots = {
3679
- projectRoot: path.resolve(process.cwd()),
3680
- workspaceRoot: findWorkspaceRoot(process.cwd())
3681
- };
3682
- const session2 = new inspector.Session();
3683
- session2.connect();
3684
- try {
3685
- await post(session2, "Debugger.enable");
3686
- await post(session2, "Profiler.enable");
3687
- await post(session2, "Profiler.startPreciseCoverage", { callCount: false, detailed: true });
3688
- activeRun = {
3689
- roots,
3690
- session: session2,
3691
- sourceCache: /* @__PURE__ */ new Map(),
3692
- startedAt: Date.now()
3693
- };
3694
- res.json({ ok: true, status: "started", startedAt: activeRun.startedAt });
3695
- } catch (error) {
3696
- await shutdownSession(session2);
3697
- activeRun = null;
3698
- res.status(500).json({ ok: false, error: describeError(error) });
3699
- }
3700
- });
3701
- app.post("/api/dev/coverage/stop", async (req, res) => {
3702
- if (process.env.NODE_ENV === "production") {
3703
- res.status(404).json({ error: "Not Found" });
3704
- return;
3705
- }
3706
- const run = activeRun;
3707
- if (!run) {
3708
- res.status(409).json({ ok: false, error: "coverage_not_started" });
3709
- return;
3710
- }
3711
- activeRun = null;
3712
- const outputRoot = path.join(run.roots.projectRoot, "build", "coverage", "server");
3713
- const outputFiles = {
3714
- api: path.join(outputRoot, "api", "v8-coverage.json"),
3715
- worker: path.join(outputRoot, "worker", "v8-coverage.json"),
3716
- ssr: path.join(outputRoot, "ssr", "v8-coverage.json")
3717
- };
3718
- try {
3719
- const taken = await post(run.session, "Profiler.takePreciseCoverage");
3720
- const result = Array.isArray(taken?.result) ? taken.result : [];
3721
- const scriptsByTarget = {
3722
- api: [],
3723
- worker: [],
3724
- ssr: []
3725
- };
3726
- for (const entry of result) {
3727
- const scriptId = typeof entry?.scriptId === "string" ? entry.scriptId : null;
3728
- const url = typeof entry?.url === "string" ? entry.url : null;
3729
- if (!scriptId || !url) {
3730
- continue;
3731
- }
3732
- const normalized = await normalizeScriptUrl(url, run.roots);
3733
- if (!normalized) {
3734
- continue;
3735
- }
3736
- const source = await resolveScriptSource(run.session, run.sourceCache, scriptId);
3737
- const script = {
3738
- absolutePath: normalized.absolutePath,
3739
- relativePath: normalized.relativePath,
3740
- source,
3741
- functions: Array.isArray(entry?.functions) ? entry.functions : [],
3742
- url
3743
- };
3744
- const target = classifyServerTarget(script.absolutePath, run.roots.projectRoot);
3745
- scriptsByTarget[target].push(script);
3746
- }
3747
- await Promise.all(
3748
- Object.keys(outputFiles).map(async (target) => {
3749
- const outputFile = outputFiles[target];
3750
- const payload = {
3751
- testId: `node-${target}`,
3752
- scripts: scriptsByTarget[target]
3753
- };
3754
- await fsPromises.mkdir(path.dirname(outputFile), { recursive: true });
3755
- await fsPromises.writeFile(outputFile, JSON.stringify(payload, null, 2), "utf8");
3756
- })
3757
- );
3758
- res.json({
3759
- ok: true,
3760
- status: "stopped",
3761
- outputFiles,
3762
- scriptCount: {
3763
- api: scriptsByTarget.api.length,
3764
- worker: scriptsByTarget.worker.length,
3765
- ssr: scriptsByTarget.ssr.length
3766
- }
3767
- });
3768
- } catch (error) {
3769
- res.status(500).json({ ok: false, error: describeError(error) });
3770
- } finally {
3771
- await shutdownSession(run.session);
3772
- }
3773
- });
3774
- }
3775
- process.env = {
3776
- ...env,
3777
- ...__rb_env__,
3778
- ...process.env
3779
- };
3780
- const isProduction$1 = process.env.NODE_ENV === "production";
3781
- const SESSION_MAX_AGE_S = 3600 * 24 * 60;
3782
- const getMongoUrl = (serverEnv) => {
3783
- const explicitUrl = serverEnv.MONGODB_URL || serverEnv.MONGO_URL || serverEnv.MONGODB_URI || serverEnv.DB_URL;
3784
- if (explicitUrl) {
3785
- return explicitUrl;
3786
- }
3787
- if (serverEnv.DB_PORT) {
3788
- const host = serverEnv.DB_HOST ?? "localhost";
3789
- const appName = serverEnv.APP_NAME?.trim();
3790
- if (!appName) {
3791
- throw new Error("Missing APP_NAME (required to build MongoDB session store DB name)");
3792
- }
3793
- return `mongodb://${host}:${serverEnv.DB_PORT}/${appName}-sessions`;
3794
- }
3795
- return void 0;
3796
- };
3797
- const createMongoSessionStore = async (serverEnv) => {
3798
- const mongoUrl = getMongoUrl(serverEnv);
3799
- if (!mongoUrl) {
3800
- throw new Error("Missing REDIS_URL and Mongo connection details (MONGODB_URL/MONGO_URL/MONGODB_URI/DB_PORT)");
3801
- }
3802
- console.log("Using MongoDB session store");
3803
- const client2 = await MongoClient.connect(mongoUrl, {
3804
- family: 4,
3805
- serverSelectionTimeoutMS: 2e3,
3806
- connectTimeoutMS: 2e3
3807
- });
3808
- const store = MongoStore.create({
3809
- client: client2,
3810
- collectionName: "sessions",
3811
- ttl: SESSION_MAX_AGE_S
3812
- });
3813
- await store.collectionP;
3814
- return store;
3815
- };
3816
- const createRedisSessionStore = async (redisUrl) => {
3817
- const reconnectStrategy = (retries) => {
3818
- console.log("redis_client::rb/server reconnectStrategy::retrying with arg", retries);
3819
- if (retries < 5) {
3820
- console.log("retry count:", retries, "retrying in 1s");
3821
- return 4e3;
3822
- } else {
3823
- return new Error("max retries expired");
3824
- }
3825
- };
3826
- const redisClient = createClient({
3827
- url: redisUrl,
3828
- socket: {
3829
- reconnectStrategy,
3830
- connectTimeout: 1e4,
3831
- keepAlive: false
3832
- }
3833
- });
3834
- redisClient.on("ready", () => {
3835
- console.log("session-storage::redis_client connected");
3836
- });
3837
- redisClient.on("error", (error) => {
3838
- console.log("session-storage::redis_client ERROR", error);
3839
- });
3840
- console.log("Using Redis session store");
3841
- await redisClient.connect();
3842
- return new RedisStore({
3843
- client: redisClient,
3844
- ttl: SESSION_MAX_AGE_S
3845
- });
3846
- };
3847
- const initServer = async (app, serverEnv) => {
3848
- await initApiClient({ app });
3849
- const replicaSetInitPromise = checkInitReplicaSet(serverEnv).catch((error) => {
3850
- const message = error instanceof Error ? error.message : String(error);
3851
- console.warn(`[rb/server] MongoDB replica set auto-init error: ${message}`);
3852
- });
3853
- app.disable("x-powered-by");
3854
- app.set("trust proxy", true);
3855
- app.use(requestIp.mw());
3856
- app.use((req, res, next) => {
3857
- if (req.headers.host?.startsWith("www.")) {
3858
- const newHost = req.headers.host.replace("www.", "");
3859
- return res.redirect(301, `${req.protocol}://${newHost}${req.originalUrl}`);
3860
- }
3861
- next();
3862
- });
3863
- metricsIngestProxyMiddleware(app);
3864
- const posthog = getPosthogClient(serverEnv);
3865
- if (posthog) {
3866
- app.locals.posthog = posthog;
3867
- bindExpressErrorHandler(app, posthog);
3868
- ensurePosthogShutdown();
3869
- }
3870
- if (!serverEnv.MASTER_KEY) {
3871
- throw new Error("MASTER_KEY must be defined to derive the session secret");
3872
- }
3873
- const sessionSecret = getDerivedKey(serverEnv.MASTER_KEY, "express_session_key");
3874
- const redisUrl = serverEnv.REDIS_URL?.trim();
3875
- let store;
3876
- if (redisUrl) {
3877
- store = await createRedisSessionStore(redisUrl);
3878
- } else {
3879
- await replicaSetInitPromise;
3880
- store = await createMongoSessionStore(serverEnv);
3881
- }
3882
- const sessionConfig = {
3883
- name: "session",
3884
- store,
3885
- proxy: true,
3886
- resave: false,
3887
- saveUninitialized: false,
3888
- secret: sessionSecret,
3889
- cookie: {
3890
- maxAge: SESSION_MAX_AGE_S * 1e3
3891
- }
3892
- };
3893
- if (isProduction$1) {
3894
- sessionConfig.cookie.secure = "auto";
3895
- }
3896
- const sessionMiddleware = session(sessionConfig);
3897
- if (!isProduction$1) {
3898
- registerDevCoverageEndpoints(app);
3899
- }
3900
- app.use(sessionMiddleware);
3901
- return {
3902
- sessionMiddleware
3903
- };
3904
- };
3905
- async function hashPassword(password, salt) {
3906
- const keyLength = 64;
3907
- const options = {
3908
- N: 8192,
3909
- // CPU/memory cost parameter
3910
- r: 8,
3911
- // Block size
3912
- p: 1
3913
- // Parallelization factor
3914
- };
3915
- const derivedKey = await new Promise((resolve, reject) => {
3916
- scrypt(password, salt, keyLength, options, (err, derivedKey2) => {
3917
- if (err) {
3918
- reject(err);
3919
- } else {
3920
- resolve(derivedKey2);
3921
- }
3922
- });
3923
- });
3924
- return derivedKey;
3925
- }
3926
- const DEFAULT_SCRYPT_N = 8192;
3927
- const DEFAULT_SCRYPT_R = 8;
3928
- const DEFAULT_SCRYPT_P = 4;
3929
- const DEFAULT_SCRYPT_KEYLEN = 64;
3930
- const DEFAULT_SCRYPT_SALT_BYTES = 16;
3931
- const DEFAULT_SCRYPT_MAXMEM_BYTES = 256 * 1024 * 1024;
3932
- const MAX_SCRYPT_MAXMEM_BYTES = 1024 * 1024 * 1024;
3933
- const MAX_SCRYPT_N = 1048576;
3934
- const MAX_SCRYPT_R = 64;
3935
- const MAX_SCRYPT_P = 16;
3936
- const MAX_SCRYPT_KEYLEN = 128;
3937
- const MAX_SCRYPT_SALT_BYTES = 64;
3938
- const parseEnvInt = (value) => {
3939
- if (typeof value !== "string") return void 0;
3940
- const trimmed = value.trim();
3941
- if (!trimmed) return void 0;
3942
- const parsed = Number(trimmed);
3943
- if (!Number.isSafeInteger(parsed)) return void 0;
3944
- return parsed;
3945
- };
3946
- const isPowerOfTwo = (value) => (value & value - 1) === 0;
3947
- const estimateScryptMemoryBytes = ({ N, r, p }) => {
3948
- return 128 * r * (N + p);
3949
- };
3950
- const validateScryptParams = (params) => {
3951
- const { N, r, p, keylen, saltBytes, maxmemBytes } = params;
3952
- if (!Number.isSafeInteger(N) || N < 2 || N > MAX_SCRYPT_N || !isPowerOfTwo(N)) {
3953
- return { ok: false, error: "invalid_scrypt_N" };
3954
- }
3955
- if (!Number.isSafeInteger(r) || r < 1 || r > MAX_SCRYPT_R) {
3956
- return { ok: false, error: "invalid_scrypt_r" };
3957
- }
3958
- if (!Number.isSafeInteger(p) || p < 1 || p > MAX_SCRYPT_P) {
3959
- return { ok: false, error: "invalid_scrypt_p" };
3960
- }
3961
- if (!Number.isSafeInteger(keylen) || keylen < 16 || keylen > MAX_SCRYPT_KEYLEN) {
3962
- return { ok: false, error: "invalid_scrypt_keylen" };
3963
- }
3964
- if (!Number.isSafeInteger(saltBytes) || saltBytes < 8 || saltBytes > MAX_SCRYPT_SALT_BYTES) {
3965
- return { ok: false, error: "invalid_scrypt_salt_bytes" };
3966
- }
3967
- if (!Number.isSafeInteger(maxmemBytes) || maxmemBytes < 16 * 1024 * 1024 || maxmemBytes > MAX_SCRYPT_MAXMEM_BYTES) {
3968
- return { ok: false, error: "invalid_scrypt_maxmem" };
3969
- }
3970
- const estimatedMem = estimateScryptMemoryBytes({ N, r, p });
3971
- if (estimatedMem > maxmemBytes) {
3972
- return { ok: false, error: "scrypt_params_exceed_maxmem" };
3973
- }
3974
- return { ok: true };
3975
- };
3976
- const getCurrentMaxmemBytes = (opts) => {
3977
- const envMaxmemBytes = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_MAXMEM_BYTES);
3978
- const maxmemBytes = opts?.maxmemBytes ?? envMaxmemBytes ?? DEFAULT_SCRYPT_MAXMEM_BYTES;
3979
- if (!Number.isSafeInteger(maxmemBytes) || maxmemBytes < 16 * 1024 * 1024 || maxmemBytes > MAX_SCRYPT_MAXMEM_BYTES) {
3980
- throw new Error("invalid_scrypt_maxmem");
3981
- }
3982
- return maxmemBytes;
3983
- };
3984
- const getCurrentScryptParams = (opts) => {
3985
- const envN = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_N);
3986
- const envR = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_R);
3987
- const envP = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_P);
3988
- const envKeylen = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_KEYLEN);
3989
- const envSaltBytes = parseEnvInt(process.env.RB_PASSWORD_SCRYPT_SALT_BYTES);
3990
- const params = {
3991
- N: opts?.N ?? envN ?? DEFAULT_SCRYPT_N,
3992
- r: opts?.r ?? envR ?? DEFAULT_SCRYPT_R,
3993
- p: opts?.p ?? envP ?? DEFAULT_SCRYPT_P,
3994
- keylen: opts?.keylen ?? envKeylen ?? DEFAULT_SCRYPT_KEYLEN,
3995
- saltBytes: opts?.saltBytes ?? envSaltBytes ?? DEFAULT_SCRYPT_SALT_BYTES,
3996
- maxmemBytes: getCurrentMaxmemBytes(opts)
3997
- };
3998
- const validated = validateScryptParams(params);
3999
- if (!validated.ok) {
4000
- throw new Error(validated.error);
4001
- }
4002
- return params;
4003
- };
4004
- const scryptAsync = async (password, salt, params) => {
4005
- const { N, r, p, keylen, maxmemBytes } = params;
4006
- return await new Promise((resolve, reject) => {
4007
- crypto.scrypt(password, salt, keylen, { N, r, p, maxmem: maxmemBytes }, (err, derivedKey) => {
4008
- if (err) {
4009
- reject(err);
4010
- return;
4011
- }
4012
- resolve(derivedKey);
4013
- });
4014
- });
4015
- };
4016
- const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
4017
- const parseB64 = (value) => {
4018
- if (!value) return null;
4019
- if (!base64Regex.test(value)) return null;
4020
- if (value.length % 4 !== 0) return null;
4021
- const buf = Buffer.from(value, "base64");
4022
- if (buf.length === 0) return null;
4023
- if (buf.toString("base64") !== value) return null;
4024
- return buf;
4025
- };
4026
- const MAX_STORED_PASSWORD_LENGTH = 1024;
4027
- const MAX_STORED_PASSWORD_SECTION_LENGTH = 256;
4028
- const parseStoredScryptHash = (stored) => {
4029
- if (stored.length > MAX_STORED_PASSWORD_LENGTH) return null;
4030
- const parts = stored.split("$");
4031
- if (parts.length !== 5) return null;
4032
- if (parts[0] !== "" || parts[1] !== "scrypt") return null;
4033
- const paramsStr = parts[2];
4034
- const saltB64 = parts[3];
4035
- const dkB64 = parts[4];
4036
- if (paramsStr.length > MAX_STORED_PASSWORD_SECTION_LENGTH) return null;
4037
- if (saltB64.length > MAX_STORED_PASSWORD_SECTION_LENGTH) return null;
4038
- if (dkB64.length > MAX_STORED_PASSWORD_SECTION_LENGTH) return null;
4039
- const kvPairs = paramsStr.split(",").filter(Boolean);
4040
- if (kvPairs.length === 0) return null;
4041
- const params = /* @__PURE__ */ new Map();
4042
- for (const pair of kvPairs) {
4043
- const idx = pair.indexOf("=");
4044
- if (idx <= 0 || idx === pair.length - 1) return null;
4045
- const key = pair.slice(0, idx);
4046
- const valueRaw = pair.slice(idx + 1);
4047
- if (!/^[a-zA-Z0-9]+$/.test(key)) return null;
4048
- if (params.has(key)) return null;
4049
- const value = Number(valueRaw);
4050
- if (!Number.isSafeInteger(value)) return null;
4051
- params.set(key, value);
4052
- }
4053
- const N = params.get("N");
4054
- const r = params.get("r");
4055
- const p = params.get("p");
4056
- const keylen = params.get("keylen");
4057
- if (N === void 0 || r === void 0 || p === void 0 || keylen === void 0) return null;
4058
- if (params.size !== 4) return null;
4059
- const salt = parseB64(saltB64);
4060
- const dk = parseB64(dkB64);
4061
- if (!salt || !dk) return null;
4062
- if (dk.length !== keylen) return null;
4063
- if (salt.length < 8 || salt.length > MAX_SCRYPT_SALT_BYTES) return null;
4064
- if (dk.length < 16 || dk.length > MAX_SCRYPT_KEYLEN) return null;
4065
- const currentMaxmemBytes = getCurrentMaxmemBytes();
4066
- const validated = validateScryptParams({
4067
- N,
4068
- r,
4069
- p,
4070
- keylen,
4071
- saltBytes: salt.length,
4072
- maxmemBytes: currentMaxmemBytes
4073
- });
4074
- if (!validated.ok) return null;
4075
- return { N, r, p, keylen, salt, dk };
4076
- };
4077
- async function hashPasswordForStorage(password, opts) {
4078
- const { N, r, p, keylen, saltBytes, maxmemBytes } = getCurrentScryptParams(opts);
4079
- const salt = crypto.randomBytes(saltBytes);
4080
- const dk = await scryptAsync(password, salt, { N, r, p, keylen, maxmemBytes });
4081
- const saltB64 = salt.toString("base64");
4082
- const dkB64 = dk.toString("base64");
4083
- return `$scrypt$N=${N},r=${r},p=${p},keylen=${keylen}$${saltB64}$${dkB64}`;
4084
- }
4085
- async function verifyPasswordFromStorage(password, stored) {
4086
- const parsed = parseStoredScryptHash(stored);
4087
- if (!parsed) return false;
4088
- const { N, r, p, keylen, salt, dk } = parsed;
4089
- const maxmemBytes = getCurrentMaxmemBytes();
4090
- let derivedKey;
4091
- try {
4092
- derivedKey = await scryptAsync(password, salt, { N, r, p, keylen, maxmemBytes });
4093
- } catch {
4094
- return false;
4095
- }
4096
- if (derivedKey.length !== dk.length) return false;
4097
- return crypto.timingSafeEqual(dk, derivedKey);
4098
- }
4099
- function createLocation(current, to, state = null, key) {
4100
- const location = {
4101
- pathname: current,
4102
- search: "",
4103
- hash: "",
4104
- ...typeof to === "string" ? parsePath(to) : to,
4105
- state,
4106
- // TODO: This could be cleaned up. push/replace should probably just take
4107
- // full Locations now and avoid the need to run through this flow at all
4108
- // But that's a pretty big refactor to the current test suite so going to
4109
- // keep as is for the time being and just let any incoming keys take precedence
4110
- key: to && to.key || key
4111
- };
4112
- return location;
4113
- }
4114
- function getShortCircuitMatches(routes) {
4115
- const route = routes.length === 1 ? routes[0] : routes.find((r) => r.index || !r.path || r.path === "/") || {
4116
- id: "__shim-error-route__"
4117
- };
4118
- return {
4119
- matches: [
4120
- {
4121
- params: {},
4122
- pathname: "",
4123
- pathnameBase: "",
4124
- route
4125
- }
4126
- ],
4127
- route
4128
- };
4129
- }
4130
- const NOT_FOUND_STATUS = 404;
4131
- const LOADER_TIMEOUT_MS = 4e3;
4132
- const getErrorStatus = (error) => {
4133
- if (!error) return void 0;
4134
- const candidate = error?.reason ?? error;
4135
- if (typeof candidate?.status === "number") return candidate.status;
4136
- if (typeof candidate?.statusCode === "number") return candidate.statusCode;
4137
- if (typeof candidate?.response?.status === "number") return candidate.response.status;
4138
- return void 0;
4139
- };
4140
- const isResponseLike = (value) => {
4141
- return Boolean(
4142
- value && typeof value === "object" && typeof value.status === "number" && value.headers && typeof value.headers.get === "function"
4143
- );
4144
- };
4145
- const isRedirectResponse = (value) => {
4146
- if (!isResponseLike(value)) return false;
4147
- return value.status >= 300 && value.status < 400 && Boolean(value.headers.get("Location"));
4148
- };
4149
- const getRouteHandleStatusCode = (route) => {
4150
- if (!route) return void 0;
4151
- const handle = route.handle;
4152
- if (!handle) return void 0;
4153
- const candidate = handle.statusCode ?? handle.status ?? handle.httpStatus;
4154
- if (typeof candidate === "number") return candidate;
4155
- return void 0;
4156
- };
4157
- const isNotFoundFallbackRoute = (route) => {
4158
- if (!route) return false;
4159
- if (route.path !== "*") return false;
4160
- if (route.loader) return false;
4161
- if (route.action) return false;
4162
- if (route.children?.length) return false;
4163
- return true;
4164
- };
4165
- async function applyRouteLoaders(req, dataRoutes) {
4166
- const baseUrl = `${req.protocol}://${req.get("host")}`;
4167
- const url = new URL(req.originalUrl, baseUrl);
4168
- const method = req.method;
4169
- const location = createLocation("", createPath(url), null, "default");
4170
- const baseContext = {
4171
- basename: "",
4172
- location,
4173
- loaderHeaders: {},
4174
- actionHeaders: {}
4175
- };
4176
- const matches = matchRoutes(dataRoutes, location);
4177
- if (!matches || matches.length === 0) {
4178
- const error = {
4179
- status: 404,
4180
- message: `No route matches URL: ${req.originalUrl}`
4181
- };
4182
- const { matches: notFoundMatches, route } = getShortCircuitMatches(dataRoutes);
4183
- return {
4184
- ...baseContext,
4185
- matches: notFoundMatches,
4186
- loaderData: {},
4187
- actionData: null,
4188
- errors: { [route.id]: error },
4189
- statusCode: 404
4190
- };
4191
- }
4192
- if (method !== "GET") {
4193
- return {
4194
- ...baseContext,
4195
- matches,
4196
- loaderData: {},
4197
- actionData: null,
4198
- errors: null,
4199
- statusCode: 200
4200
- };
4201
- }
4202
- const runLoaderWithTimeout = async (route, params) => {
4203
- if (!route.loader) return null;
4204
- let timeoutId;
4205
- const timeoutPromise = new Promise((_, reject) => {
4206
- timeoutId = setTimeout(() => {
4207
- const err = new Error(`Loader timeout after ${LOADER_TIMEOUT_MS}ms`);
4208
- err.status = 504;
4209
- console.error("[rpcbase timeout][server loader]", {
4210
- routeId: route.id,
4211
- ms: LOADER_TIMEOUT_MS,
4212
- url: req.originalUrl
4213
- });
4214
- reject({ id: route.id, path: route.path, reason: err });
4215
- }, LOADER_TIMEOUT_MS);
4216
- });
4217
- const loaderPromise = (async () => {
4218
- try {
4219
- const data = await route.loader({
4220
- params,
4221
- ctx: { req }
4222
- });
4223
- return { id: route.id, path: route.path, data };
4224
- } catch (error) {
4225
- throw { id: route.id, path: route.path, reason: error };
4226
- } finally {
4227
- if (timeoutId) {
4228
- clearTimeout(timeoutId);
4229
- }
4230
- }
4231
- })();
4232
- return Promise.race([loaderPromise, timeoutPromise]);
4233
- };
4234
- const loaderPromisesResults = await Promise.allSettled(
4235
- matches.map((match) => runLoaderWithTimeout(match.route, match.params))
4236
- );
4237
- const loaderData = {};
4238
- let errors = null;
4239
- let hasNotFoundError = false;
4240
- let hasNonNotFoundError = false;
4241
- let redirectResponse = null;
4242
- let redirectRouteId = null;
4243
- let redirectRoutePath = null;
4244
- for (const result of loaderPromisesResults) {
4245
- if (result.status === "fulfilled") {
4246
- if (result.value) {
4247
- if (isRedirectResponse(result.value.data)) {
4248
- redirectResponse = result.value.data;
4249
- redirectRouteId = result.value.id;
4250
- redirectRoutePath = result.value.path ?? null;
4251
- } else {
4252
- loaderData[result.value.id] = result.value.data;
4253
- }
4254
- }
4255
- } else if (result.status === "rejected") {
4256
- const id = result.reason?.id;
4257
- if (!id) {
4258
- throw new Error(
4259
- `missing route ID in error: ${result.reason}`
4260
- );
4261
- }
4262
- const reasonCandidate = result.reason?.reason ?? result.reason;
4263
- if (isRedirectResponse(reasonCandidate)) {
4264
- redirectResponse = reasonCandidate;
4265
- redirectRouteId = id;
4266
- redirectRoutePath = result.reason?.path ?? null;
4267
- continue;
4268
- }
4269
- if (!errors) {
4270
- errors = {};
4271
- }
4272
- const status = getErrorStatus(result.reason);
4273
- if (status === NOT_FOUND_STATUS) {
4274
- hasNotFoundError = true;
4275
- } else {
4276
- hasNonNotFoundError = true;
4277
- }
4278
- errors[id] = result.reason;
4279
- }
4280
- }
4281
- if (redirectResponse) {
4282
- return {
4283
- ...baseContext,
4284
- matches,
4285
- loaderData,
4286
- actionData: null,
4287
- errors: null,
4288
- statusCode: redirectResponse.status,
4289
- redirectResponse,
4290
- redirectRouteId,
4291
- redirectRoutePath
4292
- };
4293
- }
4294
- let statusCode = 200;
4295
- if (errors) {
4296
- if (hasNonNotFoundError) {
4297
- statusCode = 500;
4298
- } else if (hasNotFoundError) {
4299
- statusCode = NOT_FOUND_STATUS;
4300
- } else {
4301
- statusCode = 500;
4302
- }
4303
- }
4304
- if (!errors && statusCode === 200) {
4305
- const leafRoute = matches.at(-1)?.route;
4306
- const handleStatusCode = getRouteHandleStatusCode(leafRoute);
4307
- if (typeof handleStatusCode === "number") {
4308
- statusCode = handleStatusCode;
4309
- } else if (isNotFoundFallbackRoute(leafRoute)) {
4310
- statusCode = NOT_FOUND_STATUS;
4311
- }
4312
- }
4313
- return {
4314
- ...baseContext,
4315
- matches,
4316
- loaderData,
4317
- actionData: null,
4318
- errors,
4319
- statusCode
4320
- };
4321
- }
4322
- async function renderSSR(req, dataRoutes) {
4323
- const routerContext = await applyRouteLoaders(req, dataRoutes);
4324
- const isMatched = routerContext.matches.length > 0;
4325
- if (routerContext.redirectResponse) {
4326
- return {
4327
- element: null,
4328
- isMatched,
4329
- statusCode: routerContext.statusCode ?? routerContext.redirectResponse.status ?? 302,
4330
- redirectResponse: routerContext.redirectResponse,
4331
- redirectRouteId: routerContext.redirectRouteId,
4332
- redirectRoutePath: routerContext.redirectRoutePath
4333
- };
4334
- }
4335
- if (routerContext.errors) {
4336
- if (routerContext.statusCode === 404) {
4337
- console.warn(`SSR 404 ${req.method} ${req.originalUrl}`, {
4338
- errors: routerContext.errors
4339
- });
4340
- } else {
4341
- const matchesSummary = routerContext.matches?.map((m) => ({
4342
- routeId: m.route.id,
4343
- routePath: m.route.path,
4344
- pathname: m.pathname,
4345
- pathnameBase: m.pathnameBase
4346
- }));
4347
- console.error(
4348
- `SSR ${routerContext.statusCode || 500} ${req.method} ${req.originalUrl}`,
4349
- {
4350
- errors: routerContext.errors,
4351
- matches: matchesSummary
4352
- }
4353
- );
4354
- const errorEntries = Object.entries(routerContext.errors || {});
4355
- const firstErrorEntry = errorEntries[0]?.[1];
4356
- const firstReason = firstErrorEntry?.reason ?? firstErrorEntry;
4357
- if (errorEntries.length === 1 && firstReason instanceof Error) {
4358
- firstReason.details = routerContext.errors;
4359
- throw firstReason;
4360
- }
4361
- const extra = firstErrorEntry?.id ? ` (route ${firstErrorEntry.id}${firstErrorEntry?.path ? ` ${firstErrorEntry.path}` : ""})` : "";
4362
- const error = new Error(`SSR loader error${extra}`);
4363
- error.details = routerContext.errors;
4364
- throw error;
4365
- }
4366
- }
4367
- const router = createStaticRouter(dataRoutes, routerContext);
4368
- const element = /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(
4369
- StaticRouterProvider,
4370
- {
4371
- router,
4372
- context: routerContext
4373
- }
4374
- ) });
4375
- return {
4376
- element,
4377
- isMatched,
4378
- statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200)
4379
- };
4380
- }
4381
- const ABORT_DELAY_MS = 1e4;
4382
- const APP_HTML_PLACEHOLDER = "<!--app-html-->";
4383
- const DEFAULT_SERVER_ERROR_MESSAGE = "We couldn't render this page on the server. Please refresh and try again.";
4384
- const FALLBACK_ERROR_TEMPLATE_START = `<!doctype html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>Server error</title><style>body{margin:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0b1021;color:#eef1f7;display:flex;align-items:center;justify-content:center;min-height:100vh;}main{max-width:420px;padding:32px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:14px;box-shadow:0 25px 50px rgba(0,0,0,0.35);}h1{font-size:24px;margin:0 0 8px;}p{margin:0 0 12px;line-height:1.5;}code{background:rgba(255,255,255,0.08);padding:2px 6px;border-radius:6px;font-size:12px;}</style></head><body><main>`;
4385
- const FALLBACK_ERROR_TEMPLATE_END = "</main></body></html>";
4386
- const isProduction = env.NODE_ENV === "production";
4387
- const templateHtml = isProduction ? readFileSync("./build/dist/client/src/client/index.html", "utf-8") : "";
4388
- const handleRedirectionResponse = (res, redirectResponse, location) => {
4389
- res.status(redirectResponse.status || 302);
4390
- try {
4391
- const headers = redirectResponse.headers;
4392
- const setCookies = headers.getSetCookie?.();
4393
- const fallbackSetCookies = [];
4394
- for (const [key, value] of headers) {
4395
- if (key.toLowerCase() === "set-cookie") {
4396
- if (!setCookies?.length) {
4397
- fallbackSetCookies.push(value);
4398
- }
4399
- continue;
4400
- }
4401
- res.setHeader(key, value);
4402
- }
4403
- if (setCookies?.length) {
4404
- res.setHeader("Set-Cookie", setCookies);
4405
- } else if (fallbackSetCookies.length === 1) {
4406
- res.setHeader("Set-Cookie", fallbackSetCookies[0]);
4407
- } else if (fallbackSetCookies.length > 1) {
4408
- res.setHeader("Set-Cookie", fallbackSetCookies);
4409
- }
4410
- } catch {
4411
- }
4412
- if (location) res.setHeader("Location", location);
4413
- res.end();
4414
- };
4415
- const formatErrorDetails = (error) => {
4416
- if (isProduction) return void 0;
4417
- if (!error) return void 0;
4418
- if (error instanceof Error) {
4419
- return error.stack || error.message;
4420
- }
4421
- if (typeof error === "string") {
4422
- return error;
4423
- }
4424
- try {
4425
- return JSON.stringify(error, null, 2);
4426
- } catch {
4427
- return void 0;
4428
- }
4429
- };
4430
- const sendErrorResponse = ({
4431
- res,
4432
- htmlStart,
4433
- htmlEnd,
4434
- error,
4435
- status = 500,
4436
- title = "Server rendering error",
4437
- message = DEFAULT_SERVER_ERROR_MESSAGE,
4438
- errorExtraComponent
4439
- }) => {
4440
- if (res.headersSent) return;
4441
- const start = htmlStart ?? FALLBACK_ERROR_TEMPLATE_START;
4442
- const end = htmlEnd ?? FALLBACK_ERROR_TEMPLATE_END;
4443
- res.status(status);
4444
- res.set({ "Content-Type": "text/html" });
4445
- const details = formatErrorDetails(error);
4446
- const state = {
4447
- statusCode: status,
4448
- title,
4449
- message,
4450
- details
4451
- };
4452
- const markup = renderToStaticMarkup(
4453
- createElement(
4454
- StrictMode,
4455
- null,
4456
- createElement(SsrErrorFallback, {
4457
- state,
4458
- renderErrorExtra: errorExtraComponent ? (payload) => createElement(errorExtraComponent, { state: payload }) : void 0
4459
- })
4460
- )
4461
- );
4462
- const serializedState = `<script>window.${SSR_ERROR_STATE_GLOBAL_KEY}=${serializeSsrErrorState({
4463
- ...state,
4464
- details: isProduction ? void 0 : details
4465
- })};<\/script>`;
4466
- res.end(`${start}${markup}${serializedState}${end}`);
4467
- };
4468
- const ssrMiddleware = ({
4469
- viteInstance,
4470
- dataRoutes,
4471
- errorExtraComponent,
4472
- renderTemplateStart,
4473
- posthog
4474
- }) => async (req, res, next) => {
4475
- let template;
4476
- let htmlStart = null;
4477
- let htmlEnd = null;
4478
- let responseCommitted = false;
4479
- const posthogClient = posthog ?? req.app?.locals?.posthog ?? null;
4480
- const captureException = (error, extra) => {
4481
- if (!error) return;
4482
- captureServerException(error, posthogClient, {
4483
- path: req.originalUrl,
4484
- method: req.method,
4485
- source: "ssrMiddleware",
4486
- ...extra
4487
- });
4488
- };
4489
- const finalizeWithErrorPage = (error, status = 500, message = DEFAULT_SERVER_ERROR_MESSAGE) => {
4490
- if (responseCommitted) return;
4491
- sendErrorResponse({
4492
- res,
4493
- htmlStart,
4494
- htmlEnd,
4495
- error,
4496
- status,
4497
- message,
4498
- errorExtraComponent
4499
- });
4500
- responseCommitted = true;
4501
- if (error) {
4502
- captureException(error, { phase: "finalizeWithErrorPage" });
4503
- }
4504
- };
4505
- try {
4506
- const base = "/";
4507
- const url = req.originalUrl.replace(base, "");
4508
- if (isProduction) {
4509
- template = templateHtml;
4510
- } else {
4511
- template = await fsPromises.readFile("./src/client/index.html", "utf-8");
4512
- template = await viteInstance.transformIndexHtml(url, template);
4513
- }
4514
- const placeholderIndex = template.indexOf(APP_HTML_PLACEHOLDER);
4515
- if (placeholderIndex === -1) {
4516
- throw new Error(`Template missing ${APP_HTML_PLACEHOLDER} placeholder`);
4517
- }
4518
- const templateStart = template.slice(0, placeholderIndex);
4519
- if (renderTemplateStart) {
4520
- htmlStart = await renderTemplateStart(req, templateStart);
4521
- } else {
4522
- htmlStart = templateStart;
4523
- }
4524
- htmlEnd = template.slice(placeholderIndex + APP_HTML_PLACEHOLDER.length);
4525
- const {
4526
- element,
4527
- isMatched,
4528
- statusCode,
4529
- redirectResponse,
4530
- redirectRouteId,
4531
- redirectRoutePath
4532
- } = await renderSSR(req, dataRoutes);
4533
- if (redirectResponse) {
4534
- if (!responseCommitted) {
4535
- const location = redirectResponse.headers?.get?.("Location");
4536
- if (!isProduction) {
4537
- console.info("SSR redirect", {
4538
- method: req.method,
4539
- url: req.originalUrl,
4540
- status: redirectResponse.status,
4541
- location,
4542
- routeId: redirectRouteId ?? void 0,
4543
- routePath: redirectRoutePath ?? void 0
4544
- });
4545
- }
4546
- responseCommitted = true;
4547
- handleRedirectionResponse(res, redirectResponse, location);
4548
- }
4549
- return;
4550
- }
4551
- if (!isMatched) {
4552
- next();
4553
- return;
4554
- }
4555
- let didError = false;
4556
- const { pipe, abort } = renderToPipeableStream(
4557
- element,
4558
- {
4559
- onShellError(error) {
4560
- if (error instanceof Error) {
4561
- viteInstance?.ssrFixStacktrace(error);
4562
- }
4563
- console.error("SSR shell error", error);
4564
- captureException(error, { phase: "shell" });
4565
- finalizeWithErrorPage(error);
4566
- abort();
4567
- },
4568
- onError(error) {
4569
- didError = true;
4570
- if (error instanceof Error) {
4571
- viteInstance?.ssrFixStacktrace(error);
4572
- }
4573
- console.error("SSR rendering error", error);
4574
- captureException(error, { phase: "render" });
4575
- },
4576
- onShellReady() {
4577
- if (responseCommitted) {
4578
- return;
4579
- }
4580
- if (!htmlStart || !htmlEnd) {
4581
- finalizeWithErrorPage();
4582
- return;
4583
- }
4584
- responseCommitted = true;
4585
- const responseStatus = didError ? 500 : statusCode || 200;
4586
- res.status(responseStatus);
4587
- res.set({ "Content-Type": "text/html" });
4588
- const transformStream = new Transform({
4589
- transform(chunk, encoding, callback) {
4590
- res.write(chunk, encoding);
4591
- callback();
4592
- }
4593
- });
4594
- const start = htmlStart;
4595
- const end = htmlEnd;
4596
- res.write(start);
4597
- transformStream.on("finish", () => {
4598
- res.end(end);
4599
- });
4600
- pipe(transformStream);
4601
- }
4602
- }
4603
- );
4604
- setTimeout(() => {
4605
- abort();
4606
- }, ABORT_DELAY_MS);
4607
- } catch (err) {
4608
- if (err instanceof Error) {
4609
- viteInstance?.ssrFixStacktrace(err);
4610
- console.error("SSR middleware error:", err.message);
4611
- console.error(err.stack);
4612
- } else {
4613
- console.error("Unknown SSR middleware error", err);
4614
- }
4615
- captureException(err, { phase: "catch" });
4616
- finalizeWithErrorPage(err);
4617
- }
4618
- };
4619
- export {
4620
- getDerivedKey,
4621
- hashPassword,
4622
- hashPasswordForStorage,
4623
- initServer,
4624
- s as sendEmail,
4625
- ssrMiddleware,
4626
- verifyPasswordFromStorage
4627
- };
4628
- //# sourceMappingURL=index.js.map