mailery 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/spa/index-B88z7X-U.css +1 -0
- package/dist/admin/spa/index-m4wK04gU.js +9 -0
- package/dist/admin/spa/index-m4wK04gU.js.map +1 -0
- package/dist/admin/spa/index.html +2 -2
- package/dist/admin/spa/template-editor-CKQLM0Me.js +156 -0
- package/dist/admin/spa/{template-editor-pGcCFnJ7.js.map → template-editor-CKQLM0Me.js.map} +1 -1
- package/dist/cli.cjs +5 -10
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +911 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +359 -13
- package/dist/index.d.ts +359 -13
- package/dist/index.js +904 -107
- package/dist/index.js.map +1 -1
- package/dist/{null-DDg3ojHe.d.cts → null-CnhsvKvy.d.cts} +136 -6
- package/dist/{null-DDg3ojHe.d.ts → null-CnhsvKvy.d.ts} +136 -6
- package/dist/testing.cjs +592 -191
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +591 -192
- package/dist/testing.js.map +1 -1
- package/package.json +15 -11
- package/dist/admin/spa/index-B6riKdpB.css +0 -1
- package/dist/admin/spa/index-l7m1PyMb.js +0 -41
- package/dist/admin/spa/index-l7m1PyMb.js.map +0 -1
- package/dist/admin/spa/template-editor-pGcCFnJ7.js +0 -502
package/dist/testing.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { MongoClient, ObjectId } from 'mongodb';
|
|
3
|
-
import crypto2 from 'crypto';
|
|
3
|
+
import crypto2, { randomBytes } from 'crypto';
|
|
4
4
|
import sgMail from '@sendgrid/mail';
|
|
5
5
|
import IORedis from 'ioredis';
|
|
6
6
|
import Handlebars from 'handlebars';
|
|
@@ -9,6 +9,9 @@ import mjml2html from 'mjml';
|
|
|
9
9
|
import dns from 'dns/promises';
|
|
10
10
|
import net from 'net';
|
|
11
11
|
import psl from 'psl';
|
|
12
|
+
import 'fast-xml-parser';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
12
15
|
|
|
13
16
|
var __create = Object.create;
|
|
14
17
|
var __defProp = Object.defineProperty;
|
|
@@ -22,11 +25,20 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
22
25
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
23
26
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
24
27
|
});
|
|
25
|
-
var __esm = (fn, res) => function __init() {
|
|
26
|
-
|
|
28
|
+
var __esm = (fn, res, err) => function __init() {
|
|
29
|
+
if (err) throw err[0];
|
|
30
|
+
try {
|
|
31
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
throw err = [e], e;
|
|
34
|
+
}
|
|
27
35
|
};
|
|
28
36
|
var __commonJS = (cb, mod) => function __require2() {
|
|
29
|
-
|
|
37
|
+
try {
|
|
38
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
throw mod = 0, e;
|
|
41
|
+
}
|
|
30
42
|
};
|
|
31
43
|
var __export = (target, all) => {
|
|
32
44
|
for (var name in all)
|
|
@@ -259,6 +271,31 @@ var init_mongo = __esm({
|
|
|
259
271
|
}
|
|
260
272
|
});
|
|
261
273
|
|
|
274
|
+
// src/server/providers/webhook-tolerance.ts
|
|
275
|
+
function resolveWebhookToleranceSeconds(option) {
|
|
276
|
+
if (option === false || option === 0) return 0;
|
|
277
|
+
if (option === void 0 || option === null) return DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
|
|
278
|
+
if (typeof option !== "number" || !Number.isFinite(option) || option < 0) {
|
|
279
|
+
return DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
|
|
280
|
+
}
|
|
281
|
+
return option;
|
|
282
|
+
}
|
|
283
|
+
function isWebhookTimestampFresh(rawTimestamp, toleranceSeconds, now = /* @__PURE__ */ new Date()) {
|
|
284
|
+
if (toleranceSeconds === 0) return true;
|
|
285
|
+
if (!rawTimestamp) return false;
|
|
286
|
+
if (!/^\d{1,15}$/.test(rawTimestamp)) return false;
|
|
287
|
+
const timestampMs = Number(rawTimestamp) * 1e3;
|
|
288
|
+
if (!Number.isFinite(timestampMs)) return false;
|
|
289
|
+
const deltaMs = Math.abs(now.getTime() - timestampMs);
|
|
290
|
+
return deltaMs <= toleranceSeconds * 1e3;
|
|
291
|
+
}
|
|
292
|
+
var DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
|
|
293
|
+
var init_webhook_tolerance = __esm({
|
|
294
|
+
"src/server/providers/webhook-tolerance.ts"() {
|
|
295
|
+
DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
262
299
|
// src/server/providers/sendgrid.ts
|
|
263
300
|
var sendgrid_exports = {};
|
|
264
301
|
__export(sendgrid_exports, {
|
|
@@ -305,6 +342,7 @@ function mapEventType(sgEvent) {
|
|
|
305
342
|
var SG_SIG_HEADER, SG_TS_HEADER, SendGridProvider;
|
|
306
343
|
var init_sendgrid = __esm({
|
|
307
344
|
"src/server/providers/sendgrid.ts"() {
|
|
345
|
+
init_webhook_tolerance();
|
|
308
346
|
SG_SIG_HEADER = "x-twilio-email-event-webhook-signature";
|
|
309
347
|
SG_TS_HEADER = "x-twilio-email-event-webhook-timestamp";
|
|
310
348
|
SendGridProvider = class {
|
|
@@ -312,10 +350,13 @@ var init_sendgrid = __esm({
|
|
|
312
350
|
this.opts = opts;
|
|
313
351
|
sgMail.setApiKey(opts.apiKey);
|
|
314
352
|
this.sendRatePerSecond = opts.sendRatePerSecond ?? 10;
|
|
353
|
+
this.webhookToleranceSeconds = resolveWebhookToleranceSeconds(opts.webhookToleranceSeconds);
|
|
315
354
|
}
|
|
316
355
|
opts;
|
|
317
356
|
name = "sendgrid";
|
|
318
357
|
sendRatePerSecond;
|
|
358
|
+
/** Resolved replay window in seconds; `0` means the check is disabled. */
|
|
359
|
+
webhookToleranceSeconds;
|
|
319
360
|
async send(args) {
|
|
320
361
|
const msg = {
|
|
321
362
|
to: args.to,
|
|
@@ -355,10 +396,11 @@ var init_sendgrid = __esm({
|
|
|
355
396
|
try {
|
|
356
397
|
const verifier = crypto2.createVerify("sha256");
|
|
357
398
|
verifier.update(payload);
|
|
358
|
-
|
|
399
|
+
if (!verifier.verify(this.opts.webhookVerificationKey, sig, "base64")) return false;
|
|
359
400
|
} catch {
|
|
360
401
|
return false;
|
|
361
402
|
}
|
|
403
|
+
return isWebhookTimestampFresh(ts, this.webhookToleranceSeconds);
|
|
362
404
|
}
|
|
363
405
|
parseWebhookEvents(payload) {
|
|
364
406
|
if (!Array.isArray(payload)) return [];
|
|
@@ -797,13 +839,13 @@ function __disposeResources(env) {
|
|
|
797
839
|
}
|
|
798
840
|
return next();
|
|
799
841
|
}
|
|
800
|
-
function __rewriteRelativeImportExtension(
|
|
801
|
-
if (typeof
|
|
802
|
-
return
|
|
842
|
+
function __rewriteRelativeImportExtension(path2, preserveJsx) {
|
|
843
|
+
if (typeof path2 === "string" && /^\.\.?\//.test(path2)) {
|
|
844
|
+
return path2.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
|
|
803
845
|
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
|
|
804
846
|
});
|
|
805
847
|
}
|
|
806
|
-
return
|
|
848
|
+
return path2;
|
|
807
849
|
}
|
|
808
850
|
var extendStatics, __assign, __createBinding, __setModuleDefault, ownKeys, _SuppressedError, tslib_es6_default;
|
|
809
851
|
var init_tslib_es6 = __esm({
|
|
@@ -1633,7 +1675,7 @@ var require_lib = __commonJS({
|
|
|
1633
1675
|
"node_modules/new-find-package-json/lib/index.js"(exports) {
|
|
1634
1676
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1635
1677
|
exports.findSync = exports.find = exports.findAsync = void 0;
|
|
1636
|
-
var
|
|
1678
|
+
var path2 = __require("path");
|
|
1637
1679
|
var fs_1 = __require("fs");
|
|
1638
1680
|
var debug = require_src();
|
|
1639
1681
|
var log = debug("new-find-package-json:main");
|
|
@@ -1641,14 +1683,14 @@ var require_lib = __commonJS({
|
|
|
1641
1683
|
log(`findSync: called with "${input}" and "${base}" and "${fileName}"`);
|
|
1642
1684
|
base = !!base ? base : "";
|
|
1643
1685
|
const useFileName = !!fileName ? fileName : "package.json";
|
|
1644
|
-
const useBase = !!base ||
|
|
1645
|
-
let testPath =
|
|
1686
|
+
const useBase = !!base || path2.isAbsolute(base) ? base : process.cwd();
|
|
1687
|
+
let testPath = path2.resolve(useBase, input);
|
|
1646
1688
|
let wasRoot = false;
|
|
1647
1689
|
while (testPath) {
|
|
1648
|
-
if (testPath ===
|
|
1690
|
+
if (testPath === path2.parse(testPath).root) {
|
|
1649
1691
|
wasRoot = true;
|
|
1650
1692
|
}
|
|
1651
|
-
const testFile =
|
|
1693
|
+
const testFile = path2.resolve(testPath, useFileName);
|
|
1652
1694
|
log(`findSync: testing path "${testFile}"`);
|
|
1653
1695
|
const result = await statPathAsync(testFile);
|
|
1654
1696
|
if (!!result && result.isFile()) {
|
|
@@ -1659,7 +1701,7 @@ var require_lib = __commonJS({
|
|
|
1659
1701
|
if (wasRoot) {
|
|
1660
1702
|
break;
|
|
1661
1703
|
}
|
|
1662
|
-
testPath =
|
|
1704
|
+
testPath = path2.resolve(testPath, "..");
|
|
1663
1705
|
}
|
|
1664
1706
|
}
|
|
1665
1707
|
exports.findAsync = findAsync;
|
|
@@ -1668,14 +1710,14 @@ var require_lib = __commonJS({
|
|
|
1668
1710
|
log(`findSync: called with "${input}" and "${base}" and "${fileName}"`);
|
|
1669
1711
|
base = !!base ? base : "";
|
|
1670
1712
|
const useFileName = !!fileName ? fileName : "package.json";
|
|
1671
|
-
const useBase = !!base ||
|
|
1672
|
-
let testPath =
|
|
1713
|
+
const useBase = !!base || path2.isAbsolute(base) ? base : process.cwd();
|
|
1714
|
+
let testPath = path2.resolve(useBase, input);
|
|
1673
1715
|
let wasRoot = false;
|
|
1674
1716
|
while (testPath) {
|
|
1675
|
-
if (testPath ===
|
|
1717
|
+
if (testPath === path2.parse(testPath).root) {
|
|
1676
1718
|
wasRoot = true;
|
|
1677
1719
|
}
|
|
1678
|
-
const testFile =
|
|
1720
|
+
const testFile = path2.resolve(testPath, useFileName);
|
|
1679
1721
|
log(`findSync: testing path "${testFile}"`);
|
|
1680
1722
|
const result = statPathSync(testFile);
|
|
1681
1723
|
if (!!result && result.isFile()) {
|
|
@@ -1686,14 +1728,14 @@ var require_lib = __commonJS({
|
|
|
1686
1728
|
if (wasRoot) {
|
|
1687
1729
|
break;
|
|
1688
1730
|
}
|
|
1689
|
-
testPath =
|
|
1731
|
+
testPath = path2.resolve(testPath, "..");
|
|
1690
1732
|
}
|
|
1691
1733
|
}
|
|
1692
1734
|
exports.findSync = findSync;
|
|
1693
|
-
function statPathSync(
|
|
1735
|
+
function statPathSync(path3) {
|
|
1694
1736
|
var _a;
|
|
1695
1737
|
try {
|
|
1696
|
-
return (0, fs_1.statSync)(
|
|
1738
|
+
return (0, fs_1.statSync)(path3);
|
|
1697
1739
|
} catch (err) {
|
|
1698
1740
|
if (err != void 0 && err != null && ((_a = err) === null || _a === void 0 ? void 0 : _a.code) === "ENOENT") {
|
|
1699
1741
|
return void 0;
|
|
@@ -1701,8 +1743,8 @@ var require_lib = __commonJS({
|
|
|
1701
1743
|
throw err;
|
|
1702
1744
|
}
|
|
1703
1745
|
}
|
|
1704
|
-
async function statPathAsync(
|
|
1705
|
-
return fs_1.promises.stat(
|
|
1746
|
+
async function statPathAsync(path3) {
|
|
1747
|
+
return fs_1.promises.stat(path3).catch((err) => {
|
|
1706
1748
|
if (err.code === "ENOENT") {
|
|
1707
1749
|
return void 0;
|
|
1708
1750
|
}
|
|
@@ -1821,18 +1863,18 @@ https://typegoose.github.io/mongodb-memory-server/docs/guides/migration/migrate7
|
|
|
1821
1863
|
exports.AuthNotObjectError = AuthNotObjectError;
|
|
1822
1864
|
var InsufficientPermissionsError = class extends Error {
|
|
1823
1865
|
path;
|
|
1824
|
-
constructor(
|
|
1825
|
-
super(`File "${
|
|
1826
|
-
this.path =
|
|
1866
|
+
constructor(path2) {
|
|
1867
|
+
super(`File "${path2}" does not have the required Permissions, required Permissions: "--x"`);
|
|
1868
|
+
this.path = path2;
|
|
1827
1869
|
}
|
|
1828
1870
|
};
|
|
1829
1871
|
exports.InsufficientPermissionsError = InsufficientPermissionsError;
|
|
1830
1872
|
var BinaryNotFoundError = class extends Error {
|
|
1831
1873
|
path;
|
|
1832
1874
|
extra;
|
|
1833
|
-
constructor(
|
|
1834
|
-
super(`No Binary at path "${
|
|
1835
|
-
this.path =
|
|
1875
|
+
constructor(path2, extra = "") {
|
|
1876
|
+
super(`No Binary at path "${path2}" was found! (ENOENT)${extra}`);
|
|
1877
|
+
this.path = path2;
|
|
1836
1878
|
this.extra = extra;
|
|
1837
1879
|
}
|
|
1838
1880
|
};
|
|
@@ -3907,7 +3949,7 @@ var require_utils = __commonJS({
|
|
|
3907
3949
|
var fs_1 = __require("fs");
|
|
3908
3950
|
var errors_1 = require_errors();
|
|
3909
3951
|
var os_1 = __require("os");
|
|
3910
|
-
var
|
|
3952
|
+
var path2 = tslib_1.__importStar(__require("path"));
|
|
3911
3953
|
var crypto_1 = __require("crypto");
|
|
3912
3954
|
var semver = tslib_1.__importStar(require_semver2());
|
|
3913
3955
|
var log = (0, debug_1.default)("MongoMS:utils");
|
|
@@ -3991,16 +4033,16 @@ var require_utils = __commonJS({
|
|
|
3991
4033
|
...opts
|
|
3992
4034
|
};
|
|
3993
4035
|
}
|
|
3994
|
-
async function statPath(
|
|
3995
|
-
return fs_1.promises.stat(
|
|
4036
|
+
async function statPath(path3) {
|
|
4037
|
+
return fs_1.promises.stat(path3).catch((err) => {
|
|
3996
4038
|
if (["ENOENT", "EACCES"].includes(err.code)) {
|
|
3997
4039
|
return void 0;
|
|
3998
4040
|
}
|
|
3999
4041
|
throw err;
|
|
4000
4042
|
});
|
|
4001
4043
|
}
|
|
4002
|
-
async function pathExists(
|
|
4003
|
-
return !isNullOrUndefined(await statPath(
|
|
4044
|
+
async function pathExists(path3) {
|
|
4045
|
+
return !isNullOrUndefined(await statPath(path3));
|
|
4004
4046
|
}
|
|
4005
4047
|
var ManagerBase = class {
|
|
4006
4048
|
};
|
|
@@ -4008,27 +4050,27 @@ var require_utils = __commonJS({
|
|
|
4008
4050
|
var ManagerAdvanced = class extends ManagerBase {
|
|
4009
4051
|
};
|
|
4010
4052
|
exports.ManagerAdvanced = ManagerAdvanced;
|
|
4011
|
-
async function checkBinaryPermissions(
|
|
4053
|
+
async function checkBinaryPermissions(path3) {
|
|
4012
4054
|
try {
|
|
4013
|
-
await fs_1.promises.access(
|
|
4055
|
+
await fs_1.promises.access(path3, fs_1.constants.X_OK);
|
|
4014
4056
|
} catch (err) {
|
|
4015
4057
|
if (errorWithCode(err)) {
|
|
4016
4058
|
if (err.code === "EACCES") {
|
|
4017
|
-
throw new errors_1.InsufficientPermissionsError(
|
|
4059
|
+
throw new errors_1.InsufficientPermissionsError(path3);
|
|
4018
4060
|
}
|
|
4019
4061
|
if (err.code === "ENOENT") {
|
|
4020
|
-
throw new errors_1.BinaryNotFoundError(
|
|
4062
|
+
throw new errors_1.BinaryNotFoundError(path3);
|
|
4021
4063
|
}
|
|
4022
4064
|
}
|
|
4023
4065
|
throw err;
|
|
4024
4066
|
}
|
|
4025
4067
|
}
|
|
4026
|
-
async function mkdir(
|
|
4027
|
-
await fs_1.promises.mkdir(
|
|
4068
|
+
async function mkdir(path3) {
|
|
4069
|
+
await fs_1.promises.mkdir(path3, { recursive: true });
|
|
4028
4070
|
}
|
|
4029
4071
|
async function createTmpDir(prefix, atPath) {
|
|
4030
4072
|
const tmpPath = atPath ?? (0, os_1.tmpdir)();
|
|
4031
|
-
return fs_1.promises.mkdtemp(
|
|
4073
|
+
return fs_1.promises.mkdtemp(path2.join(tmpPath, prefix));
|
|
4032
4074
|
}
|
|
4033
4075
|
async function removeDir(dirPath) {
|
|
4034
4076
|
const stat = await statPath(dirPath);
|
|
@@ -4050,7 +4092,7 @@ var require_utils = __commonJS({
|
|
|
4050
4092
|
return md5(await fs_1.promises.readFile(file));
|
|
4051
4093
|
}
|
|
4052
4094
|
function lockfilePath(downloadDir, version) {
|
|
4053
|
-
return
|
|
4095
|
+
return path2.resolve(downloadDir, `${version}.lock`);
|
|
4054
4096
|
}
|
|
4055
4097
|
function getStorageEngine(storageEngine, coercedVersion) {
|
|
4056
4098
|
if (storageEngine === "ephemeralForTest" && semver.gte(coercedVersion, "7.0.0")) {
|
|
@@ -4084,7 +4126,7 @@ var require_resolveConfig = __commonJS({
|
|
|
4084
4126
|
var camelcase_1 = tslib_1.__importDefault(require_camelcase());
|
|
4085
4127
|
var new_find_package_json_1 = require_lib();
|
|
4086
4128
|
var debug_1 = tslib_1.__importDefault(require_src());
|
|
4087
|
-
var
|
|
4129
|
+
var path2 = tslib_1.__importStar(__require("path"));
|
|
4088
4130
|
var fs_1 = __require("fs");
|
|
4089
4131
|
var utils_1 = require_utils();
|
|
4090
4132
|
Symbol.dispose ??= /* @__PURE__ */ Symbol("Symbol.dispose");
|
|
@@ -4141,7 +4183,7 @@ var require_resolveConfig = __commonJS({
|
|
|
4141
4183
|
const config = readout?.config?.mongodbMemoryServer;
|
|
4142
4184
|
if (!(0, utils_1.isNullOrUndefined)(config) && Object.keys(config ?? {}).length > 0) {
|
|
4143
4185
|
log(`findPackageJson: Found package with non-empty config field at "${filename}"`);
|
|
4144
|
-
const filepath =
|
|
4186
|
+
const filepath = path2.dirname(filename);
|
|
4145
4187
|
packagejson = {
|
|
4146
4188
|
filePath: filepath,
|
|
4147
4189
|
config: processConfigOption(config, filepath)
|
|
@@ -4161,10 +4203,10 @@ var require_resolveConfig = __commonJS({
|
|
|
4161
4203
|
const ccDownloadDir = (0, camelcase_1.default)(ResolveConfigVariables.DOWNLOAD_DIR);
|
|
4162
4204
|
const ccSystemBinary = (0, camelcase_1.default)(ResolveConfigVariables.SYSTEM_BINARY);
|
|
4163
4205
|
if (ccDownloadDir in returnobj) {
|
|
4164
|
-
returnobj[ccDownloadDir] =
|
|
4206
|
+
returnobj[ccDownloadDir] = path2.resolve(filepath, returnobj[ccDownloadDir]);
|
|
4165
4207
|
}
|
|
4166
4208
|
if (ccSystemBinary in returnobj) {
|
|
4167
|
-
returnobj[ccSystemBinary] =
|
|
4209
|
+
returnobj[ccSystemBinary] = path2.resolve(filepath, returnobj[ccSystemBinary]);
|
|
4168
4210
|
}
|
|
4169
4211
|
return returnobj;
|
|
4170
4212
|
}
|
|
@@ -4307,7 +4349,7 @@ var require_debug2 = __commonJS({
|
|
|
4307
4349
|
var require_follow_redirects = __commonJS({
|
|
4308
4350
|
"node_modules/follow-redirects/index.js"(exports, module) {
|
|
4309
4351
|
var url = __require("url");
|
|
4310
|
-
var
|
|
4352
|
+
var URL2 = url.URL;
|
|
4311
4353
|
var http = __require("http");
|
|
4312
4354
|
var https = __require("https");
|
|
4313
4355
|
var Writable = __require("stream").Writable;
|
|
@@ -4323,7 +4365,7 @@ var require_follow_redirects = __commonJS({
|
|
|
4323
4365
|
})();
|
|
4324
4366
|
var useNativeURL = false;
|
|
4325
4367
|
try {
|
|
4326
|
-
assert(new
|
|
4368
|
+
assert(new URL2(""));
|
|
4327
4369
|
} catch (error) {
|
|
4328
4370
|
useNativeURL = error.code === "ERR_INVALID_URL";
|
|
4329
4371
|
}
|
|
@@ -4712,7 +4754,7 @@ var require_follow_redirects = __commonJS({
|
|
|
4712
4754
|
function parseUrl(input) {
|
|
4713
4755
|
var parsed;
|
|
4714
4756
|
if (useNativeURL) {
|
|
4715
|
-
parsed = new
|
|
4757
|
+
parsed = new URL2(input);
|
|
4716
4758
|
} else {
|
|
4717
4759
|
parsed = validateUrl(url.parse(input));
|
|
4718
4760
|
if (!isString(parsed.protocol)) {
|
|
@@ -4722,7 +4764,7 @@ var require_follow_redirects = __commonJS({
|
|
|
4722
4764
|
return parsed;
|
|
4723
4765
|
}
|
|
4724
4766
|
function resolveUrl(relative, base) {
|
|
4725
|
-
return useNativeURL ? new
|
|
4767
|
+
return useNativeURL ? new URL2(relative, base) : parseUrl(url.resolve(base, relative));
|
|
4726
4768
|
}
|
|
4727
4769
|
function validateUrl(input) {
|
|
4728
4770
|
if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
|
|
@@ -4804,7 +4846,7 @@ var require_follow_redirects = __commonJS({
|
|
|
4804
4846
|
return typeof value === "object" && "length" in value;
|
|
4805
4847
|
}
|
|
4806
4848
|
function isURL(value) {
|
|
4807
|
-
return
|
|
4849
|
+
return URL2 && value instanceof URL2;
|
|
4808
4850
|
}
|
|
4809
4851
|
function escapeRegex(regex) {
|
|
4810
4852
|
return regex.replace(/[\]\\/()*+?.$]/g, "\\$&");
|
|
@@ -7197,7 +7239,7 @@ var require_pend = __commonJS({
|
|
|
7197
7239
|
// node_modules/yauzl/fd-slicer.js
|
|
7198
7240
|
var require_fd_slicer = __commonJS({
|
|
7199
7241
|
"node_modules/yauzl/fd-slicer.js"(exports) {
|
|
7200
|
-
var
|
|
7242
|
+
var fs2 = __require("fs");
|
|
7201
7243
|
var util = __require("util");
|
|
7202
7244
|
var stream = __require("stream");
|
|
7203
7245
|
var Readable = stream.Readable;
|
|
@@ -7222,7 +7264,7 @@ var require_fd_slicer = __commonJS({
|
|
|
7222
7264
|
FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
|
|
7223
7265
|
var self = this;
|
|
7224
7266
|
self.pend.go(function(cb) {
|
|
7225
|
-
|
|
7267
|
+
fs2.read(self.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
|
|
7226
7268
|
cb();
|
|
7227
7269
|
callback(err, bytesRead, buffer2);
|
|
7228
7270
|
});
|
|
@@ -7231,7 +7273,7 @@ var require_fd_slicer = __commonJS({
|
|
|
7231
7273
|
FdSlicer.prototype.write = function(buffer, offset, length, position, callback) {
|
|
7232
7274
|
var self = this;
|
|
7233
7275
|
self.pend.go(function(cb) {
|
|
7234
|
-
|
|
7276
|
+
fs2.write(self.fd, buffer, offset, length, position, function(err, written, buffer2) {
|
|
7235
7277
|
cb();
|
|
7236
7278
|
callback(err, written, buffer2);
|
|
7237
7279
|
});
|
|
@@ -7252,7 +7294,7 @@ var require_fd_slicer = __commonJS({
|
|
|
7252
7294
|
if (self.refCount > 0) return;
|
|
7253
7295
|
if (self.refCount < 0) throw new Error("invalid unref");
|
|
7254
7296
|
if (self.autoClose) {
|
|
7255
|
-
|
|
7297
|
+
fs2.close(self.fd, onCloseDone);
|
|
7256
7298
|
}
|
|
7257
7299
|
function onCloseDone(err) {
|
|
7258
7300
|
if (err) {
|
|
@@ -7289,7 +7331,7 @@ var require_fd_slicer = __commonJS({
|
|
|
7289
7331
|
self.context.pend.go(function(cb) {
|
|
7290
7332
|
if (self.destroyed) return cb();
|
|
7291
7333
|
var buffer = Buffer.allocUnsafe(toRead);
|
|
7292
|
-
|
|
7334
|
+
fs2.read(self.context.fd, buffer, 0, toRead, self.pos, function(err, bytesRead) {
|
|
7293
7335
|
if (err) {
|
|
7294
7336
|
self.destroy(err);
|
|
7295
7337
|
} else if (bytesRead === 0) {
|
|
@@ -7336,7 +7378,7 @@ var require_fd_slicer = __commonJS({
|
|
|
7336
7378
|
}
|
|
7337
7379
|
self.context.pend.go(function(cb) {
|
|
7338
7380
|
if (self.destroyed) return cb();
|
|
7339
|
-
|
|
7381
|
+
fs2.write(self.context.fd, buffer, 0, buffer.length, self.pos, function(err2, bytes) {
|
|
7340
7382
|
if (err2) {
|
|
7341
7383
|
self.destroy();
|
|
7342
7384
|
cb();
|
|
@@ -7774,7 +7816,7 @@ var require_buffer_crc32 = __commonJS({
|
|
|
7774
7816
|
// node_modules/yauzl/index.js
|
|
7775
7817
|
var require_yauzl = __commonJS({
|
|
7776
7818
|
"node_modules/yauzl/index.js"(exports) {
|
|
7777
|
-
var
|
|
7819
|
+
var fs2 = __require("fs");
|
|
7778
7820
|
var zlib = __require("zlib");
|
|
7779
7821
|
var fd_slicer = require_fd_slicer();
|
|
7780
7822
|
var crc32 = require_buffer_crc32();
|
|
@@ -7795,7 +7837,7 @@ var require_yauzl = __commonJS({
|
|
|
7795
7837
|
exports.Entry = Entry;
|
|
7796
7838
|
exports.LocalFileHeader = LocalFileHeader;
|
|
7797
7839
|
exports.RandomAccessReader = RandomAccessReader;
|
|
7798
|
-
function open(
|
|
7840
|
+
function open(path2, options2, callback) {
|
|
7799
7841
|
if (typeof options2 === "function") {
|
|
7800
7842
|
callback = options2;
|
|
7801
7843
|
options2 = null;
|
|
@@ -7807,10 +7849,10 @@ var require_yauzl = __commonJS({
|
|
|
7807
7849
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
7808
7850
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
7809
7851
|
if (callback == null) callback = defaultCallback;
|
|
7810
|
-
|
|
7852
|
+
fs2.open(path2, "r", function(err, fd) {
|
|
7811
7853
|
if (err) return callback(err);
|
|
7812
7854
|
fromFd(fd, options2, function(err2, zipfile) {
|
|
7813
|
-
if (err2)
|
|
7855
|
+
if (err2) fs2.close(fd, defaultCallback);
|
|
7814
7856
|
callback(err2, zipfile);
|
|
7815
7857
|
});
|
|
7816
7858
|
});
|
|
@@ -7827,7 +7869,7 @@ var require_yauzl = __commonJS({
|
|
|
7827
7869
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
7828
7870
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
7829
7871
|
if (callback == null) callback = defaultCallback;
|
|
7830
|
-
|
|
7872
|
+
fs2.fstat(fd, function(err, stats) {
|
|
7831
7873
|
if (err) return callback(err);
|
|
7832
7874
|
var reader = fd_slicer.createFromFd(fd, { autoClose: true });
|
|
7833
7875
|
fromRandomAccessReader(reader, stats.size, options2, callback);
|
|
@@ -8610,15 +8652,15 @@ var require_getos = __commonJS({
|
|
|
8610
8652
|
id_like: input.match(OSRegex.id_like)?.[1].toLocaleLowerCase().split(" ")
|
|
8611
8653
|
};
|
|
8612
8654
|
}
|
|
8613
|
-
async function tryReleaseFile(
|
|
8655
|
+
async function tryReleaseFile(path2, parser) {
|
|
8614
8656
|
try {
|
|
8615
|
-
const output = await fs_1.promises.readFile(
|
|
8657
|
+
const output = await fs_1.promises.readFile(path2);
|
|
8616
8658
|
return parser(output.toString());
|
|
8617
8659
|
} catch (err) {
|
|
8618
8660
|
if ((0, utils_1.errorWithCode)(err) && !["ENOENT", "EACCES"].includes(err.code)) {
|
|
8619
8661
|
throw err;
|
|
8620
8662
|
}
|
|
8621
|
-
log(`tryReleaseFile: "${
|
|
8663
|
+
log(`tryReleaseFile: "${path2}" does not exist`);
|
|
8622
8664
|
return void 0;
|
|
8623
8665
|
}
|
|
8624
8666
|
}
|
|
@@ -9591,11 +9633,11 @@ var require_dist2 = __commonJS({
|
|
|
9591
9633
|
// node_modules/commondir/index.js
|
|
9592
9634
|
var require_commondir = __commonJS({
|
|
9593
9635
|
"node_modules/commondir/index.js"(exports, module) {
|
|
9594
|
-
var
|
|
9636
|
+
var path2 = __require("path");
|
|
9595
9637
|
module.exports = function(basedir, relfiles) {
|
|
9596
9638
|
if (relfiles) {
|
|
9597
9639
|
var files = relfiles.map(function(r) {
|
|
9598
|
-
return
|
|
9640
|
+
return path2.resolve(basedir, r);
|
|
9599
9641
|
});
|
|
9600
9642
|
} else {
|
|
9601
9643
|
var files = basedir;
|
|
@@ -9718,12 +9760,12 @@ var require_p_locate = __commonJS({
|
|
|
9718
9760
|
// node_modules/locate-path/index.js
|
|
9719
9761
|
var require_locate_path = __commonJS({
|
|
9720
9762
|
"node_modules/locate-path/index.js"(exports, module) {
|
|
9721
|
-
var
|
|
9722
|
-
var
|
|
9763
|
+
var path2 = __require("path");
|
|
9764
|
+
var fs2 = __require("fs");
|
|
9723
9765
|
var { promisify } = __require("util");
|
|
9724
9766
|
var pLocate = require_p_locate();
|
|
9725
|
-
var fsStat = promisify(
|
|
9726
|
-
var fsLStat = promisify(
|
|
9767
|
+
var fsStat = promisify(fs2.stat);
|
|
9768
|
+
var fsLStat = promisify(fs2.lstat);
|
|
9727
9769
|
var typeMappings = {
|
|
9728
9770
|
directory: "isDirectory",
|
|
9729
9771
|
file: "isFile"
|
|
@@ -9746,7 +9788,7 @@ var require_locate_path = __commonJS({
|
|
|
9746
9788
|
const statFn = options2.allowSymlinks ? fsStat : fsLStat;
|
|
9747
9789
|
return pLocate(paths, async (path_) => {
|
|
9748
9790
|
try {
|
|
9749
|
-
const stat = await statFn(
|
|
9791
|
+
const stat = await statFn(path2.resolve(options2.cwd, path_));
|
|
9750
9792
|
return matchType(options2.type, stat);
|
|
9751
9793
|
} catch (_) {
|
|
9752
9794
|
return false;
|
|
@@ -9761,10 +9803,10 @@ var require_locate_path = __commonJS({
|
|
|
9761
9803
|
...options2
|
|
9762
9804
|
};
|
|
9763
9805
|
checkType(options2);
|
|
9764
|
-
const statFn = options2.allowSymlinks ?
|
|
9806
|
+
const statFn = options2.allowSymlinks ? fs2.statSync : fs2.lstatSync;
|
|
9765
9807
|
for (const path_ of paths) {
|
|
9766
9808
|
try {
|
|
9767
|
-
const stat = statFn(
|
|
9809
|
+
const stat = statFn(path2.resolve(options2.cwd, path_));
|
|
9768
9810
|
if (matchType(options2.type, stat)) {
|
|
9769
9811
|
return path_;
|
|
9770
9812
|
}
|
|
@@ -9778,20 +9820,20 @@ var require_locate_path = __commonJS({
|
|
|
9778
9820
|
// node_modules/path-exists/index.js
|
|
9779
9821
|
var require_path_exists = __commonJS({
|
|
9780
9822
|
"node_modules/path-exists/index.js"(exports, module) {
|
|
9781
|
-
var
|
|
9823
|
+
var fs2 = __require("fs");
|
|
9782
9824
|
var { promisify } = __require("util");
|
|
9783
|
-
var pAccess = promisify(
|
|
9784
|
-
module.exports = async (
|
|
9825
|
+
var pAccess = promisify(fs2.access);
|
|
9826
|
+
module.exports = async (path2) => {
|
|
9785
9827
|
try {
|
|
9786
|
-
await pAccess(
|
|
9828
|
+
await pAccess(path2);
|
|
9787
9829
|
return true;
|
|
9788
9830
|
} catch (_) {
|
|
9789
9831
|
return false;
|
|
9790
9832
|
}
|
|
9791
9833
|
};
|
|
9792
|
-
module.exports.sync = (
|
|
9834
|
+
module.exports.sync = (path2) => {
|
|
9793
9835
|
try {
|
|
9794
|
-
|
|
9836
|
+
fs2.accessSync(path2);
|
|
9795
9837
|
return true;
|
|
9796
9838
|
} catch (_) {
|
|
9797
9839
|
return false;
|
|
@@ -9803,13 +9845,13 @@ var require_path_exists = __commonJS({
|
|
|
9803
9845
|
// node_modules/find-up/index.js
|
|
9804
9846
|
var require_find_up = __commonJS({
|
|
9805
9847
|
"node_modules/find-up/index.js"(exports, module) {
|
|
9806
|
-
var
|
|
9848
|
+
var path2 = __require("path");
|
|
9807
9849
|
var locatePath = require_locate_path();
|
|
9808
9850
|
var pathExists = require_path_exists();
|
|
9809
9851
|
var stop = /* @__PURE__ */ Symbol("findUp.stop");
|
|
9810
9852
|
module.exports = async (name, options2 = {}) => {
|
|
9811
|
-
let directory =
|
|
9812
|
-
const { root } =
|
|
9853
|
+
let directory = path2.resolve(options2.cwd || "");
|
|
9854
|
+
const { root } = path2.parse(directory);
|
|
9813
9855
|
const paths = [].concat(name);
|
|
9814
9856
|
const runMatcher = async (locateOptions) => {
|
|
9815
9857
|
if (typeof name !== "function") {
|
|
@@ -9827,17 +9869,17 @@ var require_find_up = __commonJS({
|
|
|
9827
9869
|
return;
|
|
9828
9870
|
}
|
|
9829
9871
|
if (foundPath) {
|
|
9830
|
-
return
|
|
9872
|
+
return path2.resolve(directory, foundPath);
|
|
9831
9873
|
}
|
|
9832
9874
|
if (directory === root) {
|
|
9833
9875
|
return;
|
|
9834
9876
|
}
|
|
9835
|
-
directory =
|
|
9877
|
+
directory = path2.dirname(directory);
|
|
9836
9878
|
}
|
|
9837
9879
|
};
|
|
9838
9880
|
module.exports.sync = (name, options2 = {}) => {
|
|
9839
|
-
let directory =
|
|
9840
|
-
const { root } =
|
|
9881
|
+
let directory = path2.resolve(options2.cwd || "");
|
|
9882
|
+
const { root } = path2.parse(directory);
|
|
9841
9883
|
const paths = [].concat(name);
|
|
9842
9884
|
const runMatcher = (locateOptions) => {
|
|
9843
9885
|
if (typeof name !== "function") {
|
|
@@ -9855,12 +9897,12 @@ var require_find_up = __commonJS({
|
|
|
9855
9897
|
return;
|
|
9856
9898
|
}
|
|
9857
9899
|
if (foundPath) {
|
|
9858
|
-
return
|
|
9900
|
+
return path2.resolve(directory, foundPath);
|
|
9859
9901
|
}
|
|
9860
9902
|
if (directory === root) {
|
|
9861
9903
|
return;
|
|
9862
9904
|
}
|
|
9863
|
-
directory =
|
|
9905
|
+
directory = path2.dirname(directory);
|
|
9864
9906
|
}
|
|
9865
9907
|
};
|
|
9866
9908
|
module.exports.exists = pathExists;
|
|
@@ -9872,17 +9914,17 @@ var require_find_up = __commonJS({
|
|
|
9872
9914
|
// node_modules/pkg-dir/index.js
|
|
9873
9915
|
var require_pkg_dir = __commonJS({
|
|
9874
9916
|
"node_modules/pkg-dir/index.js"(exports, module) {
|
|
9875
|
-
var
|
|
9917
|
+
var path2 = __require("path");
|
|
9876
9918
|
var findUp = require_find_up();
|
|
9877
9919
|
var pkgDir = async (cwd) => {
|
|
9878
9920
|
const filePath = await findUp("package.json", { cwd });
|
|
9879
|
-
return filePath &&
|
|
9921
|
+
return filePath && path2.dirname(filePath);
|
|
9880
9922
|
};
|
|
9881
9923
|
module.exports = pkgDir;
|
|
9882
9924
|
module.exports.default = pkgDir;
|
|
9883
9925
|
module.exports.sync = (cwd) => {
|
|
9884
9926
|
const filePath = findUp.sync("package.json", { cwd });
|
|
9885
|
-
return filePath &&
|
|
9927
|
+
return filePath && path2.dirname(filePath);
|
|
9886
9928
|
};
|
|
9887
9929
|
}
|
|
9888
9930
|
});
|
|
@@ -11046,14 +11088,14 @@ var require_semver3 = __commonJS({
|
|
|
11046
11088
|
// node_modules/make-dir/index.js
|
|
11047
11089
|
var require_make_dir = __commonJS({
|
|
11048
11090
|
"node_modules/make-dir/index.js"(exports, module) {
|
|
11049
|
-
var
|
|
11050
|
-
var
|
|
11091
|
+
var fs2 = __require("fs");
|
|
11092
|
+
var path2 = __require("path");
|
|
11051
11093
|
var { promisify } = __require("util");
|
|
11052
11094
|
var semver = require_semver3();
|
|
11053
11095
|
var useNativeRecursiveOption = semver.satisfies(process.version, ">=10.12.0");
|
|
11054
11096
|
var checkPath = (pth) => {
|
|
11055
11097
|
if (process.platform === "win32") {
|
|
11056
|
-
const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(
|
|
11098
|
+
const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path2.parse(pth).root, ""));
|
|
11057
11099
|
if (pathHasInvalidWinCharacters) {
|
|
11058
11100
|
const error = new Error(`Path contains invalid characters: ${pth}`);
|
|
11059
11101
|
error.code = "EINVAL";
|
|
@@ -11064,7 +11106,7 @@ var require_make_dir = __commonJS({
|
|
|
11064
11106
|
var processOptions = (options2) => {
|
|
11065
11107
|
const defaults = {
|
|
11066
11108
|
mode: 511,
|
|
11067
|
-
fs
|
|
11109
|
+
fs: fs2
|
|
11068
11110
|
};
|
|
11069
11111
|
return {
|
|
11070
11112
|
...defaults,
|
|
@@ -11084,8 +11126,8 @@ var require_make_dir = __commonJS({
|
|
|
11084
11126
|
options2 = processOptions(options2);
|
|
11085
11127
|
const mkdir = promisify(options2.fs.mkdir);
|
|
11086
11128
|
const stat = promisify(options2.fs.stat);
|
|
11087
|
-
if (useNativeRecursiveOption && options2.fs.mkdir ===
|
|
11088
|
-
const pth =
|
|
11129
|
+
if (useNativeRecursiveOption && options2.fs.mkdir === fs2.mkdir) {
|
|
11130
|
+
const pth = path2.resolve(input);
|
|
11089
11131
|
await mkdir(pth, {
|
|
11090
11132
|
mode: options2.mode,
|
|
11091
11133
|
recursive: true
|
|
@@ -11101,13 +11143,13 @@ var require_make_dir = __commonJS({
|
|
|
11101
11143
|
throw error;
|
|
11102
11144
|
}
|
|
11103
11145
|
if (error.code === "ENOENT") {
|
|
11104
|
-
if (
|
|
11146
|
+
if (path2.dirname(pth) === pth) {
|
|
11105
11147
|
throw permissionError(pth);
|
|
11106
11148
|
}
|
|
11107
11149
|
if (error.message.includes("null bytes")) {
|
|
11108
11150
|
throw error;
|
|
11109
11151
|
}
|
|
11110
|
-
await make(
|
|
11152
|
+
await make(path2.dirname(pth));
|
|
11111
11153
|
return make(pth);
|
|
11112
11154
|
}
|
|
11113
11155
|
try {
|
|
@@ -11121,15 +11163,15 @@ var require_make_dir = __commonJS({
|
|
|
11121
11163
|
return pth;
|
|
11122
11164
|
}
|
|
11123
11165
|
};
|
|
11124
|
-
return make(
|
|
11166
|
+
return make(path2.resolve(input));
|
|
11125
11167
|
};
|
|
11126
11168
|
module.exports = makeDir;
|
|
11127
11169
|
module.exports.sync = (input, options2) => {
|
|
11128
11170
|
checkPath(input);
|
|
11129
11171
|
options2 = processOptions(options2);
|
|
11130
|
-
if (useNativeRecursiveOption && options2.fs.mkdirSync ===
|
|
11131
|
-
const pth =
|
|
11132
|
-
|
|
11172
|
+
if (useNativeRecursiveOption && options2.fs.mkdirSync === fs2.mkdirSync) {
|
|
11173
|
+
const pth = path2.resolve(input);
|
|
11174
|
+
fs2.mkdirSync(pth, {
|
|
11133
11175
|
mode: options2.mode,
|
|
11134
11176
|
recursive: true
|
|
11135
11177
|
});
|
|
@@ -11143,13 +11185,13 @@ var require_make_dir = __commonJS({
|
|
|
11143
11185
|
throw error;
|
|
11144
11186
|
}
|
|
11145
11187
|
if (error.code === "ENOENT") {
|
|
11146
|
-
if (
|
|
11188
|
+
if (path2.dirname(pth) === pth) {
|
|
11147
11189
|
throw permissionError(pth);
|
|
11148
11190
|
}
|
|
11149
11191
|
if (error.message.includes("null bytes")) {
|
|
11150
11192
|
throw error;
|
|
11151
11193
|
}
|
|
11152
|
-
make(
|
|
11194
|
+
make(path2.dirname(pth));
|
|
11153
11195
|
return make(pth);
|
|
11154
11196
|
}
|
|
11155
11197
|
try {
|
|
@@ -11162,7 +11204,7 @@ var require_make_dir = __commonJS({
|
|
|
11162
11204
|
}
|
|
11163
11205
|
return pth;
|
|
11164
11206
|
};
|
|
11165
|
-
return make(
|
|
11207
|
+
return make(path2.resolve(input));
|
|
11166
11208
|
};
|
|
11167
11209
|
}
|
|
11168
11210
|
});
|
|
@@ -11170,15 +11212,15 @@ var require_make_dir = __commonJS({
|
|
|
11170
11212
|
// node_modules/find-cache-dir/index.js
|
|
11171
11213
|
var require_find_cache_dir = __commonJS({
|
|
11172
11214
|
"node_modules/find-cache-dir/index.js"(exports, module) {
|
|
11173
|
-
var
|
|
11174
|
-
var
|
|
11215
|
+
var path2 = __require("path");
|
|
11216
|
+
var fs2 = __require("fs");
|
|
11175
11217
|
var commonDir = require_commondir();
|
|
11176
11218
|
var pkgDir = require_pkg_dir();
|
|
11177
11219
|
var makeDir = require_make_dir();
|
|
11178
11220
|
var { env, cwd } = process;
|
|
11179
|
-
var isWritable = (
|
|
11221
|
+
var isWritable = (path3) => {
|
|
11180
11222
|
try {
|
|
11181
|
-
|
|
11223
|
+
fs2.accessSync(path3, fs2.constants.W_OK);
|
|
11182
11224
|
return true;
|
|
11183
11225
|
} catch (_) {
|
|
11184
11226
|
return false;
|
|
@@ -11189,20 +11231,20 @@ var require_find_cache_dir = __commonJS({
|
|
|
11189
11231
|
makeDir.sync(directory);
|
|
11190
11232
|
}
|
|
11191
11233
|
if (options2.thunk) {
|
|
11192
|
-
return (...arguments_) =>
|
|
11234
|
+
return (...arguments_) => path2.join(directory, ...arguments_);
|
|
11193
11235
|
}
|
|
11194
11236
|
return directory;
|
|
11195
11237
|
}
|
|
11196
11238
|
function getNodeModuleDirectory(directory) {
|
|
11197
|
-
const nodeModules =
|
|
11198
|
-
if (!isWritable(nodeModules) && (
|
|
11239
|
+
const nodeModules = path2.join(directory, "node_modules");
|
|
11240
|
+
if (!isWritable(nodeModules) && (fs2.existsSync(nodeModules) || !isWritable(path2.join(directory)))) {
|
|
11199
11241
|
return;
|
|
11200
11242
|
}
|
|
11201
11243
|
return nodeModules;
|
|
11202
11244
|
}
|
|
11203
11245
|
module.exports = (options2 = {}) => {
|
|
11204
11246
|
if (env.CACHE_DIR && !["true", "false", "1", "0"].includes(env.CACHE_DIR)) {
|
|
11205
|
-
return useDirectory(
|
|
11247
|
+
return useDirectory(path2.join(env.CACHE_DIR, options2.name), options2);
|
|
11206
11248
|
}
|
|
11207
11249
|
let { cwd: directory = cwd() } = options2;
|
|
11208
11250
|
if (options2.files) {
|
|
@@ -11216,7 +11258,7 @@ var require_find_cache_dir = __commonJS({
|
|
|
11216
11258
|
if (!nodeModules) {
|
|
11217
11259
|
return void 0;
|
|
11218
11260
|
}
|
|
11219
|
-
return useDirectory(
|
|
11261
|
+
return useDirectory(path2.join(directory, "node_modules", ".cache", options2.name), options2);
|
|
11220
11262
|
};
|
|
11221
11263
|
}
|
|
11222
11264
|
});
|
|
@@ -11230,7 +11272,7 @@ var require_DryMongoBinary = __commonJS({
|
|
|
11230
11272
|
var debug_1 = tslib_1.__importDefault(require_src());
|
|
11231
11273
|
var resolveConfig_1 = require_resolveConfig();
|
|
11232
11274
|
var utils_1 = require_utils();
|
|
11233
|
-
var
|
|
11275
|
+
var path2 = tslib_1.__importStar(__require("path"));
|
|
11234
11276
|
var os_1 = __require("os");
|
|
11235
11277
|
var find_cache_dir_1 = tslib_1.__importDefault(require_find_cache_dir());
|
|
11236
11278
|
var getos_1 = require_getos();
|
|
@@ -11251,7 +11293,7 @@ var require_DryMongoBinary = __commonJS({
|
|
|
11251
11293
|
const useOpts = await this.generateOptions(opts);
|
|
11252
11294
|
if (useOpts.systemBinary) {
|
|
11253
11295
|
log(`locateBinary: env "SYSTEM_BINARY" was provided with value: "${useOpts.systemBinary}"`);
|
|
11254
|
-
const systemReturn = await this.getSystemPath(
|
|
11296
|
+
const systemReturn = await this.getSystemPath(path2.resolve(useOpts.systemBinary));
|
|
11255
11297
|
if ((0, utils_1.isNullOrUndefined)(systemReturn)) {
|
|
11256
11298
|
throw new errors_1.BinaryNotFoundError(useOpts.systemBinary, " (systemBinary)");
|
|
11257
11299
|
}
|
|
@@ -11268,7 +11310,7 @@ var require_DryMongoBinary = __commonJS({
|
|
|
11268
11310
|
log("locateBinary: could not find a existing binary");
|
|
11269
11311
|
return void 0;
|
|
11270
11312
|
}
|
|
11271
|
-
if (returnValue[0] && await (0, utils_1.pathExists)((0, utils_1.lockfilePath)(
|
|
11313
|
+
if (returnValue[0] && await (0, utils_1.pathExists)((0, utils_1.lockfilePath)(path2.dirname(returnValue[1]), useOpts.version))) {
|
|
11272
11314
|
log("locateBinary: binary found, but also a download-lock, trying to resolve lock");
|
|
11273
11315
|
return void 0;
|
|
11274
11316
|
}
|
|
@@ -11299,7 +11341,7 @@ var require_DryMongoBinary = __commonJS({
|
|
|
11299
11341
|
arch: ensuredOpts.arch || (0, os_1.arch)(),
|
|
11300
11342
|
systemBinary: (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.SYSTEM_BINARY) || ensuredOpts.systemBinary || ""
|
|
11301
11343
|
};
|
|
11302
|
-
final.downloadDir =
|
|
11344
|
+
final.downloadDir = path2.dirname((await this.generateDownloadPath(final))[1]);
|
|
11303
11345
|
if ((0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.ARCHIVE_NAME) || (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.DOWNLOAD_URL)) {
|
|
11304
11346
|
const input = (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.DOWNLOAD_URL) || (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.ARCHIVE_NAME);
|
|
11305
11347
|
log(`generateOptions: ARCHIVE_NAME or DOWNLOAD_URL defined, generating options based on that (input: "${input}")`);
|
|
@@ -11346,7 +11388,7 @@ var require_DryMongoBinary = __commonJS({
|
|
|
11346
11388
|
let binaryName;
|
|
11347
11389
|
if ((0, resolveConfig_1.envToBool)((0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.USE_ARCHIVE_NAME_FOR_BINARY_NAME))) {
|
|
11348
11390
|
const archiveName = await new MongoBinaryDownloadUrl_1.MongoBinaryDownloadUrl(opts).getArchiveName();
|
|
11349
|
-
binaryName =
|
|
11391
|
+
binaryName = path2.parse(archiveName).name;
|
|
11350
11392
|
} else {
|
|
11351
11393
|
const addExe = opts.platform === "win32" ? ".exe" : "";
|
|
11352
11394
|
const dist = (0, getos_1.isLinuxOS)(opts.os) ? opts.os.dist : opts.os.os;
|
|
@@ -11359,7 +11401,7 @@ var require_DryMongoBinary = __commonJS({
|
|
|
11359
11401
|
*/
|
|
11360
11402
|
static combineBinaryName(basePath, binaryName) {
|
|
11361
11403
|
log("combineBinaryName");
|
|
11362
|
-
return
|
|
11404
|
+
return path2.resolve(basePath, binaryName);
|
|
11363
11405
|
}
|
|
11364
11406
|
/**
|
|
11365
11407
|
* Probe if the provided "systemBinary" is an existing path
|
|
@@ -11394,11 +11436,11 @@ ${err instanceof Error ? err.message : err}`);
|
|
|
11394
11436
|
};
|
|
11395
11437
|
const binaryName = await this.getBinaryName(opts);
|
|
11396
11438
|
let nodeModulesDLDir = process.env["INIT_CWD"] || process.cwd();
|
|
11397
|
-
while (nodeModulesDLDir.includes(`node_modules${
|
|
11398
|
-
nodeModulesDLDir =
|
|
11439
|
+
while (nodeModulesDLDir.includes(`node_modules${path2.sep}mongodb-memory-server`)) {
|
|
11440
|
+
nodeModulesDLDir = path2.resolve(nodeModulesDLDir, "..", "..");
|
|
11399
11441
|
}
|
|
11400
11442
|
const configPackagePath = (0, resolveConfig_1.packageJsonPath)();
|
|
11401
|
-
if (configPackagePath && await (0, utils_1.pathExists)(
|
|
11443
|
+
if (configPackagePath && await (0, utils_1.pathExists)(path2.resolve(configPackagePath, "node_modules"))) {
|
|
11402
11444
|
nodeModulesDLDir = configPackagePath;
|
|
11403
11445
|
}
|
|
11404
11446
|
const tmpModulesCache = (0, find_cache_dir_1.default)({
|
|
@@ -11406,16 +11448,16 @@ ${err instanceof Error ? err.message : err}`);
|
|
|
11406
11448
|
cwd: nodeModulesDLDir
|
|
11407
11449
|
});
|
|
11408
11450
|
if (!(0, utils_1.isNullOrUndefined)(tmpModulesCache)) {
|
|
11409
|
-
final.modulesCache = this.combineBinaryName(
|
|
11451
|
+
final.modulesCache = this.combineBinaryName(path2.resolve(tmpModulesCache), binaryName);
|
|
11410
11452
|
}
|
|
11411
|
-
const homeCache =
|
|
11453
|
+
const homeCache = path2.resolve(this.homedir(), ".cache/mongodb-binaries");
|
|
11412
11454
|
final.homeCache = this.combineBinaryName(homeCache, binaryName);
|
|
11413
11455
|
const resolveConfigValue = opts.downloadDir || (0, resolveConfig_1.resolveConfig)(resolveConfig_1.ResolveConfigVariables.DOWNLOAD_DIR);
|
|
11414
11456
|
if (!(0, utils_1.isNullOrUndefined)(resolveConfigValue) && resolveConfigValue.length > 0) {
|
|
11415
11457
|
log(`generatePaths: resolveConfigValue is not empty`);
|
|
11416
11458
|
final.resolveConfig = this.combineBinaryName(resolveConfigValue, binaryName);
|
|
11417
11459
|
}
|
|
11418
|
-
final.relative = this.combineBinaryName(
|
|
11460
|
+
final.relative = this.combineBinaryName(path2.resolve(process.cwd(), "mongodb-binaries"), binaryName);
|
|
11419
11461
|
return final;
|
|
11420
11462
|
}
|
|
11421
11463
|
/**
|
|
@@ -12378,7 +12420,7 @@ var require_lockfile = __commonJS({
|
|
|
12378
12420
|
var events_1 = __require("events");
|
|
12379
12421
|
var utils = tslib_1.__importStar(require_utils());
|
|
12380
12422
|
var debug_1 = tslib_1.__importDefault(require_src());
|
|
12381
|
-
var
|
|
12423
|
+
var path2 = tslib_1.__importStar(__require("path"));
|
|
12382
12424
|
var fs_1 = __require("fs");
|
|
12383
12425
|
var async_mutex_1 = require_lib2();
|
|
12384
12426
|
var errors_1 = require_errors();
|
|
@@ -12418,7 +12460,7 @@ var require_lockfile = __commonJS({
|
|
|
12418
12460
|
static async lock(file) {
|
|
12419
12461
|
await utils.ensureAsync();
|
|
12420
12462
|
log(`lock: Locking file "${file}"`);
|
|
12421
|
-
const useFile =
|
|
12463
|
+
const useFile = path2.resolve(file.trim());
|
|
12422
12464
|
utils.assertion(useFile.length > 0, new Error("Provided Path for lock file is length of 0"));
|
|
12423
12465
|
const status = await this.checkLock(useFile);
|
|
12424
12466
|
switch (status) {
|
|
@@ -12519,7 +12561,7 @@ var require_lockfile = __commonJS({
|
|
|
12519
12561
|
log(`createLock: Map already has file "${file}"`);
|
|
12520
12562
|
throw new RepeatError(true);
|
|
12521
12563
|
}
|
|
12522
|
-
await utils.mkdir(
|
|
12564
|
+
await utils.mkdir(path2.dirname(file));
|
|
12523
12565
|
await fs_1.promises.writeFile(file, `${process.pid.toString()} ${uuid}`);
|
|
12524
12566
|
this.files.add(file);
|
|
12525
12567
|
this.events.emit(LockFileEvents.lock, file);
|
|
@@ -12705,7 +12747,7 @@ var require_MongoInstance = __commonJS({
|
|
|
12705
12747
|
exports.MongoInstance = exports.MongoInstanceEvents = void 0;
|
|
12706
12748
|
var tslib_1 = (init_tslib_es6(), __toCommonJS(tslib_es6_exports));
|
|
12707
12749
|
var child_process_1 = __require("child_process");
|
|
12708
|
-
var
|
|
12750
|
+
var path2 = tslib_1.__importStar(__require("path"));
|
|
12709
12751
|
var MongoBinary_1 = require_MongoBinary();
|
|
12710
12752
|
var debug_1 = tslib_1.__importDefault(require_src());
|
|
12711
12753
|
var utils_1 = require_utils();
|
|
@@ -12942,7 +12984,7 @@ var require_MongoInstance = __commonJS({
|
|
|
12942
12984
|
*/
|
|
12943
12985
|
_launchMongod(mongoBin) {
|
|
12944
12986
|
this.debug("_launchMongod: Launching Mongod Process");
|
|
12945
|
-
const childProcess = (0, child_process_1.spawn)(
|
|
12987
|
+
const childProcess = (0, child_process_1.spawn)(path2.resolve(mongoBin), this.prepareCommandArgs(), {
|
|
12946
12988
|
...this.spawnOpts,
|
|
12947
12989
|
stdio: "pipe"
|
|
12948
12990
|
// ensure that stdio is always an pipe, regardless of user input
|
|
@@ -12952,7 +12994,7 @@ var require_MongoInstance = __commonJS({
|
|
|
12952
12994
|
childProcess.on("close", this.closeHandler.bind(this));
|
|
12953
12995
|
childProcess.on("error", this.errorHandler.bind(this));
|
|
12954
12996
|
if ((0, utils_1.isNullOrUndefined)(childProcess.pid)) {
|
|
12955
|
-
throw new errors_1.StartBinaryFailedError(
|
|
12997
|
+
throw new errors_1.StartBinaryFailedError(path2.resolve(mongoBin));
|
|
12956
12998
|
}
|
|
12957
12999
|
childProcess.unref();
|
|
12958
13000
|
this.emit(MongoInstanceEvents.instanceLaunched);
|
|
@@ -12966,7 +13008,7 @@ var require_MongoInstance = __commonJS({
|
|
|
12966
13008
|
*/
|
|
12967
13009
|
_launchKiller(parentPid, childPid) {
|
|
12968
13010
|
this.debug(`_launchKiller: Launching Killer Process (parent: ${parentPid}, child: ${childPid})`);
|
|
12969
|
-
const killer = (0, child_process_1.fork)(
|
|
13011
|
+
const killer = (0, child_process_1.fork)(path2.resolve(__dirname, "../../scripts/mongo_killer.js"), [parentPid.toString(), childPid.toString()], {
|
|
12970
13012
|
execArgv: [],
|
|
12971
13013
|
detached: true,
|
|
12972
13014
|
stdio: "ignore"
|
|
@@ -14137,8 +14179,20 @@ var NullProvider = class {
|
|
|
14137
14179
|
status: "accepted"
|
|
14138
14180
|
};
|
|
14139
14181
|
}
|
|
14182
|
+
/**
|
|
14183
|
+
* Fails closed. This provider has no signing key, so it can never establish
|
|
14184
|
+
* that an inbound payload is authentic — and a method whose entire job is to
|
|
14185
|
+
* reject unauthenticated input must not answer "yes" by default. Returning
|
|
14186
|
+
* `true` here would mean any unsigned body posted to the webhook route is
|
|
14187
|
+
* accepted as genuine whenever the null provider is registered, which is
|
|
14188
|
+
* exactly the dev/staging configuration where that is easiest to miss.
|
|
14189
|
+
*
|
|
14190
|
+
* Nothing is lost: `parseWebhookEvents()` returns `[]`, so there was never
|
|
14191
|
+
* any inbound behaviour to preserve. To exercise the inbound half locally,
|
|
14192
|
+
* use a provider that actually signs (see `test/README.md`).
|
|
14193
|
+
*/
|
|
14140
14194
|
async verifyWebhook() {
|
|
14141
|
-
return
|
|
14195
|
+
return false;
|
|
14142
14196
|
}
|
|
14143
14197
|
parseWebhookEvents() {
|
|
14144
14198
|
return [];
|
|
@@ -14268,6 +14322,10 @@ var flowStepSchema = z.lazy(
|
|
|
14268
14322
|
}),
|
|
14269
14323
|
z.object({
|
|
14270
14324
|
type: z.literal("webhook"),
|
|
14325
|
+
// `url` is validated for shape only and is deliberately never templated —
|
|
14326
|
+
// see the comment on handleWebhookStep in src/server/runner/step.ts, and
|
|
14327
|
+
// INVARIANT 16. Rendering contact/event data into this field would turn an
|
|
14328
|
+
// admin-authored flow into a server-side request forgery primitive.
|
|
14271
14329
|
url: z.string().url(),
|
|
14272
14330
|
method: z.enum(["POST", "PUT"]).optional(),
|
|
14273
14331
|
payload: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -14361,6 +14419,7 @@ var DEFAULTS = {
|
|
|
14361
14419
|
requireDoubleOptIn: false,
|
|
14362
14420
|
unsubscribeTokenLifetimeDays: 90,
|
|
14363
14421
|
transactionalRespectUnsubscribe: false,
|
|
14422
|
+
unsubscribeWriteTimeoutMs: 5e3,
|
|
14364
14423
|
doiTemplateSlug: "doi-confirmation",
|
|
14365
14424
|
doiTokenLifetimeDays: 7,
|
|
14366
14425
|
broadcastConfirmationThreshold: 1e3,
|
|
@@ -14377,7 +14436,9 @@ var DEFAULTS = {
|
|
|
14377
14436
|
trackOpens: true,
|
|
14378
14437
|
trackClicks: true,
|
|
14379
14438
|
storeTrackingIp: false,
|
|
14380
|
-
storeRenderedBody: false
|
|
14439
|
+
storeRenderedBody: false,
|
|
14440
|
+
requireSignedTrackingUrls: false,
|
|
14441
|
+
trackingUrlLifetimeDays: 0
|
|
14381
14442
|
};
|
|
14382
14443
|
var CIRCUIT_BREAKER_DEFAULTS = {
|
|
14383
14444
|
hardBounceRatePctTrip: 2,
|
|
@@ -14394,6 +14455,7 @@ function resolveConfig(c) {
|
|
|
14394
14455
|
requireDoubleOptIn: c.requireDoubleOptIn ?? DEFAULTS.requireDoubleOptIn,
|
|
14395
14456
|
unsubscribeTokenLifetimeDays: c.unsubscribeTokenLifetimeDays ?? DEFAULTS.unsubscribeTokenLifetimeDays,
|
|
14396
14457
|
transactionalRespectUnsubscribe: c.transactionalRespectUnsubscribe ?? DEFAULTS.transactionalRespectUnsubscribe,
|
|
14458
|
+
unsubscribeWriteTimeoutMs: c.unsubscribeWriteTimeoutMs ?? DEFAULTS.unsubscribeWriteTimeoutMs,
|
|
14397
14459
|
doiTemplateSlug: c.doiTemplateSlug ?? DEFAULTS.doiTemplateSlug,
|
|
14398
14460
|
doiTokenLifetimeDays: c.doiTokenLifetimeDays ?? DEFAULTS.doiTokenLifetimeDays,
|
|
14399
14461
|
broadcastConfirmationThreshold: c.broadcastConfirmationThreshold ?? DEFAULTS.broadcastConfirmationThreshold,
|
|
@@ -14411,6 +14473,8 @@ function resolveConfig(c) {
|
|
|
14411
14473
|
trackClicks: c.trackClicks ?? DEFAULTS.trackClicks,
|
|
14412
14474
|
storeTrackingIp: c.storeTrackingIp ?? DEFAULTS.storeTrackingIp,
|
|
14413
14475
|
storeRenderedBody: c.storeRenderedBody ?? DEFAULTS.storeRenderedBody,
|
|
14476
|
+
requireSignedTrackingUrls: c.requireSignedTrackingUrls ?? DEFAULTS.requireSignedTrackingUrls,
|
|
14477
|
+
trackingUrlLifetimeDays: c.trackingUrlLifetimeDays ?? DEFAULTS.trackingUrlLifetimeDays,
|
|
14414
14478
|
circuitBreaker: { ...CIRCUIT_BREAKER_DEFAULTS, ...c.circuitBreaker ?? {} }
|
|
14415
14479
|
};
|
|
14416
14480
|
}
|
|
@@ -14613,6 +14677,22 @@ function isoDay(d) {
|
|
|
14613
14677
|
function randomId() {
|
|
14614
14678
|
return crypto2.randomBytes(8).toString("hex");
|
|
14615
14679
|
}
|
|
14680
|
+
|
|
14681
|
+
// src/server/provider-lookup.ts
|
|
14682
|
+
var PROVIDER_METHODS = ["send", "verifyWebhook", "parseWebhookEvents"];
|
|
14683
|
+
function isMailProvider(candidate) {
|
|
14684
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
14685
|
+
const p = candidate;
|
|
14686
|
+
return PROVIDER_METHODS.every((m) => typeof p[m] === "function");
|
|
14687
|
+
}
|
|
14688
|
+
function resolveProvider(providers, name) {
|
|
14689
|
+
if (typeof name !== "string" || !Object.hasOwn(providers, name)) return null;
|
|
14690
|
+
const candidate = providers[name];
|
|
14691
|
+
return isMailProvider(candidate) ? candidate : null;
|
|
14692
|
+
}
|
|
14693
|
+
function registeredProviderNames(providers) {
|
|
14694
|
+
return Object.keys(providers).filter((name) => resolveProvider(providers, name) !== null).sort();
|
|
14695
|
+
}
|
|
14616
14696
|
function signUnsubscribeToken(payload, secret) {
|
|
14617
14697
|
const body = JSON.stringify({
|
|
14618
14698
|
e: payload.email.toLowerCase(),
|
|
@@ -14623,6 +14703,19 @@ function signUnsubscribeToken(payload, secret) {
|
|
|
14623
14703
|
const hmac = crypto2.createHmac("sha256", secret).update(bodyB64).digest();
|
|
14624
14704
|
return `${bodyB64}.${b64url(hmac)}`;
|
|
14625
14705
|
}
|
|
14706
|
+
var TRACKING_SCOPES = ["open", "click"];
|
|
14707
|
+
var TRACKING_SIG_LENGTH = 12;
|
|
14708
|
+
function trackingMessage(scope, params) {
|
|
14709
|
+
const tail = params.linkId === void 0 ? "" : `:${params.linkId}`;
|
|
14710
|
+
return `mailery.track.v1:${scope}:${params.sendId}${tail}`;
|
|
14711
|
+
}
|
|
14712
|
+
function signTrackingToken(scope, params, secret) {
|
|
14713
|
+
if (!TRACKING_SCOPES.includes(scope)) {
|
|
14714
|
+
throw new Error(`signTrackingToken: unknown scope ${String(scope)}`);
|
|
14715
|
+
}
|
|
14716
|
+
const mac = crypto2.createHmac("sha256", secret).update(trackingMessage(scope, params)).digest();
|
|
14717
|
+
return b64url(mac).slice(0, TRACKING_SIG_LENGTH);
|
|
14718
|
+
}
|
|
14626
14719
|
function b64url(buf) {
|
|
14627
14720
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
14628
14721
|
}
|
|
@@ -14639,6 +14732,39 @@ function signDoiToken(payload, secret) {
|
|
|
14639
14732
|
const hmac = crypto2.createHmac("sha256", secret).update(bodyB64).digest();
|
|
14640
14733
|
return `${bodyB64}.${b64url(hmac)}`;
|
|
14641
14734
|
}
|
|
14735
|
+
|
|
14736
|
+
// src/server/unsubscribe.ts
|
|
14737
|
+
async function applyUnsubscribe(collections, input, now = /* @__PURE__ */ new Date()) {
|
|
14738
|
+
const normalized = input.email;
|
|
14739
|
+
await collections.suppressions.updateOne(
|
|
14740
|
+
{ email: normalized, scope: input.scope },
|
|
14741
|
+
{
|
|
14742
|
+
$setOnInsert: {
|
|
14743
|
+
email: normalized,
|
|
14744
|
+
emailHash: sha256Hex(normalized),
|
|
14745
|
+
scope: input.scope,
|
|
14746
|
+
// mailer_suppressions canonical reason — see plans/02-data-model.md.
|
|
14747
|
+
reason: "unsubscribed",
|
|
14748
|
+
source: input.source,
|
|
14749
|
+
notes: input.notes ?? null,
|
|
14750
|
+
addedAt: now,
|
|
14751
|
+
expiresAt: null
|
|
14752
|
+
}
|
|
14753
|
+
},
|
|
14754
|
+
{ upsert: true }
|
|
14755
|
+
);
|
|
14756
|
+
await collections.subscriptions.updateOne(
|
|
14757
|
+
{ emailAtSubscribe: normalized },
|
|
14758
|
+
{
|
|
14759
|
+
$set: {
|
|
14760
|
+
status: "unsubscribed",
|
|
14761
|
+
unsubscribedAt: now,
|
|
14762
|
+
unsubscribeReason: input.reason,
|
|
14763
|
+
updatedAt: now
|
|
14764
|
+
}
|
|
14765
|
+
}
|
|
14766
|
+
);
|
|
14767
|
+
}
|
|
14642
14768
|
var QUEUE_NAMES = {
|
|
14643
14769
|
tick: "mailer-tick",
|
|
14644
14770
|
advance: "mailer-advance",
|
|
@@ -15433,7 +15559,7 @@ async function suppressOnce(ctx, email, reason, scope) {
|
|
|
15433
15559
|
}
|
|
15434
15560
|
|
|
15435
15561
|
// src/server/runner/predicate.ts
|
|
15436
|
-
var
|
|
15562
|
+
var DEFAULT_BOT_UA_RE = /Mimecast|SafeLinks|proofpoint|HeadlessChrome|Googlebot|bingbot/i;
|
|
15437
15563
|
async function evaluatePredicate(predicate, ctx) {
|
|
15438
15564
|
const p = predicate;
|
|
15439
15565
|
if ("hasTag" in p) return ctx.contact.tags.includes(p.hasTag);
|
|
@@ -15501,17 +15627,35 @@ async function openOrClickCount(ctx, kind, opts, excludeBots) {
|
|
|
15501
15627
|
return await ctx.collections.sends.countDocuments(filter);
|
|
15502
15628
|
}
|
|
15503
15629
|
const docs = await ctx.collections.sends.find(filter).limit(1e3).toArray();
|
|
15630
|
+
const botRe = ctx.botFilter?.userAgentPattern ?? DEFAULT_BOT_UA_RE;
|
|
15631
|
+
const minOpenDelayMs = ctx.botFilter?.minOpenDelayMs ?? 0;
|
|
15504
15632
|
let n = 0;
|
|
15505
15633
|
for (const s of docs) {
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
}
|
|
15510
|
-
const hasHumanClick = s.clickedLinks?.some((c) => !c.userAgent || !BOT_UA_RE.test(c.userAgent));
|
|
15511
|
-
if (hasHumanClick) n++;
|
|
15634
|
+
const doc = s;
|
|
15635
|
+
const human = kind === "opened" ? hasHumanOpen(doc, botRe, minOpenDelayMs) : (doc.clickedLinks ?? []).some((c) => !isBotUserAgent(c.userAgent, botRe));
|
|
15636
|
+
if (human) n++;
|
|
15512
15637
|
}
|
|
15513
15638
|
return n;
|
|
15514
15639
|
}
|
|
15640
|
+
function isBotUserAgent(ua, botRe) {
|
|
15641
|
+
if (typeof ua !== "string" || ua.trim() === "") return false;
|
|
15642
|
+
return botRe.test(ua);
|
|
15643
|
+
}
|
|
15644
|
+
function hasHumanOpen(send, botRe, minOpenDelayMs) {
|
|
15645
|
+
const opens = send.opens && send.opens.length > 0 ? send.opens : send.openedAt ? [{ openedAt: send.openedAt, userAgent: null }] : [];
|
|
15646
|
+
return opens.some((o) => {
|
|
15647
|
+
if (isBotUserAgent(o.userAgent, botRe)) return false;
|
|
15648
|
+
if (isPrefetchOpen(o.openedAt, send.queuedAt, minOpenDelayMs)) return false;
|
|
15649
|
+
return true;
|
|
15650
|
+
});
|
|
15651
|
+
}
|
|
15652
|
+
function isPrefetchOpen(openedAt, queuedAt, minOpenDelayMs) {
|
|
15653
|
+
if (!minOpenDelayMs || minOpenDelayMs <= 0) return false;
|
|
15654
|
+
if (!(openedAt instanceof Date) || !(queuedAt instanceof Date)) return false;
|
|
15655
|
+
const delta = openedAt.getTime() - queuedAt.getTime();
|
|
15656
|
+
if (!Number.isFinite(delta) || delta < 0) return false;
|
|
15657
|
+
return delta < minOpenDelayMs;
|
|
15658
|
+
}
|
|
15515
15659
|
function effectiveLowerBound(ctx, opts) {
|
|
15516
15660
|
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
15517
15661
|
if (opts.sinceFlowStart) return ctx.run.enteredAt;
|
|
@@ -15633,6 +15777,7 @@ function derivePlaintext(html) {
|
|
|
15633
15777
|
]
|
|
15634
15778
|
}).trim();
|
|
15635
15779
|
}
|
|
15780
|
+
var PLAIN_TEXT = { noEscape: true };
|
|
15636
15781
|
async function renderTemplate(template, ctx, opts = {}) {
|
|
15637
15782
|
const hb = makeHandlebars(opts.helpers);
|
|
15638
15783
|
const subject = hb.compile(template.subject)(ctx);
|
|
@@ -15644,7 +15789,7 @@ async function renderTemplate(template, ctx, opts = {}) {
|
|
|
15644
15789
|
const compiled = await compileTemplate(template.body.mjml);
|
|
15645
15790
|
html = hb.compile(compiled.html)(ctx);
|
|
15646
15791
|
}
|
|
15647
|
-
const plainText = template.body.plainText ? hb.compile(template.body.plainText)(ctx) : derivePlaintext(html);
|
|
15792
|
+
const plainText = template.body.plainText ? hb.compile(template.body.plainText, PLAIN_TEXT)(ctx) : derivePlaintext(html);
|
|
15648
15793
|
return {
|
|
15649
15794
|
subject,
|
|
15650
15795
|
preheader,
|
|
@@ -15670,12 +15815,14 @@ function applyTracking(html, opts) {
|
|
|
15670
15815
|
seen.set(url, linkId);
|
|
15671
15816
|
links.push({ linkId, url });
|
|
15672
15817
|
}
|
|
15673
|
-
const
|
|
15818
|
+
const sig = opts.signingSecret ? `/${signTrackingToken("click", { sendId: opts.sendId, linkId }, opts.signingSecret)}` : "";
|
|
15819
|
+
const newHref = `${opts.publicUrl}/m/click/${opts.sendId}/${linkId}${sig}`;
|
|
15674
15820
|
return `<a${pre}href="${newHref}"${post}>`;
|
|
15675
15821
|
});
|
|
15676
15822
|
}
|
|
15677
15823
|
if (opts.trackOpens) {
|
|
15678
|
-
const
|
|
15824
|
+
const sig = opts.signingSecret ? `.${signTrackingToken("open", { sendId: opts.sendId }, opts.signingSecret)}` : "";
|
|
15825
|
+
const pixel = `<img src="${opts.publicUrl}/m/open/${opts.sendId}${sig}.png" width="1" height="1" alt="" style="display:block;border:0" />`;
|
|
15679
15826
|
if (/<\/body>/i.test(out)) {
|
|
15680
15827
|
out = out.replace(/<\/body>/i, `${pixel}</body>`);
|
|
15681
15828
|
} else {
|
|
@@ -15686,14 +15833,22 @@ function applyTracking(html, opts) {
|
|
|
15686
15833
|
}
|
|
15687
15834
|
function shouldSkipClickRewrite(url, preserve, fullTag) {
|
|
15688
15835
|
if (!url) return true;
|
|
15689
|
-
if (url.startsWith("mailto:")) return true;
|
|
15690
|
-
if (url.startsWith("tel:")) return true;
|
|
15691
15836
|
if (url.startsWith("#")) return true;
|
|
15692
15837
|
if (url.startsWith("{{") || url.startsWith("{")) return true;
|
|
15693
15838
|
if (preserve.has(url.trim())) return true;
|
|
15694
15839
|
if (/data-mailer-notrack=["']true["']/.test(fullTag)) return true;
|
|
15840
|
+
if (!isHttpUrl(url)) return true;
|
|
15695
15841
|
return false;
|
|
15696
15842
|
}
|
|
15843
|
+
function isHttpUrl(url) {
|
|
15844
|
+
let parsed;
|
|
15845
|
+
try {
|
|
15846
|
+
parsed = new URL(url.trim());
|
|
15847
|
+
} catch {
|
|
15848
|
+
return false;
|
|
15849
|
+
}
|
|
15850
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
15851
|
+
}
|
|
15697
15852
|
var NAMED_ENTITIES = {
|
|
15698
15853
|
amp: "&",
|
|
15699
15854
|
lt: "<",
|
|
@@ -15914,7 +16069,8 @@ async function dispatchSend(sendId, ctx) {
|
|
|
15914
16069
|
publicUrl: ctx.config.publicUrl,
|
|
15915
16070
|
trackOpens: template.trackOpens ?? ctx.config.trackOpens,
|
|
15916
16071
|
trackClicks: template.trackClicks ?? ctx.config.trackClicks,
|
|
15917
|
-
preserveUrls: [renderCtx.unsubscribeUrl]
|
|
16072
|
+
preserveUrls: [renderCtx.unsubscribeUrl],
|
|
16073
|
+
signingSecret: ctx.config.unsubscribeSecret
|
|
15918
16074
|
});
|
|
15919
16075
|
await ctx.collections.sends.updateOne(
|
|
15920
16076
|
{ _id: send._id },
|
|
@@ -15932,9 +16088,14 @@ async function dispatchSend(sendId, ctx) {
|
|
|
15932
16088
|
}
|
|
15933
16089
|
}
|
|
15934
16090
|
);
|
|
15935
|
-
const provider = ctx.providers
|
|
16091
|
+
const provider = resolveProvider(ctx.providers, send.provider);
|
|
15936
16092
|
if (!provider) {
|
|
15937
|
-
|
|
16093
|
+
const known = registeredProviderNames(ctx.providers);
|
|
16094
|
+
await markFailed(
|
|
16095
|
+
send._id,
|
|
16096
|
+
`provider_unknown: no provider is registered as "${send.provider}". Registered providers: ${known.length ? known.join(", ") : "(none)"}.`,
|
|
16097
|
+
ctx
|
|
16098
|
+
);
|
|
15938
16099
|
return;
|
|
15939
16100
|
}
|
|
15940
16101
|
const headers = send.kind === "marketing" ? {
|
|
@@ -16133,7 +16294,7 @@ async function handleWait(run, step2, ctx) {
|
|
|
16133
16294
|
}
|
|
16134
16295
|
async function handleCondition(run, step2, contact, ctx) {
|
|
16135
16296
|
if (!contact) return;
|
|
16136
|
-
const result = await evaluatePredicate(step2.test, { contact, run, collections: ctx.collections });
|
|
16297
|
+
const result = await evaluatePredicate(step2.test, { contact, run, collections: ctx.collections, botFilter: ctx.config.botFilter });
|
|
16137
16298
|
if (result) {
|
|
16138
16299
|
await advanceStep(run, ctx, { action: "condition_evaluated", details: { result: true } });
|
|
16139
16300
|
return;
|
|
@@ -16146,7 +16307,7 @@ async function handleCondition(run, step2, contact, ctx) {
|
|
|
16146
16307
|
}
|
|
16147
16308
|
async function handleBranch(run, step2, contact, ctx) {
|
|
16148
16309
|
if (!contact) return;
|
|
16149
|
-
const result = await evaluatePredicate(step2.test, { contact, run, collections: ctx.collections });
|
|
16310
|
+
const result = await evaluatePredicate(step2.test, { contact, run, collections: ctx.collections, botFilter: ctx.config.botFilter });
|
|
16150
16311
|
const newPath = [...run.currentBranchPath, run.currentStepIndex, result ? "true" : "false", 0];
|
|
16151
16312
|
const updated = await ctx.collections.flowRuns.findOneAndUpdate(
|
|
16152
16313
|
{ _id: run._id, currentStepIndex: run.currentStepIndex },
|
|
@@ -16917,14 +17078,14 @@ function createPostmasterClient(cfg, fetcher = globalThis.fetch) {
|
|
|
16917
17078
|
};
|
|
16918
17079
|
return cached.accessToken;
|
|
16919
17080
|
}
|
|
16920
|
-
async function authedGet(
|
|
17081
|
+
async function authedGet(path2) {
|
|
16921
17082
|
const token = await getAccessToken();
|
|
16922
|
-
const res = await fetchWithTimeout(fetcher, `${POSTMASTER_BASE}${
|
|
17083
|
+
const res = await fetchWithTimeout(fetcher, `${POSTMASTER_BASE}${path2}`, {
|
|
16923
17084
|
method: "GET",
|
|
16924
17085
|
headers: { authorization: `Bearer ${token}` }
|
|
16925
17086
|
});
|
|
16926
17087
|
if (!res.ok) {
|
|
16927
|
-
throw new Error(`Postmaster GET ${
|
|
17088
|
+
throw new Error(`Postmaster GET ${path2} failed: ${res.status} ${await safeText(res)}`);
|
|
16928
17089
|
}
|
|
16929
17090
|
return await res.json();
|
|
16930
17091
|
}
|
|
@@ -17302,6 +17463,250 @@ async function pruneDmarcFailures(ctx, opts = {}) {
|
|
|
17302
17463
|
const r = await ctx.collections.dmarcFailures.deleteMany({ receivedAt: { $lt: cutoff } });
|
|
17303
17464
|
return r.deletedCount ?? 0;
|
|
17304
17465
|
}
|
|
17466
|
+
var DRAINING_INFIX = ".draining.";
|
|
17467
|
+
var ADOPT_AFTER_MS = 5 * 60 * 1e3;
|
|
17468
|
+
var JOURNAL_WARN_BYTES = 8 * 1024 * 1024;
|
|
17469
|
+
var O_NOFOLLOW = fs.constants.O_NOFOLLOW ?? 0;
|
|
17470
|
+
var APPEND_FLAGS = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND | O_NOFOLLOW;
|
|
17471
|
+
function assertPrivateRegularFile(fd, journalPath) {
|
|
17472
|
+
const st = fs.fstatSync(fd);
|
|
17473
|
+
if (!st.isFile()) {
|
|
17474
|
+
throw new Error(`pending-unsubscribe journal is not a regular file: ${journalPath}`);
|
|
17475
|
+
}
|
|
17476
|
+
if (st.nlink > 1) {
|
|
17477
|
+
throw new Error(`pending-unsubscribe journal has ${st.nlink} hard links: ${journalPath}`);
|
|
17478
|
+
}
|
|
17479
|
+
if ((st.mode & 63) !== 0) fs.fchmodSync(fd, 384);
|
|
17480
|
+
}
|
|
17481
|
+
function serializeEntry(entry) {
|
|
17482
|
+
return JSON.stringify(entry) + "\n";
|
|
17483
|
+
}
|
|
17484
|
+
function claimToken() {
|
|
17485
|
+
return `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
|
|
17486
|
+
}
|
|
17487
|
+
function claimJournal(journalPath) {
|
|
17488
|
+
const claimed = `${journalPath}${DRAINING_INFIX}${claimToken()}`;
|
|
17489
|
+
try {
|
|
17490
|
+
fs.renameSync(journalPath, claimed);
|
|
17491
|
+
} catch (err) {
|
|
17492
|
+
if (isMissing(err)) return null;
|
|
17493
|
+
throw err;
|
|
17494
|
+
}
|
|
17495
|
+
markClaimActive(claimed);
|
|
17496
|
+
return claimed;
|
|
17497
|
+
}
|
|
17498
|
+
function markClaimActive(claimPath) {
|
|
17499
|
+
const now = /* @__PURE__ */ new Date();
|
|
17500
|
+
try {
|
|
17501
|
+
fs.utimesSync(claimPath, now, now);
|
|
17502
|
+
} catch (err) {
|
|
17503
|
+
if (!isMissing(err)) throw err;
|
|
17504
|
+
}
|
|
17505
|
+
}
|
|
17506
|
+
function adoptAbandonedClaims(journalPath, opts = {}) {
|
|
17507
|
+
const olderThanMs = opts.olderThanMs ?? ADOPT_AFTER_MS;
|
|
17508
|
+
const now = opts.now ?? Date.now();
|
|
17509
|
+
const dir = path.dirname(journalPath);
|
|
17510
|
+
const prefix = path.basename(journalPath) + DRAINING_INFIX;
|
|
17511
|
+
let names;
|
|
17512
|
+
try {
|
|
17513
|
+
names = fs.readdirSync(dir);
|
|
17514
|
+
} catch (err) {
|
|
17515
|
+
if (isMissing(err)) return [];
|
|
17516
|
+
throw err;
|
|
17517
|
+
}
|
|
17518
|
+
const adopted = [];
|
|
17519
|
+
for (const name of names) {
|
|
17520
|
+
if (!name.startsWith(prefix)) continue;
|
|
17521
|
+
const full = path.join(dir, name);
|
|
17522
|
+
let st;
|
|
17523
|
+
try {
|
|
17524
|
+
st = fs.statSync(full);
|
|
17525
|
+
} catch (err) {
|
|
17526
|
+
if (isMissing(err)) continue;
|
|
17527
|
+
throw err;
|
|
17528
|
+
}
|
|
17529
|
+
if (!st.isFile()) continue;
|
|
17530
|
+
if (now - st.mtimeMs < olderThanMs) continue;
|
|
17531
|
+
const mine = `${journalPath}${DRAINING_INFIX}${claimToken()}`;
|
|
17532
|
+
try {
|
|
17533
|
+
fs.renameSync(full, mine);
|
|
17534
|
+
markClaimActive(mine);
|
|
17535
|
+
adopted.push(mine);
|
|
17536
|
+
} catch (err) {
|
|
17537
|
+
if (!isMissing(err)) throw err;
|
|
17538
|
+
}
|
|
17539
|
+
}
|
|
17540
|
+
return adopted;
|
|
17541
|
+
}
|
|
17542
|
+
function readClaim(claimPath) {
|
|
17543
|
+
let raw;
|
|
17544
|
+
try {
|
|
17545
|
+
raw = fs.readFileSync(claimPath, "utf8");
|
|
17546
|
+
} catch (err) {
|
|
17547
|
+
if (isMissing(err)) return { entries: [], malformed: [], bytes: 0 };
|
|
17548
|
+
throw err;
|
|
17549
|
+
}
|
|
17550
|
+
const entries = [];
|
|
17551
|
+
const malformed = [];
|
|
17552
|
+
const lines = raw.split("\n");
|
|
17553
|
+
for (let i = 0; i < lines.length; i++) {
|
|
17554
|
+
const line = lines[i];
|
|
17555
|
+
if (line.trim() === "") continue;
|
|
17556
|
+
const parsed = parseEntry(line);
|
|
17557
|
+
if (parsed) entries.push(parsed);
|
|
17558
|
+
else malformed.push({ lineNumber: i + 1, preview: line.slice(0, 120) });
|
|
17559
|
+
}
|
|
17560
|
+
return { entries, malformed, bytes: Buffer.byteLength(raw) };
|
|
17561
|
+
}
|
|
17562
|
+
function parseEntry(line) {
|
|
17563
|
+
let obj;
|
|
17564
|
+
try {
|
|
17565
|
+
obj = JSON.parse(line);
|
|
17566
|
+
} catch {
|
|
17567
|
+
return null;
|
|
17568
|
+
}
|
|
17569
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return null;
|
|
17570
|
+
const o = obj;
|
|
17571
|
+
const check = unsubscribeInputSchema.safeParse({
|
|
17572
|
+
email: o.email,
|
|
17573
|
+
scope: o.scope,
|
|
17574
|
+
reason: "user_request",
|
|
17575
|
+
source: "one-click"
|
|
17576
|
+
});
|
|
17577
|
+
if (!check.success) return null;
|
|
17578
|
+
const at = typeof o.at === "number" && Number.isFinite(o.at) ? o.at : Date.now();
|
|
17579
|
+
const attempts = typeof o.attempts === "number" && Number.isFinite(o.attempts) && o.attempts > 0 ? Math.floor(o.attempts) : void 0;
|
|
17580
|
+
return {
|
|
17581
|
+
email: check.data.email,
|
|
17582
|
+
scope: check.data.scope,
|
|
17583
|
+
at,
|
|
17584
|
+
...attempts === void 0 ? {} : { attempts }
|
|
17585
|
+
};
|
|
17586
|
+
}
|
|
17587
|
+
function entryToInput(entry) {
|
|
17588
|
+
return unsubscribeInputSchema.parse({
|
|
17589
|
+
email: entry.email,
|
|
17590
|
+
scope: entry.scope,
|
|
17591
|
+
reason: "user_request",
|
|
17592
|
+
// Distinguishable in `mailer_suppressions.source` from a live one-click,
|
|
17593
|
+
// but only on rows the drain actually created (the upsert is
|
|
17594
|
+
// `$setOnInsert`, so a row the live path already wrote keeps its own).
|
|
17595
|
+
source: "one-click-drain"
|
|
17596
|
+
});
|
|
17597
|
+
}
|
|
17598
|
+
function releaseClaim(journalPath, claimPath, remaining) {
|
|
17599
|
+
if (remaining.length > 0) {
|
|
17600
|
+
fs.mkdirSync(path.dirname(journalPath), { recursive: true, mode: 448 });
|
|
17601
|
+
const fd = fs.openSync(journalPath, APPEND_FLAGS, 384);
|
|
17602
|
+
try {
|
|
17603
|
+
assertPrivateRegularFile(fd, journalPath);
|
|
17604
|
+
fs.writeSync(
|
|
17605
|
+
fd,
|
|
17606
|
+
remaining.map((e) => serializeEntry({ ...e, attempts: (e.attempts ?? 0) + 1 })).join("")
|
|
17607
|
+
);
|
|
17608
|
+
} finally {
|
|
17609
|
+
fs.closeSync(fd);
|
|
17610
|
+
}
|
|
17611
|
+
}
|
|
17612
|
+
try {
|
|
17613
|
+
fs.unlinkSync(claimPath);
|
|
17614
|
+
} catch (err) {
|
|
17615
|
+
if (!isMissing(err)) throw err;
|
|
17616
|
+
}
|
|
17617
|
+
}
|
|
17618
|
+
function journalExists(journalPath) {
|
|
17619
|
+
try {
|
|
17620
|
+
return fs.statSync(journalPath).isFile();
|
|
17621
|
+
} catch (err) {
|
|
17622
|
+
if (isMissing(err)) return false;
|
|
17623
|
+
throw err;
|
|
17624
|
+
}
|
|
17625
|
+
}
|
|
17626
|
+
function isMissing(err) {
|
|
17627
|
+
const code = err?.code;
|
|
17628
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
17629
|
+
}
|
|
17630
|
+
|
|
17631
|
+
// src/server/runner/pending-unsubs.ts
|
|
17632
|
+
var MAX_ENTRIES_PER_PASS = 1e3;
|
|
17633
|
+
var NOISY_AFTER_ATTEMPTS = 10;
|
|
17634
|
+
var consoleLog = {
|
|
17635
|
+
warn: (fields, msg) => console.warn(msg ?? "mailery", fields),
|
|
17636
|
+
error: (fields, msg) => console.error(msg ?? "mailery", fields)
|
|
17637
|
+
};
|
|
17638
|
+
async function drainPendingUnsubscribes(ctx, opts = {}) {
|
|
17639
|
+
const journalPath = opts.path ?? ctx.config.pendingUnsubsPath;
|
|
17640
|
+
const result = {
|
|
17641
|
+
enabled: Boolean(journalPath),
|
|
17642
|
+
applied: 0,
|
|
17643
|
+
deferred: 0,
|
|
17644
|
+
malformed: 0,
|
|
17645
|
+
batches: 0
|
|
17646
|
+
};
|
|
17647
|
+
if (!journalPath) return result;
|
|
17648
|
+
const log = opts.log ?? consoleLog;
|
|
17649
|
+
const budget = opts.maxEntries ?? MAX_ENTRIES_PER_PASS;
|
|
17650
|
+
const claims = [];
|
|
17651
|
+
try {
|
|
17652
|
+
claims.push(...adoptAbandonedClaims(journalPath));
|
|
17653
|
+
} catch (err) {
|
|
17654
|
+
log.error?.({ err, path: journalPath }, "mailery: pending-unsub claim adoption failed");
|
|
17655
|
+
}
|
|
17656
|
+
if (journalExists(journalPath)) {
|
|
17657
|
+
const claimed = claimJournal(journalPath);
|
|
17658
|
+
if (claimed) claims.push(claimed);
|
|
17659
|
+
}
|
|
17660
|
+
if (claims.length === 0) return result;
|
|
17661
|
+
let remainingBudget = budget;
|
|
17662
|
+
for (const claimPath of claims) {
|
|
17663
|
+
result.batches++;
|
|
17664
|
+
const batch = readClaim(claimPath);
|
|
17665
|
+
if (batch.bytes > JOURNAL_WARN_BYTES) {
|
|
17666
|
+
log.warn?.(
|
|
17667
|
+
{ path: claimPath, bytes: batch.bytes, entries: batch.entries.length },
|
|
17668
|
+
"mailery: pending-unsubscribe journal is large \u2014 Mongo has been unreachable for a while"
|
|
17669
|
+
);
|
|
17670
|
+
}
|
|
17671
|
+
for (const bad of batch.malformed) {
|
|
17672
|
+
result.malformed++;
|
|
17673
|
+
log.warn?.(
|
|
17674
|
+
{ path: claimPath, lineNumber: bad.lineNumber, preview: bad.preview },
|
|
17675
|
+
"mailery: pending-unsubscribe journal line unusable \u2014 skipped"
|
|
17676
|
+
);
|
|
17677
|
+
}
|
|
17678
|
+
const deferred = [];
|
|
17679
|
+
let stop = false;
|
|
17680
|
+
for (let i = 0; i < batch.entries.length; i++) {
|
|
17681
|
+
const entry = batch.entries[i];
|
|
17682
|
+
if (stop || remainingBudget <= 0) {
|
|
17683
|
+
deferred.push(entry);
|
|
17684
|
+
continue;
|
|
17685
|
+
}
|
|
17686
|
+
try {
|
|
17687
|
+
await applyUnsubscribe(ctx.collections, entryToInput(entry));
|
|
17688
|
+
result.applied++;
|
|
17689
|
+
remainingBudget--;
|
|
17690
|
+
} catch (err) {
|
|
17691
|
+
stop = true;
|
|
17692
|
+
deferred.push(entry);
|
|
17693
|
+
const attempts = (entry.attempts ?? 0) + 1;
|
|
17694
|
+
const fields = { err, path: claimPath, attempts, pending: batch.entries.length - i };
|
|
17695
|
+
if (attempts >= NOISY_AFTER_ATTEMPTS) {
|
|
17696
|
+
log.error?.(
|
|
17697
|
+
fields,
|
|
17698
|
+
"mailery: pending unsubscribe still not applied after repeated attempts \u2014 investigate"
|
|
17699
|
+
);
|
|
17700
|
+
} else {
|
|
17701
|
+
log.warn?.(fields, "mailery: pending-unsubscribe replay failed \u2014 deferred to next tick");
|
|
17702
|
+
}
|
|
17703
|
+
}
|
|
17704
|
+
}
|
|
17705
|
+
result.deferred += deferred.length;
|
|
17706
|
+
releaseClaim(journalPath, claimPath, deferred);
|
|
17707
|
+
}
|
|
17708
|
+
return result;
|
|
17709
|
+
}
|
|
17305
17710
|
|
|
17306
17711
|
// src/server/runner/tick.ts
|
|
17307
17712
|
var STRANDED_SEND_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
@@ -17343,6 +17748,9 @@ async function runTick(ctx) {
|
|
|
17343
17748
|
await drainOutbox(ctx).catch((err) => {
|
|
17344
17749
|
console.error("mailery: outbox drain failed", err);
|
|
17345
17750
|
});
|
|
17751
|
+
await drainPendingUnsubscribes(ctx).catch((err) => {
|
|
17752
|
+
console.error("mailery: pending-unsubscribe drain failed", err);
|
|
17753
|
+
});
|
|
17346
17754
|
await processScheduledBroadcasts2(ctx).catch((err) => {
|
|
17347
17755
|
console.error("mailery: broadcast dispatch failed", err);
|
|
17348
17756
|
});
|
|
@@ -17475,6 +17883,10 @@ var Mailer = class _Mailer {
|
|
|
17475
17883
|
* MAILER_FROM_NAME / MAILER_FROM_EMAIL
|
|
17476
17884
|
* MAILER_DEFAULT_PROVIDER — defaults to 'sendgrid' if SENDGRID_API_KEY is set
|
|
17477
17885
|
* MAILER_SENDGRID_API_KEY / MAILER_SENDGRID_WEBHOOK_KEY
|
|
17886
|
+
* MAILER_SENDGRID_WEBHOOK_TOLERANCE — signed-webhook replay window, seconds
|
|
17887
|
+
* (default 300; '0' disables the check).
|
|
17888
|
+
* Unparseable values fall back to the
|
|
17889
|
+
* default rather than disabling it.
|
|
17478
17890
|
* MAILER_HOST_USERS_COLLECTION (default 'users')
|
|
17479
17891
|
* MAILER_HOST_USERS_EMAIL_FIELD (default 'email')
|
|
17480
17892
|
* MAILER_HOST_USERS_ID_FIELD (default '_id')
|
|
@@ -17509,6 +17921,10 @@ var Mailer = class _Mailer {
|
|
|
17509
17921
|
providers.sendgrid = new SendGridProvider2({
|
|
17510
17922
|
apiKey: env.MAILER_SENDGRID_API_KEY,
|
|
17511
17923
|
webhookVerificationKey: env.MAILER_SENDGRID_WEBHOOK_KEY,
|
|
17924
|
+
// Left undefined when unset/blank so the provider default (300s) applies.
|
|
17925
|
+
// resolveWebhookToleranceSeconds() rejects NaN back to the default, so a
|
|
17926
|
+
// typo here can never silently switch the replay check off.
|
|
17927
|
+
webhookToleranceSeconds: env.MAILER_SENDGRID_WEBHOOK_TOLERANCE ? Number(env.MAILER_SENDGRID_WEBHOOK_TOLERANCE) : void 0,
|
|
17512
17928
|
sandbox: env.NODE_ENV !== "production"
|
|
17513
17929
|
});
|
|
17514
17930
|
}
|
|
@@ -17536,8 +17952,15 @@ var Mailer = class _Mailer {
|
|
|
17536
17952
|
}
|
|
17537
17953
|
static async init(input) {
|
|
17538
17954
|
const config = resolveConfig(input);
|
|
17539
|
-
if (!config.providers
|
|
17540
|
-
throw new Error(
|
|
17955
|
+
if (!resolveProvider(config.providers, config.defaultProvider)) {
|
|
17956
|
+
throw new Error(
|
|
17957
|
+
`defaultProvider "${config.defaultProvider}" is not a registered provider. Registered: ${registeredProviderNames(config.providers).join(", ") || "(none)"}`
|
|
17958
|
+
);
|
|
17959
|
+
}
|
|
17960
|
+
if (config.defaultTransactionalProvider != null && !resolveProvider(config.providers, config.defaultTransactionalProvider)) {
|
|
17961
|
+
throw new Error(
|
|
17962
|
+
`defaultTransactionalProvider "${config.defaultTransactionalProvider}" is not a registered provider. Registered: ${registeredProviderNames(config.providers).join(", ") || "(none)"}`
|
|
17963
|
+
);
|
|
17541
17964
|
}
|
|
17542
17965
|
if (config.varsAdapter) {
|
|
17543
17966
|
const { assertNoReservedVarKeys: assertNoReservedVarKeys2 } = await Promise.resolve().then(() => (init_vars(), vars_exports));
|
|
@@ -17679,38 +18102,14 @@ var Mailer = class _Mailer {
|
|
|
17679
18102
|
}
|
|
17680
18103
|
}
|
|
17681
18104
|
}
|
|
18105
|
+
/**
|
|
18106
|
+
* The writes live in `server/unsubscribe.ts` so the pending-unsubscribe
|
|
18107
|
+
* drain (INVARIANT 8) replays a journaled opt-out through exactly this path
|
|
18108
|
+
* without holding a `Mailer` instance.
|
|
18109
|
+
*/
|
|
17682
18110
|
async unsubscribe(email, opts) {
|
|
17683
18111
|
const parsed = unsubscribeInputSchema.parse({ email, ...opts });
|
|
17684
|
-
|
|
17685
|
-
const now = /* @__PURE__ */ new Date();
|
|
17686
|
-
await this.collections.suppressions.updateOne(
|
|
17687
|
-
{ email: normalized, scope: parsed.scope },
|
|
17688
|
-
{
|
|
17689
|
-
$setOnInsert: {
|
|
17690
|
-
email: normalized,
|
|
17691
|
-
emailHash: sha256Hex(normalized),
|
|
17692
|
-
scope: parsed.scope,
|
|
17693
|
-
// mailer_suppressions canonical reason — see plans/02-data-model.md.
|
|
17694
|
-
reason: "unsubscribed",
|
|
17695
|
-
source: parsed.source,
|
|
17696
|
-
notes: parsed.notes ?? null,
|
|
17697
|
-
addedAt: now,
|
|
17698
|
-
expiresAt: null
|
|
17699
|
-
}
|
|
17700
|
-
},
|
|
17701
|
-
{ upsert: true }
|
|
17702
|
-
);
|
|
17703
|
-
await this.collections.subscriptions.updateOne(
|
|
17704
|
-
{ emailAtSubscribe: normalized },
|
|
17705
|
-
{
|
|
17706
|
-
$set: {
|
|
17707
|
-
status: "unsubscribed",
|
|
17708
|
-
unsubscribedAt: now,
|
|
17709
|
-
unsubscribeReason: parsed.reason,
|
|
17710
|
-
updatedAt: now
|
|
17711
|
-
}
|
|
17712
|
-
}
|
|
17713
|
-
);
|
|
18112
|
+
await applyUnsubscribe(this.collections, parsed);
|
|
17714
18113
|
}
|
|
17715
18114
|
async suppress(email, opts) {
|
|
17716
18115
|
const parsed = suppressInputSchema.parse({ email, ...opts });
|
|
@@ -18315,7 +18714,7 @@ async function createTestMailer(opts = {}) {
|
|
|
18315
18714
|
const db = client.db("mailery-test");
|
|
18316
18715
|
const memoryAdapter = opts.adapter ? null : new MemoryContactAdapter(opts.seedContacts ?? []);
|
|
18317
18716
|
const adapter = opts.adapter ?? memoryAdapter;
|
|
18318
|
-
const provider = new RecordingProvider(await
|
|
18717
|
+
const provider = new RecordingProvider(await resolveProvider2(opts.provider ?? "null"));
|
|
18319
18718
|
const mailer = await Mailer.init({
|
|
18320
18719
|
db,
|
|
18321
18720
|
adapter,
|
|
@@ -18377,7 +18776,7 @@ async function createTestMailer(opts = {}) {
|
|
|
18377
18776
|
};
|
|
18378
18777
|
return harness;
|
|
18379
18778
|
}
|
|
18380
|
-
async function
|
|
18779
|
+
async function resolveProvider2(spec) {
|
|
18381
18780
|
if (typeof spec !== "string") return spec;
|
|
18382
18781
|
if (spec === "null") return new NullProvider();
|
|
18383
18782
|
const apiKey = process.env.SENDGRID_API_KEY;
|