dkimkit 0.0.0-stage → 0.6.1

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 (128) hide show
  1. package/README.md +153 -2
  2. package/dist/src/cli.d.ts +2 -0
  3. package/dist/src/cli.js +398 -0
  4. package/dist/src/cli.js.map +1 -0
  5. package/dist/src/dns-host.d.ts +10 -0
  6. package/dist/src/dns-host.js +31 -0
  7. package/dist/src/dns-host.js.map +1 -0
  8. package/dist/src/errors.d.ts +17 -0
  9. package/dist/src/errors.js +51 -0
  10. package/dist/src/errors.js.map +1 -0
  11. package/dist/src/hosted/api.d.ts +95 -0
  12. package/dist/src/hosted/api.js +781 -0
  13. package/dist/src/hosted/api.js.map +1 -0
  14. package/dist/src/hosted/auth.d.ts +70 -0
  15. package/dist/src/hosted/auth.js +121 -0
  16. package/dist/src/hosted/auth.js.map +1 -0
  17. package/dist/src/hosted/billing-dodo.d.ts +58 -0
  18. package/dist/src/hosted/billing-dodo.js +139 -0
  19. package/dist/src/hosted/billing-dodo.js.map +1 -0
  20. package/dist/src/hosted/billing-stripe.d.ts +82 -0
  21. package/dist/src/hosted/billing-stripe.js +205 -0
  22. package/dist/src/hosted/billing-stripe.js.map +1 -0
  23. package/dist/src/hosted/billing.d.ts +114 -0
  24. package/dist/src/hosted/billing.js +173 -0
  25. package/dist/src/hosted/billing.js.map +1 -0
  26. package/dist/src/hosted/cli.d.ts +2 -0
  27. package/dist/src/hosted/cli.js +305 -0
  28. package/dist/src/hosted/cli.js.map +1 -0
  29. package/dist/src/hosted/crypto.d.ts +22 -0
  30. package/dist/src/hosted/crypto.js +55 -0
  31. package/dist/src/hosted/crypto.js.map +1 -0
  32. package/dist/src/hosted/index.d.ts +7 -0
  33. package/dist/src/hosted/index.js +11 -0
  34. package/dist/src/hosted/index.js.map +1 -0
  35. package/dist/src/hosted/markdown.d.ts +16 -0
  36. package/dist/src/hosted/markdown.js +168 -0
  37. package/dist/src/hosted/markdown.js.map +1 -0
  38. package/dist/src/hosted/net.d.ts +9 -0
  39. package/dist/src/hosted/net.js +60 -0
  40. package/dist/src/hosted/net.js.map +1 -0
  41. package/dist/src/hosted/postgres-store.d.ts +62 -0
  42. package/dist/src/hosted/postgres-store.js +387 -0
  43. package/dist/src/hosted/postgres-store.js.map +1 -0
  44. package/dist/src/hosted/serialize.d.ts +144 -0
  45. package/dist/src/hosted/serialize.js +126 -0
  46. package/dist/src/hosted/serialize.js.map +1 -0
  47. package/dist/src/hosted/service.d.ts +166 -0
  48. package/dist/src/hosted/service.js +582 -0
  49. package/dist/src/hosted/service.js.map +1 -0
  50. package/dist/src/hosted/site.d.ts +54 -0
  51. package/dist/src/hosted/site.js +472 -0
  52. package/dist/src/hosted/site.js.map +1 -0
  53. package/dist/src/hosted/sqlite-store.d.ts +45 -0
  54. package/dist/src/hosted/sqlite-store.js +351 -0
  55. package/dist/src/hosted/sqlite-store.js.map +1 -0
  56. package/dist/src/hosted/store.d.ts +257 -0
  57. package/dist/src/hosted/store.js +181 -0
  58. package/dist/src/hosted/store.js.map +1 -0
  59. package/dist/src/hosted/widget.d.ts +46 -0
  60. package/dist/src/hosted/widget.js +130 -0
  61. package/dist/src/hosted/widget.js.map +1 -0
  62. package/dist/src/hosted/worker.d.ts +45 -0
  63. package/dist/src/hosted/worker.js +142 -0
  64. package/dist/src/hosted/worker.js.map +1 -0
  65. package/dist/src/http.d.ts +14 -0
  66. package/dist/src/http.js +54 -0
  67. package/dist/src/http.js.map +1 -0
  68. package/dist/src/index.d.ts +19 -0
  69. package/dist/src/index.js +19 -0
  70. package/dist/src/index.js.map +1 -0
  71. package/dist/src/orchestrator.d.ts +70 -0
  72. package/dist/src/orchestrator.js +69 -0
  73. package/dist/src/orchestrator.js.map +1 -0
  74. package/dist/src/propagation.d.ts +30 -0
  75. package/dist/src/propagation.js +76 -0
  76. package/dist/src/propagation.js.map +1 -0
  77. package/dist/src/providers/dns/aws.d.ts +28 -0
  78. package/dist/src/providers/dns/aws.js +103 -0
  79. package/dist/src/providers/dns/aws.js.map +1 -0
  80. package/dist/src/providers/dns/cloudflare.d.ts +26 -0
  81. package/dist/src/providers/dns/cloudflare.js +97 -0
  82. package/dist/src/providers/dns/cloudflare.js.map +1 -0
  83. package/dist/src/providers/dns/manual.d.ts +12 -0
  84. package/dist/src/providers/dns/manual.js +18 -0
  85. package/dist/src/providers/dns/manual.js.map +1 -0
  86. package/dist/src/providers/dns/route53.d.ts +34 -0
  87. package/dist/src/providers/dns/route53.js +176 -0
  88. package/dist/src/providers/dns/route53.js.map +1 -0
  89. package/dist/src/providers/email/factory.d.ts +29 -0
  90. package/dist/src/providers/email/factory.js +67 -0
  91. package/dist/src/providers/email/factory.js.map +1 -0
  92. package/dist/src/providers/email/mailgun.d.ts +36 -0
  93. package/dist/src/providers/email/mailgun.js +141 -0
  94. package/dist/src/providers/email/mailgun.js.map +1 -0
  95. package/dist/src/providers/email/postmark.d.ts +37 -0
  96. package/dist/src/providers/email/postmark.js +121 -0
  97. package/dist/src/providers/email/postmark.js.map +1 -0
  98. package/dist/src/providers/email/sendgrid.d.ts +31 -0
  99. package/dist/src/providers/email/sendgrid.js +109 -0
  100. package/dist/src/providers/email/sendgrid.js.map +1 -0
  101. package/dist/src/providers/email/ses.d.ts +39 -0
  102. package/dist/src/providers/email/ses.js +123 -0
  103. package/dist/src/providers/email/ses.js.map +1 -0
  104. package/dist/src/providers/factory.d.ts +14 -0
  105. package/dist/src/providers/factory.js +32 -0
  106. package/dist/src/providers/factory.js.map +1 -0
  107. package/dist/src/records.d.ts +35 -0
  108. package/dist/src/records.js +109 -0
  109. package/dist/src/records.js.map +1 -0
  110. package/dist/src/server.d.ts +67 -0
  111. package/dist/src/server.js +301 -0
  112. package/dist/src/server.js.map +1 -0
  113. package/dist/src/types.d.ts +75 -0
  114. package/dist/src/types.js +2 -0
  115. package/dist/src/types.js.map +1 -0
  116. package/dist/src/xml.d.ts +6 -0
  117. package/dist/src/xml.js +23 -0
  118. package/dist/src/xml.js.map +1 -0
  119. package/docs/billing.md +43 -0
  120. package/docs/getting-started.md +57 -0
  121. package/docs/hosted-api.md +192 -0
  122. package/docs/self-hosting.md +148 -0
  123. package/docs/widget.md +121 -0
  124. package/package.json +63 -4
  125. package/web/dashboard.html +390 -0
  126. package/web/index.html +697 -0
  127. package/web/widget-frame.html +480 -0
  128. package/web/widget.js +91 -0
