@huloglobal/vendure-plugin-email-tracking 0.1.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 (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +34 -0
  3. package/README.md +129 -0
  4. package/dist/email-log.entity.d.ts +57 -0
  5. package/dist/email-log.entity.d.ts.map +1 -0
  6. package/dist/email-log.entity.js +147 -0
  7. package/dist/email-log.entity.js.map +1 -0
  8. package/dist/email-tracking.controller.d.ts +51 -0
  9. package/dist/email-tracking.controller.d.ts.map +1 -0
  10. package/dist/email-tracking.controller.js +260 -0
  11. package/dist/email-tracking.controller.js.map +1 -0
  12. package/dist/email-tracking.service.d.ts +48 -0
  13. package/dist/email-tracking.service.d.ts.map +1 -0
  14. package/dist/email-tracking.service.js +196 -0
  15. package/dist/email-tracking.service.js.map +1 -0
  16. package/dist/index.d.ts +16 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +22 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/options.d.ts +43 -0
  21. package/dist/options.d.ts.map +1 -0
  22. package/dist/options.js +52 -0
  23. package/dist/options.js.map +1 -0
  24. package/dist/plugin.d.ts +53 -0
  25. package/dist/plugin.d.ts.map +1 -0
  26. package/dist/plugin.js +118 -0
  27. package/dist/plugin.js.map +1 -0
  28. package/dist/proxy-headers.d.ts +37 -0
  29. package/dist/proxy-headers.d.ts.map +1 -0
  30. package/dist/proxy-headers.js +81 -0
  31. package/dist/proxy-headers.js.map +1 -0
  32. package/dist/tracking-email-sender.d.ts +22 -0
  33. package/dist/tracking-email-sender.d.ts.map +1 -0
  34. package/dist/tracking-email-sender.js +117 -0
  35. package/dist/tracking-email-sender.js.map +1 -0
  36. package/package.json +53 -0
  37. package/ui/components/email-log.component.ts +372 -0
  38. package/ui/email-log-nav.module.ts +24 -0
  39. package/ui/email-log.module.ts +17 -0
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * Module-scoped plugin options. Populated by `EmailTrackingPlugin.init()`
4
+ * at boot and read by the service / sender / controller via the
5
+ * exported helpers below. Keeping options in module scope rather than
6
+ * threading them through every constructor avoids a refactor of the
7
+ * Nest providers — Nest creates services lazily and we want options
8
+ * available before that happens.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.setOptions = setOptions;
12
+ exports.getOptions = getOptions;
13
+ exports.setLicenceStatus = setLicenceStatus;
14
+ exports.getLicenceStatus = getLicenceStatus;
15
+ exports.trackingBaseUrl = trackingBaseUrl;
16
+ exports.ownTrackingPrefixes = ownTrackingPrefixes;
17
+ let cachedOptions = {
18
+ publicBaseUrl: 'http://localhost:3000',
19
+ licenceKey: undefined,
20
+ trackedHosts: undefined,
21
+ };
22
+ let cachedStatus = null;
23
+ function setOptions(opts) {
24
+ cachedOptions = {
25
+ publicBaseUrl: opts.publicBaseUrl.replace(/\/$/, ''),
26
+ licenceKey: opts.licenceKey,
27
+ trackedHosts: opts.trackedHosts,
28
+ };
29
+ }
30
+ function getOptions() {
31
+ return cachedOptions;
32
+ }
33
+ function setLicenceStatus(status) {
34
+ cachedStatus = status;
35
+ }
36
+ function getLicenceStatus() {
37
+ return cachedStatus;
38
+ }
39
+ /** Public tracking base URL — used by both the click-rewriter and the
40
+ * open-pixel injector. Always returns a value without a trailing slash. */
41
+ function trackingBaseUrl() {
42
+ return cachedOptions.publicBaseUrl;
43
+ }
44
+ /** Hostname prefixes the click-rewriter should treat as already-ours
45
+ * and skip rewriting. */
46
+ function ownTrackingPrefixes() {
47
+ const list = cachedOptions.trackedHosts && cachedOptions.trackedHosts.length
48
+ ? cachedOptions.trackedHosts
49
+ : [cachedOptions.publicBaseUrl];
50
+ return list.map(h => h.replace(/\/$/, '') + '/email-track/');
51
+ }
52
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAqCH,gCAMC;AAED,gCAEC;AAED,4CAEC;AAED,4CAEC;AAID,0CAEC;AAID,kDAKC;AAxCD,IAAI,aAAa,GAA+B;IAC5C,aAAa,EAAE,uBAAuB;IACtC,UAAU,EAAE,SAAS;IACrB,YAAY,EAAE,SAAS;CAC1B,CAAC;AACF,IAAI,YAAY,GAAyB,IAAI,CAAC;AAE9C,SAAgB,UAAU,CAAC,IAAgC;IACvD,aAAa,GAAG;QACZ,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QACpD,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;KAClC,CAAC;AACN,CAAC;AAED,SAAgB,UAAU;IACtB,OAAO,aAAa,CAAC;AACzB,CAAC;AAED,SAAgB,gBAAgB,CAAC,MAAqB;IAClD,YAAY,GAAG,MAAM,CAAC;AAC1B,CAAC;AAED,SAAgB,gBAAgB;IAC5B,OAAO,YAAY,CAAC;AACxB,CAAC;AAED;4EAC4E;AAC5E,SAAgB,eAAe;IAC3B,OAAO,aAAa,CAAC,aAAa,CAAC;AACvC,CAAC;AAED;0BAC0B;AAC1B,SAAgB,mBAAmB;IAC/B,MAAM,IAAI,GAAG,aAAa,CAAC,YAAY,IAAI,aAAa,CAAC,YAAY,CAAC,MAAM;QACxE,CAAC,CAAC,aAAa,CAAC,YAAY;QAC5B,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { Type } from '@vendure/core';
2
+ import { EmailTrackingPluginOptions } from './options';
3
+ /**
4
+ * `@huloglobal/vendure-plugin-email-tracking`
5
+ *
6
+ * Logs every transactional email a Vendure server sends — Vendure-plugin
7
+ * sends (order confirmation, OTP, password reset, invoice, etc.) plus
8
+ * any custom sends routed through the exposed `EmailTrackingService`.
9
+ * Each row captures recipient, subject, links to the related order /
10
+ * customer / invoice, the SMTP response, plus per-event opens + clicks
11
+ * via a 1×1 tracking pixel and a click redirector.
12
+ *
13
+ * Add to your Vendure config:
14
+ *
15
+ * ```ts
16
+ * import { EmailTrackingPlugin, TrackingEmailSender } from '@huloglobal/vendure-plugin-email-tracking';
17
+ *
18
+ * export const config: VendureConfig = {
19
+ * plugins: [
20
+ * EmailTrackingPlugin.init({
21
+ * publicBaseUrl: 'https://shop.example.com',
22
+ * licenceKey: process.env.HULO_LICENCE_KEY,
23
+ * }),
24
+ * EmailPlugin.init({
25
+ * // ... your existing email-plugin config ...
26
+ * emailSender: new TrackingEmailSender(),
27
+ * }),
28
+ * ],
29
+ * };
30
+ * ```
31
+ *
32
+ * Add the admin UI extension by including `EmailTrackingPlugin.uiExtensions`
33
+ * in your `compileUiExtensions` config.
34
+ */
35
+ export declare class EmailTrackingPlugin {
36
+ private static revocation;
37
+ static init(options: EmailTrackingPluginOptions): Type<EmailTrackingPlugin>;
38
+ static uiExtensions: {
39
+ extensionPath: string;
40
+ ngModules: ({
41
+ type: "lazy";
42
+ route: string;
43
+ ngModuleFileName: string;
44
+ ngModuleName: string;
45
+ } | {
46
+ type: "shared";
47
+ ngModuleFileName: string;
48
+ ngModuleName: string;
49
+ route?: undefined;
50
+ })[];
51
+ };
52
+ }
53
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,IAAI,EAAiB,MAAM,eAAe,CAAC;AAMxE,OAAO,EAAE,0BAA0B,EAAgC,MAAM,WAAW,CAAC;AAmBrF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAOa,mBAAmB;IAC5B,OAAO,CAAC,MAAM,CAAC,UAAU,CAAkC;IAE3D,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,0BAA0B,GAAG,IAAI,CAAC,mBAAmB,CAAC;IAgC3E,MAAM,CAAC,YAAY;;;;;;;;;;;;;MAejB;CACL"}
package/dist/plugin.js ADDED
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var EmailTrackingPlugin_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.EmailTrackingPlugin = void 0;
11
+ const core_1 = require("@vendure/core");
12
+ const vendure_licence_sdk_1 = require("@huloglobal/vendure-licence-sdk");
13
+ const email_log_entity_1 = require("./email-log.entity");
14
+ const email_tracking_service_1 = require("./email-tracking.service");
15
+ const email_tracking_controller_1 = require("./email-tracking.controller");
16
+ const options_1 = require("./options");
17
+ // Public key embedded at build time. The matching private key lives on
18
+ // HULO's licence server and never leaves it. If you fork this plugin
19
+ // privately you must replace this constant with your own public key.
20
+ const HULO_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
21
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoLmNM5UljRqe71drM6lR
22
+ Ba5vXrLOcV3GAHkYvnVFQSqdE0avrge/jsD7WdA6x8qQFNRugxQcxDJa2l0+C+BH
23
+ SbU9TimGwhA1yusHHfuz9LAXks5IQ48+2e6Pulh7iThXPJUnIKqKZUN5HhL79aaK
24
+ vrZKIgSfVhwE5PMPXWZ+Ij5IRf74PLIUn1Er75qhBXlDJ4vF8y8/3owURNC1XiUB
25
+ DGElwV/LYNoqAQei4oixe4EAxPGvFi11pgHiGuRxuWckA88y6ZHLt6urfAY9sCkj
26
+ kF+2dc2yS3j7lD+SYAaV5LQYYjePP1CYvxCZ7HHRKqthHopxY1hsK2tBtni3f7/c
27
+ UwIDAQAB
28
+ -----END PUBLIC KEY-----`;
29
+ const PLUGIN_ID = 'vendure-plugin-email-tracking';
30
+ const REVOCATION_URL = process.env.HULO_LICENCE_REVOCATION_URL
31
+ || 'https://elite.charity/licence/revoked.json';
32
+ /**
33
+ * `@huloglobal/vendure-plugin-email-tracking`
34
+ *
35
+ * Logs every transactional email a Vendure server sends — Vendure-plugin
36
+ * sends (order confirmation, OTP, password reset, invoice, etc.) plus
37
+ * any custom sends routed through the exposed `EmailTrackingService`.
38
+ * Each row captures recipient, subject, links to the related order /
39
+ * customer / invoice, the SMTP response, plus per-event opens + clicks
40
+ * via a 1×1 tracking pixel and a click redirector.
41
+ *
42
+ * Add to your Vendure config:
43
+ *
44
+ * ```ts
45
+ * import { EmailTrackingPlugin, TrackingEmailSender } from '@huloglobal/vendure-plugin-email-tracking';
46
+ *
47
+ * export const config: VendureConfig = {
48
+ * plugins: [
49
+ * EmailTrackingPlugin.init({
50
+ * publicBaseUrl: 'https://shop.example.com',
51
+ * licenceKey: process.env.HULO_LICENCE_KEY,
52
+ * }),
53
+ * EmailPlugin.init({
54
+ * // ... your existing email-plugin config ...
55
+ * emailSender: new TrackingEmailSender(),
56
+ * }),
57
+ * ],
58
+ * };
59
+ * ```
60
+ *
61
+ * Add the admin UI extension by including `EmailTrackingPlugin.uiExtensions`
62
+ * in your `compileUiExtensions` config.
63
+ */
64
+ let EmailTrackingPlugin = EmailTrackingPlugin_1 = class EmailTrackingPlugin {
65
+ static init(options) {
66
+ (0, options_1.setOptions)(options);
67
+ // Start the revocation checker once; safe to call init() again
68
+ // during hot reloads — `RevocationChecker.start()` is idempotent.
69
+ if (!EmailTrackingPlugin_1.revocation) {
70
+ EmailTrackingPlugin_1.revocation = new vendure_licence_sdk_1.RevocationChecker(REVOCATION_URL);
71
+ EmailTrackingPlugin_1.revocation.start();
72
+ }
73
+ const host = (options.publicBaseUrl || '')
74
+ .replace(/^https?:\/\//, '').replace(/\/.*$/, '');
75
+ const status = (0, vendure_licence_sdk_1.verifyLicence)({
76
+ licenceKey: options.licenceKey,
77
+ pluginId: PLUGIN_ID,
78
+ host,
79
+ publicKey: HULO_PUBLIC_KEY,
80
+ revokedIds: EmailTrackingPlugin_1.revocation.getRevokedIds(),
81
+ });
82
+ (0, options_1.setLicenceStatus)(status);
83
+ if (!status.valid) {
84
+ // eslint-disable-next-line no-console
85
+ console.warn(`[@huloglobal/vendure-plugin-email-tracking] ${status.message}` +
86
+ ` — Running in unlicensed mode. Purchase a key at https://elite-software.co.uk/licence/buy/${PLUGIN_ID}`);
87
+ }
88
+ return EmailTrackingPlugin_1;
89
+ }
90
+ };
91
+ exports.EmailTrackingPlugin = EmailTrackingPlugin;
92
+ EmailTrackingPlugin.revocation = null;
93
+ EmailTrackingPlugin.uiExtensions = {
94
+ extensionPath: __dirname + '/../ui',
95
+ ngModules: [
96
+ {
97
+ type: 'lazy',
98
+ route: 'email-log',
99
+ ngModuleFileName: 'email-log.module.ts',
100
+ ngModuleName: 'EmailLogModule',
101
+ },
102
+ {
103
+ type: 'shared',
104
+ ngModuleFileName: 'email-log-nav.module.ts',
105
+ ngModuleName: 'EmailLogNavModule',
106
+ },
107
+ ],
108
+ };
109
+ exports.EmailTrackingPlugin = EmailTrackingPlugin = EmailTrackingPlugin_1 = __decorate([
110
+ (0, core_1.VendurePlugin)({
111
+ imports: [core_1.PluginCommonModule],
112
+ providers: [email_tracking_service_1.EmailTrackingService],
113
+ controllers: [email_tracking_controller_1.EmailTrackingController],
114
+ entities: [email_log_entity_1.EmailLog],
115
+ compatibility: '^3.0.0',
116
+ })
117
+ ], EmailTrackingPlugin);
118
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,wCAAwE;AACxE,yEAAmF;AACnF,yDAA8C;AAC9C,qEAAgE;AAChE,2EAAsE;AAEtE,uCAAqF;AAErF,uEAAuE;AACvE,qEAAqE;AACrE,qEAAqE;AACrE,MAAM,eAAe,GAAG;;;;;;;;yBAQC,CAAC;AAE1B,MAAM,SAAS,GAAG,+BAA+B,CAAC;AAClD,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B;OACvD,4CAA4C,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAQI,IAAM,mBAAmB,2BAAzB,MAAM,mBAAmB;IAG5B,MAAM,CAAC,IAAI,CAAC,OAAmC;QAC3C,IAAA,oBAAU,EAAC,OAAO,CAAC,CAAC;QAEpB,+DAA+D;QAC/D,kEAAkE;QAClE,IAAI,CAAC,qBAAmB,CAAC,UAAU,EAAE,CAAC;YAClC,qBAAmB,CAAC,UAAU,GAAG,IAAI,uCAAiB,CAAC,cAAc,CAAC,CAAC;YACvE,qBAAmB,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC3C,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;aACrC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,IAAA,mCAAa,EAAC;YACzB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,QAAQ,EAAE,SAAS;YACnB,IAAI;YACJ,SAAS,EAAE,eAAe;YAC1B,UAAU,EAAE,qBAAmB,CAAC,UAAU,CAAC,aAAa,EAAE;SAC7D,CAAC,CAAC;QACH,IAAA,0BAAgB,EAAC,MAAM,CAAC,CAAC;QAEzB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAChB,sCAAsC;YACtC,OAAO,CAAC,IAAI,CACR,+CAA+C,MAAM,CAAC,OAAO,EAAE;gBAC/D,6FAA6F,SAAS,EAAE,CAC3G,CAAC;QACN,CAAC;QAED,OAAO,qBAAmB,CAAC;IAC/B,CAAC;;AAjCQ,kDAAmB;AACb,8BAAU,GAA6B,IAAI,AAAjC,CAAkC;AAkCpD,gCAAY,GAAG;IAClB,aAAa,EAAE,SAAS,GAAG,QAAQ;IACnC,SAAS,EAAE;QACP;YACI,IAAI,EAAE,MAAe;YACrB,KAAK,EAAE,WAAW;YAClB,gBAAgB,EAAE,qBAAqB;YACvC,YAAY,EAAE,gBAAgB;SACjC;QACD;YACI,IAAI,EAAE,QAAiB;YACvB,gBAAgB,EAAE,yBAAyB;YAC3C,YAAY,EAAE,mBAAmB;SACpC;KACJ;CACJ,AAfkB,CAejB;8BAlDO,mBAAmB;IAP/B,IAAA,oBAAa,EAAC;QACX,OAAO,EAAE,CAAC,yBAAkB,CAAC;QAC7B,SAAS,EAAE,CAAC,6CAAoB,CAAC;QACjC,WAAW,EAAE,CAAC,mDAAuB,CAAC;QACtC,QAAQ,EAAE,CAAC,2BAAQ,CAAC;QACpB,aAAa,EAAE,QAAQ;KAC1B,CAAC;GACW,mBAAmB,CAmD/B"}
@@ -0,0 +1,37 @@
1
+ import { Request } from 'express';
2
+ /**
3
+ * Reverse-proxy aware visitor IP extraction.
4
+ *
5
+ * Order of precedence:
6
+ * 1. Cloudflare's `CF-Connecting-IP` (always the real client IP,
7
+ * regardless of how many proxies sit in front of the worker).
8
+ * 2. `True-Client-IP` (Akamai / Cloudflare Enterprise).
9
+ * 3. `X-Real-IP` (nginx / Caddy default when proxying).
10
+ * 4. First entry in `X-Forwarded-For` (RFC 7239 ancestor; the
11
+ * left-most entry is the original client when the upstream proxy
12
+ * is trusted).
13
+ * 5. Express's `req.ip` — only useful when `app.set('trust proxy', ...)`
14
+ * has been set on the Vendure host, otherwise this is the socket
15
+ * address of the last hop.
16
+ *
17
+ * Returns `null` if none of the headers are populated and `req.ip`
18
+ * isn't available — the caller should treat this as "unknown" and
19
+ * skip IP-dependent enrichment rather than fail.
20
+ */
21
+ export declare function getRealIp(req: Request): string | null;
22
+ /**
23
+ * Cloudflare / Akamai populate the visitor's resolved country on the
24
+ * inbound request when the corresponding feature is enabled. Reading
25
+ * the upstream value avoids a per-request GeoIP lookup. Returns the
26
+ * ISO 3166-1 alpha-2 country code or `null` if no proxy header is
27
+ * present.
28
+ */
29
+ export declare function getResolvedCountry(req: Request): string | null;
30
+ /**
31
+ * Cloudflare's `cf-region-code` carries the ISO 3166-2 subdivision
32
+ * (e.g. `ENG`, `SCT`, `CA`) when the "Send subdivision data" option is
33
+ * enabled in the dashboard. Returns the bare code without the country
34
+ * prefix, or `null` if unavailable.
35
+ */
36
+ export declare function getResolvedRegion(req: Request): string | null;
37
+ //# sourceMappingURL=proxy-headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-headers.d.ts","sourceRoot":"","sources":["../src/proxy-headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkBrD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAe9D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK7D"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRealIp = getRealIp;
4
+ exports.getResolvedCountry = getResolvedCountry;
5
+ exports.getResolvedRegion = getResolvedRegion;
6
+ /**
7
+ * Reverse-proxy aware visitor IP extraction.
8
+ *
9
+ * Order of precedence:
10
+ * 1. Cloudflare's `CF-Connecting-IP` (always the real client IP,
11
+ * regardless of how many proxies sit in front of the worker).
12
+ * 2. `True-Client-IP` (Akamai / Cloudflare Enterprise).
13
+ * 3. `X-Real-IP` (nginx / Caddy default when proxying).
14
+ * 4. First entry in `X-Forwarded-For` (RFC 7239 ancestor; the
15
+ * left-most entry is the original client when the upstream proxy
16
+ * is trusted).
17
+ * 5. Express's `req.ip` — only useful when `app.set('trust proxy', ...)`
18
+ * has been set on the Vendure host, otherwise this is the socket
19
+ * address of the last hop.
20
+ *
21
+ * Returns `null` if none of the headers are populated and `req.ip`
22
+ * isn't available — the caller should treat this as "unknown" and
23
+ * skip IP-dependent enrichment rather than fail.
24
+ */
25
+ function getRealIp(req) {
26
+ var _a;
27
+ const headers = req.headers || {};
28
+ const cfIp = String(headers['cf-connecting-ip'] || '').trim();
29
+ if (cfIp)
30
+ return cfIp;
31
+ const trueClient = String(headers['true-client-ip'] || '').trim();
32
+ if (trueClient)
33
+ return trueClient;
34
+ const realIp = String(headers['x-real-ip'] || '').trim();
35
+ if (realIp)
36
+ return realIp;
37
+ const xff = String(headers['x-forwarded-for'] || '').trim();
38
+ if (xff) {
39
+ const first = (_a = xff.split(',')[0]) === null || _a === void 0 ? void 0 : _a.trim();
40
+ if (first)
41
+ return first;
42
+ }
43
+ return req.ip || null;
44
+ }
45
+ /**
46
+ * Cloudflare / Akamai populate the visitor's resolved country on the
47
+ * inbound request when the corresponding feature is enabled. Reading
48
+ * the upstream value avoids a per-request GeoIP lookup. Returns the
49
+ * ISO 3166-1 alpha-2 country code or `null` if no proxy header is
50
+ * present.
51
+ */
52
+ function getResolvedCountry(req) {
53
+ const headers = req.headers || {};
54
+ const cf = String(headers['cf-ipcountry'] || '').trim().toUpperCase();
55
+ if (cf && cf !== 'XX' && cf !== 'T1')
56
+ return cf;
57
+ const akamai = String(headers['x-akamai-edgescape'] || '').trim();
58
+ if (akamai) {
59
+ const m = akamai.match(/country_code=([A-Z]{2})/i);
60
+ if (m)
61
+ return m[1].toUpperCase();
62
+ }
63
+ const fastly = String(headers['x-country-code'] || '').trim().toUpperCase();
64
+ if (fastly && /^[A-Z]{2}$/.test(fastly))
65
+ return fastly;
66
+ return null;
67
+ }
68
+ /**
69
+ * Cloudflare's `cf-region-code` carries the ISO 3166-2 subdivision
70
+ * (e.g. `ENG`, `SCT`, `CA`) when the "Send subdivision data" option is
71
+ * enabled in the dashboard. Returns the bare code without the country
72
+ * prefix, or `null` if unavailable.
73
+ */
74
+ function getResolvedRegion(req) {
75
+ const headers = req.headers || {};
76
+ const cf = String(headers['cf-region-code'] || '').trim().toUpperCase();
77
+ if (cf && /^[A-Z0-9]{1,4}$/.test(cf))
78
+ return cf;
79
+ return null;
80
+ }
81
+ //# sourceMappingURL=proxy-headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-headers.js","sourceRoot":"","sources":["../src/proxy-headers.ts"],"names":[],"mappings":";;AAqBA,8BAkBC;AASD,gDAeC;AAQD,8CAKC;AA1ED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,SAAS,CAAC,GAAY;;IAClC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9D,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAElC,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACzD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,IAAI,GAAG,EAAE,CAAC;QACN,MAAM,KAAK,GAAG,MAAA,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,0CAAE,IAAI,EAAE,CAAC;QACxC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC5B,CAAC;IAED,OAAQ,GAAW,CAAC,EAAE,IAAI,IAAI,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,GAAY;IAC3C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;IAClC,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtE,IAAI,EAAE,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAEhD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,IAAI,MAAM,EAAE,CAAC;QACT,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACnD,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5E,IAAI,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAEvD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,GAAY;IAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;IAClC,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxE,IAAI,EAAE,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAChD,OAAO,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { Injector } from '@vendure/core';
2
+ import { EmailSender } from '@vendure/email-plugin/lib/src/sender/email-sender';
3
+ import { EmailDetails, EmailTransportOptions } from '@vendure/email-plugin/lib/src/types';
4
+ /**
5
+ * Wraps the default NodemailerEmailSender so every send by the Vendure
6
+ * email-plugin (order confirmation, password reset, OTP, invoice, etc.)
7
+ * also creates an EmailLog row and gets the open-pixel + click-tracking
8
+ * rewrites applied to its html body.
9
+ *
10
+ * The actual SMTP transport handling stays in the default sender — we just
11
+ * mutate the body in-place before delegating, and persist the row.
12
+ */
13
+ export declare class TrackingEmailSender implements EmailSender {
14
+ private inner;
15
+ private connection;
16
+ init(injector: Injector): void;
17
+ send(email: EmailDetails, options: EmailTransportOptions): Promise<void>;
18
+ /** Best-effort categorisation of plugin-email sends. */
19
+ private inferType;
20
+ private wrapHtml;
21
+ }
22
+ //# sourceMappingURL=tracking-email-sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracking-email-sender.d.ts","sourceRoot":"","sources":["../src/tracking-email-sender.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAmC,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,mDAAmD,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAO1F;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,YAAW,WAAW;IACnD,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,UAAU,CAA2B;IAE7C,IAAI,CAAC,QAAQ,EAAE,QAAQ;IASjB,IAAI,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0C9E,wDAAwD;IACxD,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,QAAQ;CAenB"}
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TrackingEmailSender = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ const nodemailer_email_sender_1 = require("@vendure/email-plugin/lib/src/sender/nodemailer-email-sender");
6
+ const email_log_entity_1 = require("./email-log.entity");
7
+ const options_1 = require("./options");
8
+ const loggerCtx = 'TrackingEmailSender';
9
+ /**
10
+ * Wraps the default NodemailerEmailSender so every send by the Vendure
11
+ * email-plugin (order confirmation, password reset, OTP, invoice, etc.)
12
+ * also creates an EmailLog row and gets the open-pixel + click-tracking
13
+ * rewrites applied to its html body.
14
+ *
15
+ * The actual SMTP transport handling stays in the default sender — we just
16
+ * mutate the body in-place before delegating, and persist the row.
17
+ */
18
+ class TrackingEmailSender {
19
+ constructor() {
20
+ this.inner = new nodemailer_email_sender_1.NodemailerEmailSender();
21
+ }
22
+ init(injector) {
23
+ try {
24
+ this.connection = injector.get(core_1.TransactionalConnection);
25
+ core_1.Logger.info('TrackingEmailSender init() OK — tracking enabled', loggerCtx);
26
+ }
27
+ catch (e) {
28
+ core_1.Logger.error(`TrackingEmailSender init() FAILED: ${e === null || e === void 0 ? void 0 : e.message}`, loggerCtx);
29
+ }
30
+ }
31
+ async send(email, options) {
32
+ var _a, _b;
33
+ const trackable = options.type === 'smtp' || options.type === 'ses' || options.type === 'sendmail';
34
+ if (!trackable) {
35
+ return this.inner.send(email, options);
36
+ }
37
+ if (!this.connection) {
38
+ core_1.Logger.warn(`TrackingEmailSender.send() called but connection is null — skipping tracking for "${email.subject}"`, loggerCtx);
39
+ return this.inner.send(email, options);
40
+ }
41
+ const repo = this.connection.rawConnection.getRepository(email_log_entity_1.EmailLog);
42
+ const row = repo.create({
43
+ type: this.inferType(email.subject),
44
+ recipient: (email.recipient || '').slice(0, 500),
45
+ subject: (email.subject || '').slice(0, 1000),
46
+ fromAddress: (email.from || '').slice(0, 500),
47
+ bcc: (_a = email.bcc) === null || _a === void 0 ? void 0 : _a.slice(0, 500),
48
+ replyTo: (_b = email.replyTo) === null || _b === void 0 ? void 0 : _b.slice(0, 500),
49
+ channelId: 1,
50
+ status: 'sent',
51
+ tracked: true,
52
+ });
53
+ const saved = await repo.save(row);
54
+ // Rewrite + pixel-inject the html body in place.
55
+ try {
56
+ email.body = this.wrapHtml(email.body || '', Number(saved.id));
57
+ }
58
+ catch (e) {
59
+ core_1.Logger.warn(`tracking wrap failed: ${e === null || e === void 0 ? void 0 : e.message}`, loggerCtx);
60
+ }
61
+ try {
62
+ await this.inner.send(email, options);
63
+ }
64
+ catch (e) {
65
+ saved.status = 'failed';
66
+ saved.errorMessage = String((e === null || e === void 0 ? void 0 : e.message) || e).slice(0, 2000);
67
+ await repo.save(saved);
68
+ throw e;
69
+ }
70
+ core_1.Logger.info(`Tracked plugin-email [${saved.type}] to ${saved.recipient} (id=${saved.id})`, loggerCtx);
71
+ }
72
+ /** Best-effort categorisation of plugin-email sends. */
73
+ inferType(subject) {
74
+ const s = (subject || '').toLowerCase();
75
+ if (s.includes('order confirmation') || s.includes('order receipt'))
76
+ return 'order-confirmation';
77
+ if (s.includes('verify'))
78
+ return 'email-verification';
79
+ if (s.includes('password'))
80
+ return 'password-reset';
81
+ if (s.includes('otp') || s.includes('one-time') || s.includes('login code'))
82
+ return 'otp-code';
83
+ if (s.includes('invoice'))
84
+ return 'invoice';
85
+ if (s.includes('review'))
86
+ return 'review-reminder';
87
+ if (s.includes('payment'))
88
+ return 'payment-due';
89
+ if (s.includes('abandoned'))
90
+ return 'abandoned-cart';
91
+ if (s.includes('email address'))
92
+ return 'email-address-change';
93
+ return 'plugin-email';
94
+ }
95
+ wrapHtml(html, eventId) {
96
+ const base = (0, options_1.trackingBaseUrl)();
97
+ const ownPrefixes = (0, options_1.ownTrackingPrefixes)();
98
+ const rewritten = html.replace(/<a\b([^>]*?)\bhref\s*=\s*(["'])(.*?)\2/gi, (m, attrs, q, url) => {
99
+ const trimmed = String(url).trim();
100
+ if (!trimmed)
101
+ return m;
102
+ if (/^(mailto:|tel:|#)/i.test(trimmed))
103
+ return m;
104
+ if (ownPrefixes.some(p => trimmed.startsWith(p)))
105
+ return m;
106
+ if (/unsubscribe/i.test(trimmed) || /\bopt[-_]?out\b/i.test(trimmed))
107
+ return m;
108
+ return `<a${attrs}href=${q}${base}/email-track/click/${eventId}?u=${encodeURIComponent(trimmed)}${q}`;
109
+ });
110
+ const pixel = `<img src="${base}/email-track/open/${eventId}.gif" width="1" height="1" alt="" border="0" style="display:none;border:0;max-height:1px;max-width:1px;outline:none;overflow:hidden;visibility:hidden">`;
111
+ if (/<\/body>/i.test(rewritten))
112
+ return rewritten.replace(/<\/body>/i, `${pixel}</body>`);
113
+ return rewritten + pixel;
114
+ }
115
+ }
116
+ exports.TrackingEmailSender = TrackingEmailSender;
117
+ //# sourceMappingURL=tracking-email-sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracking-email-sender.js","sourceRoot":"","sources":["../src/tracking-email-sender.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@huloglobal/vendure-plugin-email-tracking",
3
+ "version": "0.1.0",
4
+ "description": "Track delivery, opens and clicks on every transactional email a Vendure server sends. Wraps the @vendure/email-plugin pipeline plus an exposed helper for plugin-authored sends, persists every event to a dedicated EmailLog table, and ships an admin UI for per-customer / per-order audit trails.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "Wayne Garrison <wayne@garrison.me.uk>",
7
+ "homepage": "https://github.com/exceeded/vendure-plugin-email-tracking",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/exceeded/vendure-plugin-email-tracking.git"
11
+ },
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "files": [
15
+ "dist",
16
+ "ui",
17
+ "README.md",
18
+ "CHANGELOG.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "prepublishOnly": "yarn build"
24
+ },
25
+ "dependencies": {
26
+ "@huloglobal/vendure-licence-sdk": "^0.1.0"
27
+ },
28
+ "peerDependencies": {
29
+ "@vendure/core": ">=3.0.0",
30
+ "@vendure/email-plugin": ">=3.0.0",
31
+ "@nestjs/common": ">=10.0.0",
32
+ "nodemailer": ">=6.0.0",
33
+ "typeorm": ">=0.3.0"
34
+ },
35
+ "devDependencies": {
36
+ "@huloglobal/vendure-licence-sdk": "^0.1.0",
37
+ "@nestjs/common": "^10.0.0",
38
+ "@types/nodemailer": "^6.0.0",
39
+ "@vendure/core": "^3.6.0",
40
+ "@vendure/email-plugin": "^3.6.0",
41
+ "nodemailer": "^6.9.0",
42
+ "typeorm": "^0.3.0",
43
+ "typescript": "^5.4.0"
44
+ },
45
+ "keywords": [
46
+ "vendure",
47
+ "vendure-plugin",
48
+ "email-tracking",
49
+ "email",
50
+ "transactional-email",
51
+ "analytics"
52
+ ]
53
+ }