@gusnips/one-click-dns 0.1.0 → 0.1.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.
- package/README.md +10 -1
- package/dist/bounded-fetch-nozW2egX.d.cts +5 -0
- package/dist/bounded-fetch-nozW2egX.d.ts +5 -0
- package/dist/chunk-56ZUD4EQ.js +525 -0
- package/dist/server/index.cjs +321 -21
- package/dist/server/index.d.cts +3 -2
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.js +1 -1
- package/dist/writers/index.cjs +327 -21
- package/dist/writers/index.d.cts +9 -2
- package/dist/writers/index.d.ts +9 -2
- package/dist/writers/index.js +7 -1
- package/package.json +2 -2
- package/dist/chunk-FGM7S7GQ.js +0 -222
package/dist/chunk-FGM7S7GQ.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeDnsName
|
|
3
|
-
} from "./chunk-P2O7INDB.js";
|
|
4
|
-
|
|
5
|
-
// src/bounded-fetch.ts
|
|
6
|
-
import { readBounded } from "@gusnips/server";
|
|
7
|
-
async function fetchBounded(doFetch, url, init, limits) {
|
|
8
|
-
try {
|
|
9
|
-
const res = await doFetch(url, {
|
|
10
|
-
...init,
|
|
11
|
-
redirect: "manual",
|
|
12
|
-
signal: AbortSignal.timeout(limits.timeoutMs)
|
|
13
|
-
});
|
|
14
|
-
if (res.type === "opaqueredirect" || res.status >= 300 && res.status < 400) {
|
|
15
|
-
await res.body?.cancel().catch(() => void 0);
|
|
16
|
-
return { status: res.status || 302, text: "" };
|
|
17
|
-
}
|
|
18
|
-
const { bytes, truncated } = await readBounded(res.body, limits.maxBytes);
|
|
19
|
-
if (truncated) return null;
|
|
20
|
-
return { status: res.status, text: new TextDecoder().decode(bytes) };
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// src/guards.ts
|
|
27
|
-
function isObject(value) {
|
|
28
|
-
return typeof value === "object" && value !== null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// src/writers/types.ts
|
|
32
|
-
var DnsWriteError = class extends Error {
|
|
33
|
-
constructor(code, params = {}) {
|
|
34
|
-
super(MESSAGES[code](params));
|
|
35
|
-
this.code = code;
|
|
36
|
-
this.params = params;
|
|
37
|
-
this.name = "DnsWriteError";
|
|
38
|
-
}
|
|
39
|
-
code;
|
|
40
|
-
params;
|
|
41
|
-
};
|
|
42
|
-
var MESSAGES = {
|
|
43
|
-
token_invalid: () => "The DNS host didn't accept that token",
|
|
44
|
-
zone_read_missing: () => "That token can't read your domains",
|
|
45
|
-
dns_edit_missing: () => "That token can't change DNS records",
|
|
46
|
-
zone_not_found: (p) => `No zone holds ${p.hostname ?? "that name"}`,
|
|
47
|
-
conflict: (p) => `${p.hostname ?? "That name"} already has a record (type ${p.type ?? "?"})`,
|
|
48
|
-
unavailable: () => "Couldn't reach the DNS host"
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// src/writers/cloudflare.ts
|
|
52
|
-
var API = "https://api.cloudflare.com/client/v4";
|
|
53
|
-
var TIMEOUT_MS = 1e4;
|
|
54
|
-
var MAX_BODY_BYTES = 256 * 1024;
|
|
55
|
-
var TOKEN_CODES = /* @__PURE__ */ new Set([1e3, 6003, 6111, 1e4]);
|
|
56
|
-
var CONFLICT_CODES = /* @__PURE__ */ new Set([81053, 81054, 81057]);
|
|
57
|
-
var ID = /^[A-Za-z0-9]{1,64}$/;
|
|
58
|
-
var RECORD_TYPE = /^[A-Z0-9]{1,12}$/;
|
|
59
|
-
function readEnvelope(json) {
|
|
60
|
-
if (!isObject(json) || typeof json.success !== "boolean") return null;
|
|
61
|
-
const codes = [];
|
|
62
|
-
if (Array.isArray(json.errors)) {
|
|
63
|
-
for (const error of json.errors) {
|
|
64
|
-
if (!isObject(error)) return null;
|
|
65
|
-
if (typeof error.code === "number") codes.push(error.code);
|
|
66
|
-
if (Array.isArray(error.error_chain)) {
|
|
67
|
-
for (const link of error.error_chain)
|
|
68
|
-
if (isObject(link) && typeof link.code === "number") codes.push(link.code);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return { success: json.success, codes, result: json.result };
|
|
73
|
-
}
|
|
74
|
-
async function call(doFetch, token, path, send) {
|
|
75
|
-
const res = await fetchBounded(
|
|
76
|
-
doFetch,
|
|
77
|
-
`${API}${path}`,
|
|
78
|
-
{
|
|
79
|
-
method: send?.method ?? "GET",
|
|
80
|
-
headers: {
|
|
81
|
-
Authorization: `Bearer ${token}`,
|
|
82
|
-
Accept: "application/json",
|
|
83
|
-
...send ? { "Content-Type": "application/json" } : {}
|
|
84
|
-
},
|
|
85
|
-
body: send ? JSON.stringify(send.body) : void 0
|
|
86
|
-
},
|
|
87
|
-
{ timeoutMs: TIMEOUT_MS, maxBytes: MAX_BODY_BYTES }
|
|
88
|
-
);
|
|
89
|
-
if (!res) return { status: 0, body: null };
|
|
90
|
-
let json = null;
|
|
91
|
-
try {
|
|
92
|
-
json = JSON.parse(res.text);
|
|
93
|
-
} catch {
|
|
94
|
-
}
|
|
95
|
-
return { status: res.status, body: readEnvelope(json) };
|
|
96
|
-
}
|
|
97
|
-
function refusal(answer, stage, subject) {
|
|
98
|
-
const codes = answer.body?.codes ?? [];
|
|
99
|
-
const refusedToken = answer.status === 401 || codes.some((c) => TOKEN_CODES.has(c));
|
|
100
|
-
if (stage === "zones" && refusedToken) return new DnsWriteError("token_invalid");
|
|
101
|
-
if (codes.some((c) => CONFLICT_CODES.has(c)))
|
|
102
|
-
return new DnsWriteError("conflict", { ...subject });
|
|
103
|
-
if (answer.status === 403 || refusedToken) {
|
|
104
|
-
if (stage === "zones") return new DnsWriteError("zone_read_missing");
|
|
105
|
-
return new DnsWriteError("dns_edit_missing");
|
|
106
|
-
}
|
|
107
|
-
return new DnsWriteError("unavailable");
|
|
108
|
-
}
|
|
109
|
-
function resultOf(answer, stage, subject) {
|
|
110
|
-
const ok = answer.status >= 200 && answer.status < 300 && answer.body?.success === true;
|
|
111
|
-
if (!ok) throw refusal(answer, stage, subject);
|
|
112
|
-
return answer.body?.result;
|
|
113
|
-
}
|
|
114
|
-
function zonesOf(result) {
|
|
115
|
-
if (!Array.isArray(result)) throw new DnsWriteError("unavailable");
|
|
116
|
-
return result.map((row) => {
|
|
117
|
-
if (!isObject(row) || typeof row.id !== "string" || typeof row.name !== "string")
|
|
118
|
-
throw new DnsWriteError("unavailable");
|
|
119
|
-
if (!ID.test(row.id)) throw new DnsWriteError("unavailable");
|
|
120
|
-
return { id: row.id, name: row.name };
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
function recordsOf(result) {
|
|
124
|
-
if (!Array.isArray(result)) throw new DnsWriteError("unavailable");
|
|
125
|
-
return result.map((row) => {
|
|
126
|
-
if (!isObject(row) || typeof row.id !== "string" || typeof row.type !== "string" || typeof row.content !== "string" || !ID.test(row.id) || !RECORD_TYPE.test(row.type))
|
|
127
|
-
throw new DnsWriteError("unavailable");
|
|
128
|
-
return {
|
|
129
|
-
id: row.id,
|
|
130
|
-
type: row.type,
|
|
131
|
-
content: row.content,
|
|
132
|
-
proxied: typeof row.proxied === "boolean" ? row.proxied : void 0
|
|
133
|
-
};
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
function zoneCandidates(hostname) {
|
|
137
|
-
const labels = hostname.split(".");
|
|
138
|
-
const names = [];
|
|
139
|
-
for (let start = 0; start < labels.length - 1; start++) names.push(labels.slice(start).join("."));
|
|
140
|
-
return names;
|
|
141
|
-
}
|
|
142
|
-
var unquoted = (content) => content.replace(/"\s+"/g, "").replace(/^"|"$/g, "");
|
|
143
|
-
var sameValue = (record, existing) => record.type === "CNAME" ? normalizeDnsName(existing.content) === normalizeDnsName(record.value) : unquoted(existing.content) === record.value;
|
|
144
|
-
var isSpf = (text) => /^v=spf1(?:\s|$)/i.test(unquoted(text).trim());
|
|
145
|
-
function blocks(record, existing) {
|
|
146
|
-
if (record.type === "CNAME" || existing.type === "CNAME") return true;
|
|
147
|
-
return existing.type === "TXT" && isSpf(record.value) && isSpf(existing.content);
|
|
148
|
-
}
|
|
149
|
-
var cloudflare = {
|
|
150
|
-
host: "cloudflare",
|
|
151
|
-
/** Add the record (DNS only) to the Cloudflare zone that holds its name. An identical record
|
|
152
|
-
* is left as it is; the same CNAME behind the proxy gets the proxy turned off. */
|
|
153
|
-
async addRecord({ token, record, comment, fetch: doFetch = fetch }) {
|
|
154
|
-
const hostname = normalizeDnsName(record.name);
|
|
155
|
-
const subject = { hostname, type: record.type };
|
|
156
|
-
let zoneId;
|
|
157
|
-
for (const candidate of zoneCandidates(hostname)) {
|
|
158
|
-
const query = new URLSearchParams({ name: candidate });
|
|
159
|
-
const zones = zonesOf(
|
|
160
|
-
resultOf(await call(doFetch, token, `/zones?${query}`), "zones", subject)
|
|
161
|
-
);
|
|
162
|
-
zoneId = zones.find((z) => normalizeDnsName(z.name) === candidate)?.id;
|
|
163
|
-
if (zoneId) break;
|
|
164
|
-
}
|
|
165
|
-
if (!zoneId) throw new DnsWriteError("zone_not_found", { hostname });
|
|
166
|
-
const existing = recordsOf(
|
|
167
|
-
resultOf(
|
|
168
|
-
await call(
|
|
169
|
-
doFetch,
|
|
170
|
-
token,
|
|
171
|
-
`/zones/${zoneId}/dns_records?${new URLSearchParams({ name: hostname })}`
|
|
172
|
-
),
|
|
173
|
-
"records",
|
|
174
|
-
subject
|
|
175
|
-
)
|
|
176
|
-
);
|
|
177
|
-
const ours = existing.find((r) => r.type === record.type && sameValue(record, r));
|
|
178
|
-
if (ours) {
|
|
179
|
-
if (record.type !== "CNAME" || ours.proxied !== true) return "unchanged";
|
|
180
|
-
resultOf(
|
|
181
|
-
await call(doFetch, token, `/zones/${zoneId}/dns_records/${ours.id}`, {
|
|
182
|
-
method: "PATCH",
|
|
183
|
-
body: { proxied: false }
|
|
184
|
-
}),
|
|
185
|
-
"records",
|
|
186
|
-
subject
|
|
187
|
-
);
|
|
188
|
-
return "unproxied";
|
|
189
|
-
}
|
|
190
|
-
const blocker = existing.find((r) => blocks(record, r));
|
|
191
|
-
if (blocker) throw new DnsWriteError("conflict", { hostname, type: blocker.type });
|
|
192
|
-
resultOf(
|
|
193
|
-
await call(doFetch, token, `/zones/${zoneId}/dns_records`, {
|
|
194
|
-
method: "POST",
|
|
195
|
-
body: {
|
|
196
|
-
type: record.type,
|
|
197
|
-
name: hostname,
|
|
198
|
-
content: record.value,
|
|
199
|
-
// A proxied record hides the CNAME our checker reads.
|
|
200
|
-
...record.type === "CNAME" ? { proxied: false } : {},
|
|
201
|
-
// 1 is Cloudflare's "automatic".
|
|
202
|
-
ttl: 1,
|
|
203
|
-
...comment ? { comment } : {}
|
|
204
|
-
}
|
|
205
|
-
}),
|
|
206
|
-
"records",
|
|
207
|
-
subject
|
|
208
|
-
);
|
|
209
|
-
return "created";
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// src/writers/index.ts
|
|
214
|
-
var writers = { cloudflare };
|
|
215
|
-
|
|
216
|
-
export {
|
|
217
|
-
fetchBounded,
|
|
218
|
-
isObject,
|
|
219
|
-
DnsWriteError,
|
|
220
|
-
cloudflare,
|
|
221
|
-
writers
|
|
222
|
-
};
|