@@ -0,0 +1,109 @@
1
+ const RECORD_TYPES = ['CNAME', 'TXT', 'MX'];
2
+ export function isRecordType(type) {
3
+ return RECORD_TYPES.includes(type);
4
+ }
5
+ const DOMAIN_RE = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
6
+ /** A normalized hostname with at least two labels (IDNs must be punycode). */
7
+ export function isValidDomain(name) {
8
+ return name.length <= 253 && DOMAIN_RE.test(name);
9
+ }
10
+ export function normalizeName(name) {
11
+ return name.trim().toLowerCase().replace(/\.+$/, '');
12
+ }
13
+ /** Hostnames compare case-insensitively without trailing dots; TXT values compare with quoting and chunking removed. */
14
+ export function normalizeValue(type, value) {
15
+ if (type === 'TXT') {
16
+ return value.trim().replace(/^"(.*)"$/s, '$1').replace(/"\s*"/g, '');
17
+ }
18
+ return normalizeName(value);
19
+ }
20
+ export function sameValue(type, a, b) {
21
+ return normalizeValue(type, a) === normalizeValue(type, b);
22
+ }
23
+ const SPF_TERMINATOR = /^([~+?-]?all|redirect=.*)$/i;
24
+ export function isSpf(value) {
25
+ return /^v=spf1(\s|$)/i.test(normalizeValue('TXT', value));
26
+ }
27
+ /**
28
+ * Adds the mechanisms of `desired` (e.g. "include:mailgun.org") to an existing
29
+ * SPF record, before its `all`/`redirect` term. Returns null when the existing
30
+ * record already has them all. A name must have exactly one SPF record, so
31
+ * providers' SPF records are merged in rather than added next to the old one.
32
+ */
33
+ export function mergeSpf(existing, desired) {
34
+ const have = normalizeValue('TXT', existing).split(/\s+/).filter(Boolean);
35
+ const want = normalizeValue('TXT', desired)
36
+ .split(/\s+/)
37
+ .filter((t) => t && t.toLowerCase() !== 'v=spf1' && !SPF_TERMINATOR.test(t));
38
+ const added = want.filter((t) => !have.some((h) => h.toLowerCase() === t.toLowerCase()));
39
+ if (added.length === 0)
40
+ return null;
41
+ const end = have.findIndex((t) => SPF_TERMINATOR.test(t));
42
+ const merged = end === -1 ? [...have, ...added] : [...have.slice(0, end), ...added, ...have.slice(end)];
43
+ return { value: merged.join(' '), added };
44
+ }
45
+ /**
46
+ * Decides what to do with `record` given the records already at its name.
47
+ * Never replaces a record pointing elsewhere unless `overwrite` is set, and never
48
+ * deletes records of other types. `desired` is every record being planned, so
49
+ * a provider's own multi-value sets (e.g. two MX hosts) don't conflict with each other.
50
+ */
51
+ export function decideChange(record, existing, overwrite = false, desired = []) {
52
+ const conflict = (reason) => ({ record, action: 'conflict', reason });
53
+ const siblings = desired.filter((d) => d !== record && d.name === record.name && d.type === record.type);
54
+ const ours = (e) => siblings.some((s) => sameValue(record.type, s.value, e.value));
55
+ const sameType = existing.filter((e) => e.type === record.type);
56
+ const others = existing.filter((e) => e.type !== record.type);
57
+ if (record.type === 'CNAME' && others.length > 0) {
58
+ const types = [...new Set(others.map((e) => e.type))].join(', ');
59
+ return conflict(`a CNAME cannot coexist with the existing ${types} record(s) at this name`);
60
+ }
61
+ const blockingCname = others.find((e) => e.type === 'CNAME');
62
+ if (blockingCname) {
63
+ return conflict(`an existing CNAME (${blockingCname.value}) blocks other records at this name`);
64
+ }
65
+ const match = sameType.find((e) => sameValue(record.type, e.value, record.value));
66
+ if (match) {
67
+ if (match.proxied) {
68
+ return { record, action: 'update', existingId: match.id, reason: 'disable proxying' };
69
+ }
70
+ if (record.type === 'MX' && record.priority !== undefined && match.priority !== record.priority) {
71
+ return { record, action: 'update', existingId: match.id, reason: `priority ${match.priority} → ${record.priority}` };
72
+ }
73
+ return { record, action: 'noop' };
74
+ }
75
+ if (record.type === 'TXT') {
76
+ // SPF: merge into the existing record rather than adding a second one.
77
+ if (isSpf(record.value)) {
78
+ const spf = sameType.filter((e) => isSpf(e.value));
79
+ if (spf.length > 1)
80
+ return conflict('the name already has more than one SPF record, which makes SPF fail; reduce it to one first');
81
+ if (spf.length === 1) {
82
+ const merged = mergeSpf(spf[0].value, record.value);
83
+ if (!merged)
84
+ return { record: { ...record, value: normalizeValue('TXT', spf[0].value) }, action: 'noop' };
85
+ return {
86
+ record: { ...record, value: merged.value },
87
+ action: 'update',
88
+ existingId: spf[0].id,
89
+ reason: `add ${merged.added.join(' ')} to the existing SPF record`,
90
+ };
91
+ }
92
+ }
93
+ // Other TXT records can sit side by side.
94
+ return { record, action: 'create' };
95
+ }
96
+ const foreign = sameType.filter((e) => !ours(e));
97
+ if (foreign.length === 0)
98
+ return { record, action: 'create' };
99
+ const current = foreign.map((e) => e.value).join(', ');
100
+ if (overwrite && foreign.length === 1) {
101
+ return { record, action: 'update', existingId: foreign[0].id, reason: `replace ${current}` };
102
+ }
103
+ return conflict(`existing ${record.type} points to ${current}; use overwrite to replace it`);
104
+ }
105
+ export function describeRecord(record) {
106
+ const priority = record.priority !== undefined ? `${record.priority} ` : '';
107
+ return `${record.type} ${record.name} → ${priority}${record.value}`;
108
+ }
109
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/records.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAsB,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AAE/D,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,SAAS,GAAG,6DAA6D,CAAC;AAEhF,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,wHAAwH;AACxH,MAAM,UAAU,cAAc,CAAC,IAAgB,EAAE,KAAa;IAC5D,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAgB,EAAE,CAAS,EAAE,CAAS;IAC9D,OAAO,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAWD,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAErD,MAAM,UAAU,KAAK,CAAC,KAAa;IACjC,OAAO,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAgB,EAAE,OAAe;IACxD,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC;SACxC,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACzF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACxG,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiB,EAAE,QAA0B,EAAE,SAAS,GAAG,KAAK,EAAE,UAAuB,EAAE;IACtH,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAiB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7F,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;IACzG,MAAM,IAAI,GAAG,CAAC,CAAiB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACnG,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;IAE9D,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjE,OAAO,QAAQ,CAAC,4CAA4C,KAAK,yBAAyB,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;IAC7D,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,QAAQ,CAAC,sBAAsB,aAAa,CAAC,KAAK,qCAAqC,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAClF,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QACxF,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC;YAChG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,KAAK,CAAC,QAAQ,MAAM,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACvH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC1B,uEAAuE;QACvE,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YACnD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,QAAQ,CAAC,6FAA6F,CAAC,CAAC;YACnI,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrB,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBACrD,IAAI,CAAC,MAAM;oBAAE,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;gBAC3G,OAAO;oBACL,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;oBAC1C,MAAM,EAAE,QAAQ;oBAChB,UAAU,EAAE,GAAG,CAAC,CAAC,CAAE,CAAC,EAAE;oBACtB,MAAM,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,6BAA6B;iBACnE,CAAC;YACJ,CAAC;QACH,CAAC;QACD,0CAA0C;QAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACtC,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAE9D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,IAAI,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,OAAO,EAAE,EAAE,CAAC;IAChG,CAAC;IACD,OAAO,QAAQ,CAAC,YAAY,MAAM,CAAC,IAAI,cAAc,OAAO,+BAA+B,CAAC,CAAC;AAC/F,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAiB;IAC9C,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5E,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;AACtE,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { type SetupOptions } from './orchestrator.js';
2
+ import type { DnsProvider, EmailProvider } from './types.js';
3
+ /** Body of /api/plan, /api/setup and /api/status. Secrets fall back to environment variables. */
4
+ export interface SetupRequest {
5
+ domain: string;
6
+ subdomain?: string;
7
+ dns: string;
8
+ /** sendgrid | postmark | mailgun | ses (default sendgrid) */
9
+ emailProvider?: string;
10
+ onBehalfOf?: string;
11
+ credentials?: {
12
+ sendgridApiKey?: string;
13
+ postmarkAccountToken?: string;
14
+ postmarkInboundServerId?: string;
15
+ mailgunApiKey?: string;
16
+ mailgunRegion?: string;
17
+ /** Amazon SES uses the AWS fields below (or the AWS environment) plus this region. */
18
+ sesRegion?: string;
19
+ cloudflareApiToken?: string;
20
+ awsAccessKeyId?: string;
21
+ awsSecretAccessKey?: string;
22
+ awsSessionToken?: string;
23
+ awsProfile?: string;
24
+ route53HostedZoneId?: string;
25
+ };
26
+ inbound?: {
27
+ url: string;
28
+ hostname?: string;
29
+ spamCheck?: boolean;
30
+ sendRaw?: boolean;
31
+ };
32
+ overwrite?: boolean;
33
+ waitSeconds?: number;
34
+ }
35
+ export interface UiServerOptions {
36
+ /** Default 4545; 0 picks a free port. */
37
+ port?: number;
38
+ token?: string;
39
+ env?: NodeJS.ProcessEnv;
40
+ /** Override provider construction (custom providers, tests). */
41
+ providers?: (req: SetupRequest) => {
42
+ email: EmailProvider;
43
+ dns: DnsProvider;
44
+ } | Promise<{
45
+ email: EmailProvider;
46
+ dns: DnsProvider;
47
+ }>;
48
+ /** Merged into every setup run. */
49
+ setupDefaults?: Partial<SetupOptions>;
50
+ version?: string;
51
+ }
52
+ export interface UiServer {
53
+ /** Open this once; it exchanges the token for a cookie. */
54
+ url: string;
55
+ port: number;
56
+ close(): Promise<void>;
57
+ }
58
+ /**
59
+ * Local web UI. Binds to 127.0.0.1 only. A random token in the first URL is
60
+ * swapped for a SameSite=Strict cookie; the Host header is checked to block
61
+ * DNS rebinding; API calls must be JSON POSTs, which cross-origin pages can't
62
+ * send without a CORS preflight this server never approves.
63
+ */
64
+ export declare function startUiServer(opts?: UiServerOptions): Promise<UiServer>;
65
+ export declare function parseSetupRequest(input: unknown, { requireDns }?: {
66
+ requireDns?: boolean | undefined;
67
+ }): SetupRequest;
@@ -0,0 +1,301 @@
1
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { createServer } from 'node:http';
5
+ import { homedir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { ApiError, apiErrorHint, ConfigError, DnsConflictError } from './errors.js';
8
+ import { checkStatus, setupDomain } from './orchestrator.js';
9
+ import { loadAwsCredentials } from './providers/dns/aws.js';
10
+ import { createEmailProvider, EMAIL_PROVIDERS, isEmailProvider } from './providers/email/factory.js';
11
+ import { createDnsProvider, DNS_PROVIDERS } from './providers/factory.js';
12
+ import { isValidDomain, normalizeName } from './records.js';
13
+ const MAX_BODY_BYTES = 64 * 1024;
14
+ const PAGE = new URL('../../web/index.html', import.meta.url);
15
+ /**
16
+ * Local web UI. Binds to 127.0.0.1 only. A random token in the first URL is
17
+ * swapped for a SameSite=Strict cookie; the Host header is checked to block
18
+ * DNS rebinding; API calls must be JSON POSTs, which cross-origin pages can't
19
+ * send without a CORS preflight this server never approves.
20
+ */
21
+ export async function startUiServer(opts = {}) {
22
+ const env = opts.env ?? process.env;
23
+ const token = opts.token ?? randomBytes(24).toString('base64url');
24
+ const page = await readFile(PAGE, 'utf8');
25
+ let port = 0;
26
+ const cookieName = () => `dkimkit_${port}`;
27
+ const tokenMatches = (candidate) => {
28
+ if (!candidate)
29
+ return false;
30
+ const a = Buffer.from(candidate);
31
+ const b = Buffer.from(token);
32
+ return a.length === b.length && timingSafeEqual(a, b);
33
+ };
34
+ const hostAllowed = (host) => host === `127.0.0.1:${port}` || host === `localhost:${port}` || host === `[::1]:${port}`;
35
+ const cookie = (req) => req.headers.cookie
36
+ ?.split(';')
37
+ .map((c) => c.trim().split('='))
38
+ .find(([k]) => k === cookieName())?.[1];
39
+ const buildProviders = opts.providers ??
40
+ (async (req) => {
41
+ const c = req.credentials ?? {};
42
+ const provider = req.emailProvider ?? 'sendgrid';
43
+ let credentials;
44
+ switch (provider) {
45
+ case 'postmark':
46
+ credentials = { accountToken: c.postmarkAccountToken || env.POSTMARK_ACCOUNT_TOKEN, inboundServerId: c.postmarkInboundServerId || env.POSTMARK_INBOUND_SERVER_ID };
47
+ break;
48
+ case 'mailgun':
49
+ credentials = { apiKey: c.mailgunApiKey || env.MAILGUN_API_KEY, region: c.mailgunRegion || env.MAILGUN_REGION || 'us' };
50
+ break;
51
+ case 'ses': {
52
+ const aws = c.awsAccessKeyId && c.awsSecretAccessKey
53
+ ? { accessKeyId: c.awsAccessKeyId, secretAccessKey: c.awsSecretAccessKey, sessionToken: c.awsSessionToken }
54
+ : await loadAwsCredentials({ profile: c.awsProfile, env });
55
+ credentials = { ...aws, region: c.sesRegion || env.AWS_REGION || env.AWS_DEFAULT_REGION };
56
+ break;
57
+ }
58
+ default:
59
+ credentials = { apiKey: c.sendgridApiKey || env.SENDGRID_API_KEY };
60
+ }
61
+ return {
62
+ email: createEmailProvider(provider, credentials, { subuser: req.onBehalfOf || undefined }),
63
+ dns: createDnsProvider(req.dns, {
64
+ cloudflareApiToken: c.cloudflareApiToken,
65
+ aws: { accessKeyId: c.awsAccessKeyId, secretAccessKey: c.awsSecretAccessKey, sessionToken: c.awsSessionToken },
66
+ awsProfile: c.awsProfile,
67
+ route53HostedZoneId: c.route53HostedZoneId,
68
+ env,
69
+ }),
70
+ };
71
+ });
72
+ const runOptions = (req, email) => {
73
+ if (req.inbound && !email.supportsInbound)
74
+ throw new ConfigError(`Receiving email isn't supported with ${email.label} yet.`);
75
+ return {
76
+ domain: req.domain,
77
+ subdomain: req.subdomain,
78
+ overwrite: req.overwrite,
79
+ inbound: req.inbound && {
80
+ hostname: req.inbound.hostname || email.defaultInboundHostname(req.domain),
81
+ url: req.inbound.url,
82
+ spamCheck: req.inbound.spamCheck,
83
+ sendRaw: req.inbound.sendRaw,
84
+ },
85
+ ...opts.setupDefaults,
86
+ propagation: { timeoutMs: (req.waitSeconds ?? 300) * 1000, ...opts.setupDefaults?.propagation },
87
+ };
88
+ };
89
+ const routes = {
90
+ 'GET /api/config': async (_req, res) => {
91
+ const awsFile = env.AWS_SHARED_CREDENTIALS_FILE ?? join(homedir(), '.aws', 'credentials');
92
+ sendJson(res, 200, {
93
+ version: opts.version,
94
+ providers: DNS_PROVIDERS,
95
+ emailProviders: EMAIL_PROVIDERS,
96
+ env: {
97
+ sendgrid: Boolean(env.SENDGRID_API_KEY),
98
+ postmark: Boolean(env.POSTMARK_ACCOUNT_TOKEN),
99
+ mailgun: Boolean(env.MAILGUN_API_KEY),
100
+ awsRegion: env.AWS_REGION || env.AWS_DEFAULT_REGION || null,
101
+ cloudflare: Boolean(env.CLOUDFLARE_API_TOKEN),
102
+ aws: Boolean(env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY) || existsSync(awsFile),
103
+ },
104
+ });
105
+ },
106
+ 'POST /api/plan': async (req, res) => {
107
+ const body = parseSetupRequest(await readJson(req));
108
+ const { email, dns } = await buildProviders(body);
109
+ let domain;
110
+ let changes = [];
111
+ try {
112
+ await setupDomain(email, dns, {
113
+ ...runOptions(body, email),
114
+ dryRun: true,
115
+ onEvent: (e) => {
116
+ if (e.type === 'domain')
117
+ domain = e.auth;
118
+ if (e.type === 'plan')
119
+ changes = e.changes;
120
+ },
121
+ });
122
+ }
123
+ catch (err) {
124
+ if (!(err instanceof DnsConflictError))
125
+ throw err;
126
+ }
127
+ sendJson(res, 200, { domain, changes, conflicts: changes.some((c) => c.action === 'conflict') });
128
+ },
129
+ 'POST /api/setup': async (req, res) => {
130
+ const body = parseSetupRequest(await readJson(req));
131
+ const { email, dns } = await buildProviders(body);
132
+ const controller = new AbortController();
133
+ res.on('close', () => {
134
+ if (!res.writableFinished)
135
+ controller.abort();
136
+ });
137
+ res.writeHead(200, { 'content-type': 'application/x-ndjson; charset=utf-8', 'cache-control': 'no-store' });
138
+ const write = (line) => res.write(JSON.stringify(line) + '\n');
139
+ try {
140
+ const result = await setupDomain(email, dns, {
141
+ ...runOptions(body, email),
142
+ onEvent: (event) => write({ type: 'event', event }),
143
+ signal: controller.signal,
144
+ });
145
+ write({ type: 'result', result });
146
+ }
147
+ catch (err) {
148
+ if (!controller.signal.aborted)
149
+ write({ type: 'error', ...serializeError(err) });
150
+ }
151
+ finally {
152
+ res.end();
153
+ }
154
+ },
155
+ 'POST /api/status': async (req, res) => {
156
+ const body = parseSetupRequest(await readJson(req), { requireDns: false });
157
+ const { email } = await buildProviders({ ...body, dns: 'manual' });
158
+ sendJson(res, 200, await checkStatus(email, body.domain, body.subdomain));
159
+ },
160
+ };
161
+ const server = createServer(async (req, res) => {
162
+ try {
163
+ if (!hostAllowed(req.headers.host))
164
+ return sendText(res, 403, 'Forbidden host');
165
+ const url = new URL(req.url ?? '/', 'http://localhost');
166
+ if (url.pathname === '/' && req.method === 'GET') {
167
+ const fromQuery = url.searchParams.get('token');
168
+ if (tokenMatches(fromQuery)) {
169
+ res.writeHead(303, {
170
+ location: '/',
171
+ 'set-cookie': `${cookieName()}=${token}; HttpOnly; SameSite=Strict; Path=/`,
172
+ });
173
+ return void res.end();
174
+ }
175
+ if (!tokenMatches(cookie(req)))
176
+ return sendHtml(res, 401, LOCKED_PAGE);
177
+ return sendHtml(res, 200, page);
178
+ }
179
+ const route = routes[`${req.method} ${url.pathname}`];
180
+ if (!route)
181
+ return sendJson(res, 404, { error: 'Not found' });
182
+ if (!tokenMatches(cookie(req))) {
183
+ return sendJson(res, 401, { error: 'Session expired: open the link printed in the terminal again.' });
184
+ }
185
+ if (req.method === 'POST' && !req.headers['content-type']?.startsWith('application/json')) {
186
+ return sendJson(res, 415, { error: 'Expected application/json' });
187
+ }
188
+ await route(req, res);
189
+ }
190
+ catch (err) {
191
+ if (res.headersSent)
192
+ return void res.end();
193
+ const status = err instanceof ConfigError ? 400 : err instanceof ApiError ? 502 : 500;
194
+ sendJson(res, status, serializeError(err));
195
+ }
196
+ });
197
+ await new Promise((resolve, reject) => {
198
+ server.once('error', reject);
199
+ server.listen(opts.port ?? 4545, '127.0.0.1', () => resolve());
200
+ });
201
+ port = server.address().port;
202
+ return {
203
+ url: `http://127.0.0.1:${port}/?token=${token}`,
204
+ port,
205
+ close: () => new Promise((resolve, reject) => {
206
+ server.closeAllConnections();
207
+ server.close((err) => (err ? reject(err) : resolve()));
208
+ }),
209
+ };
210
+ }
211
+ export function parseSetupRequest(input, { requireDns = true } = {}) {
212
+ if (!input || typeof input !== 'object')
213
+ throw new ConfigError('Expected a JSON object.');
214
+ const body = input;
215
+ const domain = typeof body.domain === 'string' ? normalizeName(body.domain) : '';
216
+ if (!isValidDomain(domain))
217
+ throw new ConfigError('Enter a valid domain, like example.com.');
218
+ if (requireDns && typeof body.dns !== 'string')
219
+ throw new ConfigError('Choose a DNS provider.');
220
+ const emailProvider = typeof body.emailProvider === 'string' && body.emailProvider ? body.emailProvider : 'sendgrid';
221
+ if (!isEmailProvider(emailProvider))
222
+ throw new ConfigError(`Email provider must be one of: ${EMAIL_PROVIDERS.join(', ')}.`);
223
+ let inbound;
224
+ if (body.inbound) {
225
+ const url = String(body.inbound.url ?? '');
226
+ if (!/^https:\/\/[^\s]+$/.test(url))
227
+ throw new ConfigError('The inbound webhook URL must start with https://.');
228
+ const hostname = body.inbound.hostname ? normalizeName(String(body.inbound.hostname)) : undefined;
229
+ if (hostname && !isValidDomain(hostname))
230
+ throw new ConfigError('Enter a valid inbound hostname.');
231
+ inbound = { url, hostname, spamCheck: Boolean(body.inbound.spamCheck), sendRaw: Boolean(body.inbound.sendRaw) };
232
+ }
233
+ const waitSeconds = body.waitSeconds === undefined ? undefined : Number(body.waitSeconds);
234
+ if (waitSeconds !== undefined && !(waitSeconds >= 0 && waitSeconds <= 3600)) {
235
+ throw new ConfigError('Wait time must be between 0 and 3600 seconds.');
236
+ }
237
+ const credentials = {};
238
+ for (const [k, v] of Object.entries(body.credentials ?? {})) {
239
+ if (typeof v === 'string' && v.trim())
240
+ credentials[k] = v.trim();
241
+ }
242
+ return {
243
+ domain,
244
+ subdomain: typeof body.subdomain === 'string' && body.subdomain.trim() ? body.subdomain.trim() : undefined,
245
+ dns: String(body.dns ?? 'manual'),
246
+ emailProvider,
247
+ onBehalfOf: typeof body.onBehalfOf === 'string' ? body.onBehalfOf.trim() : undefined,
248
+ credentials,
249
+ inbound,
250
+ overwrite: Boolean(body.overwrite),
251
+ waitSeconds,
252
+ };
253
+ }
254
+ function serializeError(err) {
255
+ if (err instanceof DnsConflictError)
256
+ return { kind: 'conflict', error: err.message, conflicts: err.conflicts };
257
+ if (err instanceof ApiError) {
258
+ return { kind: 'api', error: err.message, provider: err.provider, status: err.status, hint: apiErrorHint(err) };
259
+ }
260
+ if (err instanceof ConfigError)
261
+ return { kind: 'config', error: err.message };
262
+ return { kind: 'error', error: err instanceof Error ? err.message : String(err) };
263
+ }
264
+ async function readJson(req) {
265
+ const chunks = [];
266
+ let size = 0;
267
+ for await (const chunk of req) {
268
+ size += chunk.length;
269
+ if (size > MAX_BODY_BYTES)
270
+ throw new ConfigError('Request body too large.');
271
+ chunks.push(chunk);
272
+ }
273
+ try {
274
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
275
+ }
276
+ catch {
277
+ throw new ConfigError('Request body is not valid JSON.');
278
+ }
279
+ }
280
+ function sendJson(res, status, body) {
281
+ res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
282
+ res.end(JSON.stringify(body));
283
+ }
284
+ function sendText(res, status, text) {
285
+ res.writeHead(status, { 'content-type': 'text/plain; charset=utf-8' });
286
+ res.end(text);
287
+ }
288
+ function sendHtml(res, status, html) {
289
+ res.writeHead(status, {
290
+ 'content-type': 'text/html; charset=utf-8',
291
+ 'cache-control': 'no-store',
292
+ 'content-security-policy': "default-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'",
293
+ 'referrer-policy': 'no-referrer',
294
+ });
295
+ res.end(html);
296
+ }
297
+ const LOCKED_PAGE = `<!doctype html><meta charset="utf-8"><title>DKIMkit</title>
298
+ <body style="font:16px system-ui;max-width:32rem;margin:15vh auto;padding:0 16px;color:#333">
299
+ <h1 style="font-size:1.25rem">Open the link from your terminal</h1>
300
+ <p>For security, this page only opens from the one-time link printed by <code>dkimkit ui</code>.</p></body>`;
301
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ export type RecordType = 'CNAME' | 'TXT' | 'MX';
2
+ export interface DnsRecord {
3
+ type: RecordType;
4
+ /** Fully qualified name, lowercase, no trailing dot. */
5
+ name: string;
6
+ value: string;
7
+ /** MX only. */
8
+ priority?: number;
9
+ /** What the record is for (e.g. "dkim1", "inbound_mx"). Display only. */
10
+ purpose?: string;
11
+ /** Guidance for people adding it by hand (e.g. merging into an existing SPF record). */
12
+ note?: string;
13
+ }
14
+ export interface DomainAuthentication {
15
+ id: string;
16
+ domain: string;
17
+ valid: boolean;
18
+ records: DnsRecord[];
19
+ /** True when this run created it, false when an existing one was reused. */
20
+ created: boolean;
21
+ }
22
+ export interface RecordValidation {
23
+ record: string;
24
+ valid: boolean;
25
+ reason?: string;
26
+ }
27
+ export interface ValidationResult {
28
+ valid: boolean;
29
+ results: RecordValidation[];
30
+ }
31
+ export interface InboundParseOptions {
32
+ hostname: string;
33
+ url: string;
34
+ spamCheck?: boolean;
35
+ sendRaw?: boolean;
36
+ }
37
+ export type InboundAction = 'created' | 'updated' | 'unchanged';
38
+ /** An email service provider (SendGrid, Postmark, Mailgun, Amazon SES) that issues DNS records for a sending domain. */
39
+ export interface EmailProvider {
40
+ readonly name: string;
41
+ /** Human-readable name, e.g. "Amazon SES". */
42
+ readonly label: string;
43
+ /** Whether inbound email (receiving and forwarding to a webhook) is supported. */
44
+ readonly supportsInbound: boolean;
45
+ /** Where inbound mail is received when the caller doesn't choose (e.g. parse.<domain>). */
46
+ defaultInboundHostname(domain: string): string;
47
+ findDomain(domain: string, subdomain?: string): Promise<DomainAuthentication | null>;
48
+ /** Returns the existing authentication for the domain, or creates one. */
49
+ ensureDomain(domain: string, opts?: {
50
+ subdomain?: string;
51
+ }): Promise<DomainAuthentication>;
52
+ getDomain(id: string): Promise<DomainAuthentication>;
53
+ deleteDomain(id: string): Promise<void>;
54
+ validateDomain(id: string): Promise<ValidationResult>;
55
+ /** DNS records needed to route inbound mail for `hostname` to the provider. */
56
+ inboundRecords(hostname: string): DnsRecord[];
57
+ ensureInboundParse(opts: InboundParseOptions): Promise<InboundAction>;
58
+ deleteInboundParse(hostname: string): Promise<void>;
59
+ }
60
+ export type ChangeAction = 'create' | 'update' | 'noop' | 'conflict';
61
+ export interface PlannedChange {
62
+ record: DnsRecord;
63
+ action: ChangeAction;
64
+ /** Provider record id to update, for `update`. */
65
+ existingId?: string;
66
+ reason?: string;
67
+ }
68
+ /** A DNS host that can write records. */
69
+ export interface DnsProvider {
70
+ readonly name: string;
71
+ plan(records: DnsRecord[], opts?: {
72
+ overwrite?: boolean;
73
+ }): Promise<PlannedChange[]>;
74
+ apply(changes: PlannedChange[]): Promise<void>;
75
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ /** Inner content of every `<tag>…</tag>`. Assumes the tag doesn't nest inside itself. */
2
+ export declare function xmlTags(xml: string, tag: string): string[];
3
+ /** Decoded text of the first `<tag>`, if present. */
4
+ export declare function xmlText(xml: string, tag: string): string | undefined;
5
+ export declare function decodeXml(s: string): string;
6
+ export declare function encodeXml(s: string): string;
@@ -0,0 +1,23 @@
1
+ // Just enough XML for the flat, well-known shapes of the Route53 API.
2
+ /** Inner content of every `<tag>…</tag>`. Assumes the tag doesn't nest inside itself. */
3
+ export function xmlTags(xml, tag) {
4
+ const re = new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)</${tag}>`, 'g');
5
+ return [...xml.matchAll(re)].map((m) => m[1]);
6
+ }
7
+ /** Decoded text of the first `<tag>`, if present. */
8
+ export function xmlText(xml, tag) {
9
+ const inner = xmlTags(xml, tag)[0];
10
+ return inner === undefined ? undefined : decodeXml(inner.trim());
11
+ }
12
+ const ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" };
13
+ export function decodeXml(s) {
14
+ return s.replace(/&(#x[0-9a-f]+|#\d+|\w+);/gi, (m, e) => {
15
+ if (e[0] === '#')
16
+ return String.fromCodePoint(e[1] === 'x' || e[1] === 'X' ? parseInt(e.slice(2), 16) : Number(e.slice(1)));
17
+ return ENTITIES[e] ?? m;
18
+ });
19
+ }
20
+ export function encodeXml(s) {
21
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
22
+ }
23
+ //# sourceMappingURL=xml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xml.js","sourceRoot":"","sources":["../../src/xml.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,yFAAyF;AACzF,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,GAAW;IAC9C,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,+BAA+B,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC;AACjD,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,GAAW;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,QAAQ,GAA2B,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AAE9F,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,4BAA4B,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE;QAC9D,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5H,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9H,CAAC"}