@drawbridge/drawbridge-utils 0.0.155 → 0.0.156
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/connections/index.d.cts +9 -4
- package/dist/connections/index.d.ts +9 -4
- package/dist/email.cjs +14 -0
- package/dist/email.d.cts +53 -1
- package/dist/email.d.ts +53 -1
- package/dist/email.js +13 -0
- package/package.json +1 -1
|
@@ -1520,10 +1520,15 @@ const contacts = {
|
|
|
1520
1520
|
remove : async ( { email, id, token }, { fetcher } = {} ) => {
|
|
1521
1521
|
|
|
1522
1522
|
// NO TOKEN IS A NO-OP HERE, unlike sendgrid and twilio, and the
|
|
1523
|
-
// difference is what the credential is
|
|
1524
|
-
//
|
|
1525
|
-
//
|
|
1526
|
-
//
|
|
1523
|
+
// difference is what the credential is FOR: this is internal CRM
|
|
1524
|
+
// tooling no merchant sees, so a deployment with no portal is a
|
|
1525
|
+
// supported state rather than a missing credential.
|
|
1526
|
+
//
|
|
1527
|
+
// The manifest does mark hubspotToken `required`, and that is not a
|
|
1528
|
+
// contradiction — the flag feeds `isLive` and nothing else, so it
|
|
1529
|
+
// decides whether the admin card reads live. Not live is the honest
|
|
1530
|
+
// badge when nothing will sync. It gates no behaviour, and
|
|
1531
|
+
// saveProviderSettings never consults it, so the row still writes.
|
|
1527
1532
|
if( ! token ) return;
|
|
1528
1533
|
|
|
1529
1534
|
const contact = id || await lookup({ email, fetcher, token });
|
|
@@ -1520,10 +1520,15 @@ const contacts = {
|
|
|
1520
1520
|
remove : async ( { email, id, token }, { fetcher } = {} ) => {
|
|
1521
1521
|
|
|
1522
1522
|
// NO TOKEN IS A NO-OP HERE, unlike sendgrid and twilio, and the
|
|
1523
|
-
// difference is what the credential is
|
|
1524
|
-
//
|
|
1525
|
-
//
|
|
1526
|
-
//
|
|
1523
|
+
// difference is what the credential is FOR: this is internal CRM
|
|
1524
|
+
// tooling no merchant sees, so a deployment with no portal is a
|
|
1525
|
+
// supported state rather than a missing credential.
|
|
1526
|
+
//
|
|
1527
|
+
// The manifest does mark hubspotToken `required`, and that is not a
|
|
1528
|
+
// contradiction — the flag feeds `isLive` and nothing else, so it
|
|
1529
|
+
// decides whether the admin card reads live. Not live is the honest
|
|
1530
|
+
// badge when nothing will sync. It gates no behaviour, and
|
|
1531
|
+
// saveProviderSettings never consults it, so the row still writes.
|
|
1527
1532
|
if( ! token ) return;
|
|
1528
1533
|
|
|
1529
1534
|
const contact = id || await lookup({ email, fetcher, token });
|
package/dist/email.cjs
CHANGED
|
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// lib/email.js
|
|
20
20
|
var email_exports = {};
|
|
21
21
|
__export(email_exports, {
|
|
22
|
+
subAddress: () => subAddress,
|
|
22
23
|
toCanonicalEmail: () => toCanonicalEmail
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(email_exports);
|
|
@@ -36,7 +37,20 @@ var toCanonicalEmail = (value) => {
|
|
|
36
37
|
if (!local) return null;
|
|
37
38
|
return local + "@" + domain;
|
|
38
39
|
};
|
|
40
|
+
var LOCAL_PART_LIMIT = 64;
|
|
41
|
+
var subAddress = (email, tag) => {
|
|
42
|
+
if (!email || typeof email !== "string") return email;
|
|
43
|
+
if (!tag || typeof tag !== "string") return email;
|
|
44
|
+
if (!/^[A-Za-z0-9]+$/.test(tag)) return email;
|
|
45
|
+
const at = email.lastIndexOf("@");
|
|
46
|
+
if (at < 1 || at === email.length - 1) return email;
|
|
47
|
+
const local = email.slice(0, at);
|
|
48
|
+
if (local.includes("+")) return email;
|
|
49
|
+
if (local.length + 1 + tag.length > LOCAL_PART_LIMIT) return email;
|
|
50
|
+
return local + "+" + tag + "@" + email.slice(at + 1);
|
|
51
|
+
};
|
|
39
52
|
// Annotate the CommonJS export names for ESM import in node:
|
|
40
53
|
0 && (module.exports = {
|
|
54
|
+
subAddress,
|
|
41
55
|
toCanonicalEmail
|
|
42
56
|
});
|
package/dist/email.d.cts
CHANGED
|
@@ -33,4 +33,56 @@ const toCanonicalEmail = ( value ) => {
|
|
|
33
33
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// RFC 5322 caps the local part at 64 octets. Everything here is ASCII, so
|
|
37
|
+
// length is octets.
|
|
38
|
+
const LOCAL_PART_LIMIT = 64;
|
|
39
|
+
|
|
40
|
+
// The inverse of toCanonicalEmail: put a tag INTO the local part rather than
|
|
41
|
+
// strip one out. `subAddress( 'org@send.drwbrdg.com', '507f…' )` gives
|
|
42
|
+
// `org+507f…@send.drwbrdg.com` — RFC 5233 sub-addressing, which Google delivers
|
|
43
|
+
// to the untagged mailbox natively.
|
|
44
|
+
//
|
|
45
|
+
// This is how the platform's lead-facing sender carries the ORGANIZATION.
|
|
46
|
+
// Shopify does the same thing with `store+<shopId>@shopifyemail.com`: one
|
|
47
|
+
// mailbox, one MX record, one authenticated domain, and the tenant rides in the
|
|
48
|
+
// address instead of in infrastructure. There is no per-organization address to
|
|
49
|
+
// store, so there is nothing to migrate or backfill.
|
|
50
|
+
//
|
|
51
|
+
// It is derived at send time, never persisted. Worth settling BEFORE the first
|
|
52
|
+
// send rather than after: the From line is frozen into every message already
|
|
53
|
+
// delivered, so a shared address cannot be split apart retroactively.
|
|
54
|
+
//
|
|
55
|
+
// Returns the address UNCHANGED whenever it cannot be tagged safely — a flat
|
|
56
|
+
// from-address is a working email, a malformed one is a send the provider
|
|
57
|
+
// rejects outright. Callers rely on that: the sender resolvers fall through to
|
|
58
|
+
// their own defaults on a falsy return, and never on a broken string.
|
|
59
|
+
const subAddress = ( email, tag ) => {
|
|
60
|
+
|
|
61
|
+
if( ! email || typeof email !== 'string' ) return email;
|
|
62
|
+
if( ! tag || typeof tag !== 'string' ) return email;
|
|
63
|
+
|
|
64
|
+
// Organization ids are 24-char ObjectId hex, and the short-id generator is
|
|
65
|
+
// base36 — both alphanumeric, both safely atext. Checked rather than assumed
|
|
66
|
+
// because the api schema types `id` as a bare string with no pattern, and
|
|
67
|
+
// `formats.documents.insert` spreads caller data OVER the generated id, so a
|
|
68
|
+
// hand-written id would win.
|
|
69
|
+
if( ! /^[A-Za-z0-9]+$/.test( tag ) ) return email;
|
|
70
|
+
|
|
71
|
+
const at = email.lastIndexOf( '@' );
|
|
72
|
+
|
|
73
|
+
if( at < 1 || at === email.length - 1 ) return email;
|
|
74
|
+
|
|
75
|
+
const local = email.slice( 0, at );
|
|
76
|
+
|
|
77
|
+
// Already tagged. Re-tagging would nest (`org+a+b@`), and the first tag is
|
|
78
|
+
// the one a receiving mailbox would route on — so the second would be a
|
|
79
|
+
// silent lie about which tenant the mail belongs to.
|
|
80
|
+
if( local.includes( '+' ) ) return email;
|
|
81
|
+
|
|
82
|
+
if( local.length + 1 + tag.length > LOCAL_PART_LIMIT ) return email;
|
|
83
|
+
|
|
84
|
+
return local + '+' + tag + '@' + email.slice( at + 1 );
|
|
85
|
+
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { subAddress, toCanonicalEmail };
|
package/dist/email.d.ts
CHANGED
|
@@ -33,4 +33,56 @@ const toCanonicalEmail = ( value ) => {
|
|
|
33
33
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// RFC 5322 caps the local part at 64 octets. Everything here is ASCII, so
|
|
37
|
+
// length is octets.
|
|
38
|
+
const LOCAL_PART_LIMIT = 64;
|
|
39
|
+
|
|
40
|
+
// The inverse of toCanonicalEmail: put a tag INTO the local part rather than
|
|
41
|
+
// strip one out. `subAddress( 'org@send.drwbrdg.com', '507f…' )` gives
|
|
42
|
+
// `org+507f…@send.drwbrdg.com` — RFC 5233 sub-addressing, which Google delivers
|
|
43
|
+
// to the untagged mailbox natively.
|
|
44
|
+
//
|
|
45
|
+
// This is how the platform's lead-facing sender carries the ORGANIZATION.
|
|
46
|
+
// Shopify does the same thing with `store+<shopId>@shopifyemail.com`: one
|
|
47
|
+
// mailbox, one MX record, one authenticated domain, and the tenant rides in the
|
|
48
|
+
// address instead of in infrastructure. There is no per-organization address to
|
|
49
|
+
// store, so there is nothing to migrate or backfill.
|
|
50
|
+
//
|
|
51
|
+
// It is derived at send time, never persisted. Worth settling BEFORE the first
|
|
52
|
+
// send rather than after: the From line is frozen into every message already
|
|
53
|
+
// delivered, so a shared address cannot be split apart retroactively.
|
|
54
|
+
//
|
|
55
|
+
// Returns the address UNCHANGED whenever it cannot be tagged safely — a flat
|
|
56
|
+
// from-address is a working email, a malformed one is a send the provider
|
|
57
|
+
// rejects outright. Callers rely on that: the sender resolvers fall through to
|
|
58
|
+
// their own defaults on a falsy return, and never on a broken string.
|
|
59
|
+
const subAddress = ( email, tag ) => {
|
|
60
|
+
|
|
61
|
+
if( ! email || typeof email !== 'string' ) return email;
|
|
62
|
+
if( ! tag || typeof tag !== 'string' ) return email;
|
|
63
|
+
|
|
64
|
+
// Organization ids are 24-char ObjectId hex, and the short-id generator is
|
|
65
|
+
// base36 — both alphanumeric, both safely atext. Checked rather than assumed
|
|
66
|
+
// because the api schema types `id` as a bare string with no pattern, and
|
|
67
|
+
// `formats.documents.insert` spreads caller data OVER the generated id, so a
|
|
68
|
+
// hand-written id would win.
|
|
69
|
+
if( ! /^[A-Za-z0-9]+$/.test( tag ) ) return email;
|
|
70
|
+
|
|
71
|
+
const at = email.lastIndexOf( '@' );
|
|
72
|
+
|
|
73
|
+
if( at < 1 || at === email.length - 1 ) return email;
|
|
74
|
+
|
|
75
|
+
const local = email.slice( 0, at );
|
|
76
|
+
|
|
77
|
+
// Already tagged. Re-tagging would nest (`org+a+b@`), and the first tag is
|
|
78
|
+
// the one a receiving mailbox would route on — so the second would be a
|
|
79
|
+
// silent lie about which tenant the mail belongs to.
|
|
80
|
+
if( local.includes( '+' ) ) return email;
|
|
81
|
+
|
|
82
|
+
if( local.length + 1 + tag.length > LOCAL_PART_LIMIT ) return email;
|
|
83
|
+
|
|
84
|
+
return local + '+' + tag + '@' + email.slice( at + 1 );
|
|
85
|
+
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { subAddress, toCanonicalEmail };
|
package/dist/email.js
CHANGED
|
@@ -13,6 +13,19 @@ var toCanonicalEmail = (value) => {
|
|
|
13
13
|
if (!local) return null;
|
|
14
14
|
return local + "@" + domain;
|
|
15
15
|
};
|
|
16
|
+
var LOCAL_PART_LIMIT = 64;
|
|
17
|
+
var subAddress = (email, tag) => {
|
|
18
|
+
if (!email || typeof email !== "string") return email;
|
|
19
|
+
if (!tag || typeof tag !== "string") return email;
|
|
20
|
+
if (!/^[A-Za-z0-9]+$/.test(tag)) return email;
|
|
21
|
+
const at = email.lastIndexOf("@");
|
|
22
|
+
if (at < 1 || at === email.length - 1) return email;
|
|
23
|
+
const local = email.slice(0, at);
|
|
24
|
+
if (local.includes("+")) return email;
|
|
25
|
+
if (local.length + 1 + tag.length > LOCAL_PART_LIMIT) return email;
|
|
26
|
+
return local + "+" + tag + "@" + email.slice(at + 1);
|
|
27
|
+
};
|
|
16
28
|
export {
|
|
29
|
+
subAddress,
|
|
17
30
|
toCanonicalEmail
|
|
18
31
|
};
|
package/package.json
CHANGED