@bitvea/feedback-cli 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +13 -1
- package/dist/commands/dev.d.ts +9 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +97 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +33 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/dev/contract.d.ts +94 -0
- package/dist/dev/contract.d.ts.map +1 -0
- package/dist/dev/contract.js +260 -0
- package/dist/dev/contract.js.map +1 -0
- package/dist/dev/origins.d.ts +17 -0
- package/dist/dev/origins.d.ts.map +1 -0
- package/dist/dev/origins.js +90 -0
- package/dist/dev/origins.js.map +1 -0
- package/dist/dev/server.d.ts +35 -0
- package/dist/dev/server.d.ts.map +1 -0
- package/dist/dev/server.js +270 -0
- package/dist/dev/server.js.map +1 -0
- package/dist/dev/store.d.ts +43 -0
- package/dist/dev/store.d.ts.map +1 -0
- package/dist/dev/store.js +215 -0
- package/dist/dev/store.js.map +1 -0
- package/dist/install/env.d.ts +7 -0
- package/dist/install/env.d.ts.map +1 -1
- package/dist/install/env.js +7 -0
- package/dist/install/env.js.map +1 -1
- package/dist/install/strategies.d.ts +13 -0
- package/dist/install/strategies.d.ts.map +1 -1
- package/dist/install/strategies.js +4 -1
- package/dist/install/strategies.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +26 -1
- package/dist/run.js.map +1 -1
- package/package.json +7 -5
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// The local-mode annotation, as the `dev` receiver accepts it.
|
|
2
|
+
//
|
|
3
|
+
// This is the RECEIVING copy of a contract whose sending copy lives in
|
|
4
|
+
// `packages/toolbar/src/local/contract.ts`. Two copies rather than one import,
|
|
5
|
+
// and both halves of that are forced rather than chosen:
|
|
6
|
+
//
|
|
7
|
+
// - this package may not import the toolbar, because the toolbar depends on
|
|
8
|
+
// this package and never the reverse (CLAUDE.md section 1); and
|
|
9
|
+
// - this package has ZERO runtime dependencies, which its publishable check
|
|
10
|
+
// enforces, so the Zod schema a receiver would normally reach for is not
|
|
11
|
+
// available either. The validator is written out by hand, exactly as the
|
|
12
|
+
// browser's is, for the opposite reason: the browser bundle refuses Zod.
|
|
13
|
+
//
|
|
14
|
+
// What holds the two together is the version field, which is what a receiver
|
|
15
|
+
// branches on the day the shape changes, and `contract.test.ts` here, which
|
|
16
|
+
// feeds this validator the same shapes the toolbar's own tests assert it
|
|
17
|
+
// produces. The numbers below are copies too, and each names its original.
|
|
18
|
+
//
|
|
19
|
+
// STRICT ABOUT UNKNOWN FIELDS, on purpose and on both ends: the toolbar's
|
|
20
|
+
// promise is that an annotation carries only what the page itself holds, and a
|
|
21
|
+
// receiver that quietly accepted an extra field would be the half of the
|
|
22
|
+
// contract that stopped noticing when that promise broke.
|
|
23
|
+
/** The contract version. A version this receiver does not know is refused. */
|
|
24
|
+
export const LOCAL_ANNOTATION_VERSION = 1;
|
|
25
|
+
/** Copy of `FEEDBACK_BODY_MAX` in `packages/types/src/feedback.ts`. */
|
|
26
|
+
export const ANNOTATION_TEXT_MAX = 5000;
|
|
27
|
+
/** Copy of `SCREENSHOT_DATA_URL_MAX` in `packages/types/src/feedback.ts`. */
|
|
28
|
+
export const SCREENSHOT_DATA_URL_MAX = 4_000_000;
|
|
29
|
+
/**
|
|
30
|
+
* The largest request body the receiver reads, in bytes.
|
|
31
|
+
*
|
|
32
|
+
* Copy of `LOCAL_ANNOTATION_MAX_BYTES` in the toolbar's contract: the largest
|
|
33
|
+
* picture the toolbar will send plus headroom for the words. Refusing less
|
|
34
|
+
* than this would refuse screenshots the toolbar considers ordinary; the
|
|
35
|
+
* toolbar reads a 413 as "the picture was too big" and retries with the text.
|
|
36
|
+
*/
|
|
37
|
+
export const LOCAL_ANNOTATION_MAX_BYTES = SCREENSHOT_DATA_URL_MAX + 500_000;
|
|
38
|
+
/** How many owners the component chain may carry, nearest first. */
|
|
39
|
+
export const COMPONENT_OWNERS_MAX = 10;
|
|
40
|
+
/**
|
|
41
|
+
* An annotation id. It is also the file name on disk, so this pattern is the
|
|
42
|
+
* whole of the path-traversal defence: no dot, no slash, nothing else.
|
|
43
|
+
*/
|
|
44
|
+
export const ANNOTATION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
45
|
+
/** The only picture a sink is sent: a PNG, inline. */
|
|
46
|
+
export const PNG_DATA_URL_PREFIX = "data:image/png;base64,";
|
|
47
|
+
const KNOWN_KEYS = new Set([
|
|
48
|
+
"v",
|
|
49
|
+
"id",
|
|
50
|
+
"kind",
|
|
51
|
+
"text",
|
|
52
|
+
"url",
|
|
53
|
+
"selector",
|
|
54
|
+
"pin",
|
|
55
|
+
"viewport",
|
|
56
|
+
"timestamp",
|
|
57
|
+
"userAgent",
|
|
58
|
+
"screenshot",
|
|
59
|
+
"component",
|
|
60
|
+
]);
|
|
61
|
+
function isRecord(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
function isFiniteNumber(value) {
|
|
65
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
66
|
+
}
|
|
67
|
+
function isFraction(value) {
|
|
68
|
+
return isFiniteNumber(value) && value >= 0 && value <= 1;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A C0 control character or DEL.
|
|
72
|
+
*
|
|
73
|
+
* Refused in every one-line field, because those fields end up on one line of
|
|
74
|
+
* `inbox.md` and of the receiver's terminal, and a newline or an ESC there is
|
|
75
|
+
* a way to write markup or escape sequences that read as the receiver's own.
|
|
76
|
+
* `new URL()` is no check on it: the WHATWG parser silently STRIPS tabs and
|
|
77
|
+
* newlines before parsing, so the raw string validates with them inside.
|
|
78
|
+
*/
|
|
79
|
+
export function isControlCharacter(code) {
|
|
80
|
+
return code <= 0x1f || code === 0x7f;
|
|
81
|
+
}
|
|
82
|
+
function isOneLine(value) {
|
|
83
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
84
|
+
if (isControlCharacter(value.charCodeAt(i)))
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* `value` with every control character replaced, except the ones in `keep`.
|
|
91
|
+
* For the two places the receiver writes somebody else's prose: the terminal
|
|
92
|
+
* and `inbox.md`.
|
|
93
|
+
*/
|
|
94
|
+
export function replaceControlCharacters(value, replacement, keep = "") {
|
|
95
|
+
let out = "";
|
|
96
|
+
for (const char of value) {
|
|
97
|
+
out +=
|
|
98
|
+
isControlCharacter(char.charCodeAt(0)) && !keep.includes(char)
|
|
99
|
+
? replacement
|
|
100
|
+
: char;
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function isHttpUrl(value) {
|
|
105
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (!isOneLine(value))
|
|
109
|
+
return false;
|
|
110
|
+
try {
|
|
111
|
+
const url = new URL(value);
|
|
112
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Every problem with `value` as an incoming annotation, or an empty list when
|
|
120
|
+
* it is one. The same rules, in the same order, as the toolbar's
|
|
121
|
+
* `validateLocalAnnotation(value, "send")`.
|
|
122
|
+
*/
|
|
123
|
+
export function validateAnnotation(value) {
|
|
124
|
+
const errors = [];
|
|
125
|
+
if (!isRecord(value))
|
|
126
|
+
return ["annotation: must be an object"];
|
|
127
|
+
for (const key of Object.keys(value)) {
|
|
128
|
+
if (!KNOWN_KEYS.has(key))
|
|
129
|
+
errors.push(`${key}: unknown field`);
|
|
130
|
+
}
|
|
131
|
+
if (value["v"] !== LOCAL_ANNOTATION_VERSION) {
|
|
132
|
+
errors.push(`v: must be ${LOCAL_ANNOTATION_VERSION}`);
|
|
133
|
+
}
|
|
134
|
+
const id = value["id"];
|
|
135
|
+
if (typeof id !== "string" || !ANNOTATION_ID_PATTERN.test(id)) {
|
|
136
|
+
errors.push("id: must be 1-64 characters of A-Z, a-z, 0-9, _ or -");
|
|
137
|
+
}
|
|
138
|
+
const kind = value["kind"];
|
|
139
|
+
if (kind !== "pin" && kind !== "note") {
|
|
140
|
+
errors.push('kind: must be "pin" or "note"');
|
|
141
|
+
}
|
|
142
|
+
const text = value["text"];
|
|
143
|
+
if (typeof text !== "string" ||
|
|
144
|
+
text.trim().length === 0 ||
|
|
145
|
+
text.length > ANNOTATION_TEXT_MAX) {
|
|
146
|
+
errors.push(`text: must be 1-${ANNOTATION_TEXT_MAX} characters`);
|
|
147
|
+
}
|
|
148
|
+
if (!isHttpUrl(value["url"]))
|
|
149
|
+
errors.push("url: must be an http(s) URL");
|
|
150
|
+
const selector = value["selector"];
|
|
151
|
+
const pin = value["pin"];
|
|
152
|
+
if (selector === null) {
|
|
153
|
+
if (pin !== null)
|
|
154
|
+
errors.push("pin: must be null when selector is null");
|
|
155
|
+
if (kind === "pin")
|
|
156
|
+
errors.push("selector: a pin must carry a selector");
|
|
157
|
+
}
|
|
158
|
+
else if (typeof selector !== "string" ||
|
|
159
|
+
selector.length === 0 ||
|
|
160
|
+
selector.length > 2000 ||
|
|
161
|
+
!isOneLine(selector)) {
|
|
162
|
+
errors.push("selector: must be a 1-2000 character string or null");
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
if (kind === "note")
|
|
166
|
+
errors.push("selector: a note must not carry one");
|
|
167
|
+
if (!isRecord(pin)) {
|
|
168
|
+
errors.push("pin: must be an object when selector is set");
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
if (!isFraction(pin["offsetX"]) || !isFraction(pin["offsetY"])) {
|
|
172
|
+
errors.push("pin: offsetX and offsetY must be between 0 and 1");
|
|
173
|
+
}
|
|
174
|
+
const box = pin["box"];
|
|
175
|
+
if (box !== null &&
|
|
176
|
+
!(isRecord(box) &&
|
|
177
|
+
isFiniteNumber(box["x"]) &&
|
|
178
|
+
isFiniteNumber(box["y"]) &&
|
|
179
|
+
isFiniteNumber(box["width"]) &&
|
|
180
|
+
box["width"] >= 0 &&
|
|
181
|
+
isFiniteNumber(box["height"]) &&
|
|
182
|
+
box["height"] >= 0)) {
|
|
183
|
+
errors.push("pin.box: must be null or {x, y, width, height}");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const viewport = value["viewport"];
|
|
188
|
+
if (!(isRecord(viewport) &&
|
|
189
|
+
isFiniteNumber(viewport["width"]) &&
|
|
190
|
+
viewport["width"] > 0 &&
|
|
191
|
+
isFiniteNumber(viewport["height"]) &&
|
|
192
|
+
viewport["height"] > 0 &&
|
|
193
|
+
isFiniteNumber(viewport["devicePixelRatio"]) &&
|
|
194
|
+
viewport["devicePixelRatio"] > 0)) {
|
|
195
|
+
errors.push("viewport: must be {width, height, devicePixelRatio}");
|
|
196
|
+
}
|
|
197
|
+
const timestamp = value["timestamp"];
|
|
198
|
+
if (typeof timestamp !== "string" ||
|
|
199
|
+
!isOneLine(timestamp) ||
|
|
200
|
+
Number.isNaN(Date.parse(timestamp))) {
|
|
201
|
+
errors.push("timestamp: must be an ISO 8601 date");
|
|
202
|
+
}
|
|
203
|
+
const userAgent = value["userAgent"];
|
|
204
|
+
if (typeof userAgent !== "string" ||
|
|
205
|
+
userAgent.length > 1000 ||
|
|
206
|
+
!isOneLine(userAgent)) {
|
|
207
|
+
errors.push("userAgent: must be a string of at most 1000 characters");
|
|
208
|
+
}
|
|
209
|
+
const screenshot = value["screenshot"];
|
|
210
|
+
if (screenshot !== null &&
|
|
211
|
+
(typeof screenshot !== "string" ||
|
|
212
|
+
!screenshot.startsWith(PNG_DATA_URL_PREFIX) ||
|
|
213
|
+
screenshot.length > SCREENSHOT_DATA_URL_MAX)) {
|
|
214
|
+
errors.push("screenshot: must be null or a PNG data URL");
|
|
215
|
+
}
|
|
216
|
+
const component = value["component"];
|
|
217
|
+
if (component !== undefined) {
|
|
218
|
+
const owners = isRecord(component) ? component["owners"] : undefined;
|
|
219
|
+
if (!(isRecord(component) &&
|
|
220
|
+
typeof component["name"] === "string" &&
|
|
221
|
+
component["name"].length > 0 &&
|
|
222
|
+
component["name"].length <= 200 &&
|
|
223
|
+
isOneLine(component["name"]) &&
|
|
224
|
+
Array.isArray(owners) &&
|
|
225
|
+
owners.length <= COMPONENT_OWNERS_MAX &&
|
|
226
|
+
owners.every((owner) => typeof owner === "string" &&
|
|
227
|
+
owner.length > 0 &&
|
|
228
|
+
owner.length <= 200 &&
|
|
229
|
+
isOneLine(owner)))) {
|
|
230
|
+
errors.push("component: must be {name, owners[]}");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return errors;
|
|
234
|
+
}
|
|
235
|
+
/** The eight bytes every PNG starts with. */
|
|
236
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
237
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
238
|
+
/**
|
|
239
|
+
* The PNG a validated screenshot data URL carries, or null when the bytes are
|
|
240
|
+
* not a PNG.
|
|
241
|
+
*
|
|
242
|
+
* The prefix alone is a claim, not a fact: this is about to become a file in
|
|
243
|
+
* somebody's repository with a `.png` name, so the bytes are checked for the
|
|
244
|
+
* signature rather than trusted to match the label.
|
|
245
|
+
*/
|
|
246
|
+
export function decodePngDataUrl(dataUrl) {
|
|
247
|
+
if (!dataUrl.startsWith(PNG_DATA_URL_PREFIX))
|
|
248
|
+
return null;
|
|
249
|
+
const payload = dataUrl.slice(PNG_DATA_URL_PREFIX.length);
|
|
250
|
+
if (payload.length === 0 || payload.length % 4 !== 0 || !BASE64.test(payload)) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const bytes = Buffer.from(payload, "base64");
|
|
254
|
+
if (bytes.length < PNG_SIGNATURE.length)
|
|
255
|
+
return null;
|
|
256
|
+
return bytes.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)
|
|
257
|
+
? bytes
|
|
258
|
+
: null;
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/dev/contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Whether a URL hostname (as `new URL().hostname` spells it) is this machine. */
|
|
2
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Normalise an origin the developer typed for --allow-origin, or throw a
|
|
5
|
+
* message that says what was wrong with it.
|
|
6
|
+
*
|
|
7
|
+
* An origin is a scheme, a host and a port and nothing else: a path would be
|
|
8
|
+
* silently ignored by the comparison below, which is how somebody believes they
|
|
9
|
+
* allowed one page and in fact allowed a whole site. `*` is refused by name,
|
|
10
|
+
* because this receiver writes files into a repository.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseAllowedOrigin(raw: string): string;
|
|
13
|
+
/** Whether a request's `Origin` header names a page allowed to send here. */
|
|
14
|
+
export declare function isAllowedOrigin(origin: string, extra: ReadonlySet<string>): boolean;
|
|
15
|
+
/** Whether a request's `Host` header addresses this machine. */
|
|
16
|
+
export declare function isLoopbackHostHeader(host: string | undefined): boolean;
|
|
17
|
+
//# sourceMappingURL=origins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../src/dev/origins.ts"],"names":[],"mappings":"AAoBA,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8BtD;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GACzB,OAAO,CAaT;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAStE"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Who may talk to the `dev` receiver, asked two ways.
|
|
2
|
+
//
|
|
3
|
+
// A process listening on a loopback port is reachable from EVERY page open in
|
|
4
|
+
// the developer's browser, not only from the app they are building: any site
|
|
5
|
+
// can `fetch("http://127.0.0.1:4477/...")`. So binding to 127.0.0.1 keeps the
|
|
6
|
+
// room out and does nothing about the tabs. These two checks are what does.
|
|
7
|
+
//
|
|
8
|
+
// - ORIGIN is the page asking. It must be this machine (localhost,
|
|
9
|
+
// 127.0.0.1 or [::1], any port and either scheme) or one the developer
|
|
10
|
+
// named with --allow-origin. Everything else is refused before a byte of
|
|
11
|
+
// the body is read, and gets no CORS headers, so a browser will not hand
|
|
12
|
+
// the page the answer either.
|
|
13
|
+
// - HOST is the name the request was addressed to. It must be a loopback
|
|
14
|
+
// name. That closes DNS rebinding: a hostile page that re-points its own
|
|
15
|
+
// domain at 127.0.0.1 becomes same-origin with this receiver and sends no
|
|
16
|
+
// Origin on a GET, but it still addresses the request to its own domain,
|
|
17
|
+
// and that is the name this check refuses.
|
|
18
|
+
const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
19
|
+
/** Whether a URL hostname (as `new URL().hostname` spells it) is this machine. */
|
|
20
|
+
export function isLoopbackHostname(hostname) {
|
|
21
|
+
return LOOPBACK_HOSTNAMES.has(hostname.toLowerCase().replace(/\.$/, ""));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Normalise an origin the developer typed for --allow-origin, or throw a
|
|
25
|
+
* message that says what was wrong with it.
|
|
26
|
+
*
|
|
27
|
+
* An origin is a scheme, a host and a port and nothing else: a path would be
|
|
28
|
+
* silently ignored by the comparison below, which is how somebody believes they
|
|
29
|
+
* allowed one page and in fact allowed a whole site. `*` is refused by name,
|
|
30
|
+
* because this receiver writes files into a repository.
|
|
31
|
+
*/
|
|
32
|
+
export function parseAllowedOrigin(raw) {
|
|
33
|
+
const value = raw.trim();
|
|
34
|
+
if (value === "*" || value === "null") {
|
|
35
|
+
throw new Error(`--allow-origin ${value} is refused: name the one origin that may send ` +
|
|
36
|
+
"annotations, such as https://preview.example.com.");
|
|
37
|
+
}
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(value);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error(`--allow-origin ${JSON.stringify(raw)} is not a URL.`);
|
|
44
|
+
}
|
|
45
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
46
|
+
throw new Error(`--allow-origin ${JSON.stringify(raw)} must be http(s).`);
|
|
47
|
+
}
|
|
48
|
+
if (url.username !== "" ||
|
|
49
|
+
url.password !== "" ||
|
|
50
|
+
(url.pathname !== "/" && url.pathname !== "") ||
|
|
51
|
+
url.search !== "" ||
|
|
52
|
+
url.hash !== "") {
|
|
53
|
+
throw new Error(`--allow-origin ${JSON.stringify(raw)} must be an origin only ` +
|
|
54
|
+
`(scheme, host and port), such as ${url.origin}.`);
|
|
55
|
+
}
|
|
56
|
+
return url.origin;
|
|
57
|
+
}
|
|
58
|
+
/** Whether a request's `Origin` header names a page allowed to send here. */
|
|
59
|
+
export function isAllowedOrigin(origin, extra) {
|
|
60
|
+
if (extra.has(origin))
|
|
61
|
+
return true;
|
|
62
|
+
let url;
|
|
63
|
+
try {
|
|
64
|
+
url = new URL(origin);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
// `new URL("http://x").origin` round-trips a real Origin header exactly; a
|
|
70
|
+
// header with a path or credentials in it is not one a browser sent.
|
|
71
|
+
if (url.origin !== origin)
|
|
72
|
+
return false;
|
|
73
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
74
|
+
return false;
|
|
75
|
+
return isLoopbackHostname(url.hostname);
|
|
76
|
+
}
|
|
77
|
+
/** Whether a request's `Host` header addresses this machine. */
|
|
78
|
+
export function isLoopbackHostHeader(host) {
|
|
79
|
+
if (!host || /[@/\\?#\s]/.test(host))
|
|
80
|
+
return false;
|
|
81
|
+
let url;
|
|
82
|
+
try {
|
|
83
|
+
url = new URL(`http://${host}`);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return isLoopbackHostname(url.hostname);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=origins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origins.js","sourceRoot":"","sources":["../../src/dev/origins.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** The only address this receiver ever binds. */
|
|
2
|
+
export declare const DEV_HOST = "127.0.0.1";
|
|
3
|
+
/**
|
|
4
|
+
* The port the toolbar posts to when nothing says otherwise. Copy of
|
|
5
|
+
* `DEFAULT_LOOPBACK_PORT` in `packages/toolbar/src/gating.ts`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEV_DEFAULT_PORT = 4477;
|
|
8
|
+
export declare const ANNOTATIONS_PATH = "/v1/annotations";
|
|
9
|
+
export interface DevReceiverOptions {
|
|
10
|
+
/** The repository root; files go under `<root>/.bitvea/feedback`. */
|
|
11
|
+
root: string;
|
|
12
|
+
/** The port to listen on. 0 picks a free one, which is what tests use. */
|
|
13
|
+
port?: number;
|
|
14
|
+
/** Origins beyond this machine's that may send, already normalised. */
|
|
15
|
+
allowOrigins?: Iterable<string>;
|
|
16
|
+
/** The body limit in bytes. Defaults to the contract's. */
|
|
17
|
+
maxBytes?: number;
|
|
18
|
+
/** Where one line per event goes. Defaults to silence. */
|
|
19
|
+
log?: (line: string) => void;
|
|
20
|
+
/** Where refusals go. Defaults to silence. */
|
|
21
|
+
warn?: (line: string) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface DevReceiver {
|
|
24
|
+
/** `http://127.0.0.1:<port>`. */
|
|
25
|
+
origin: string;
|
|
26
|
+
/** The collection URL, which is the toolbar's sink URL. */
|
|
27
|
+
sinkUrl: string;
|
|
28
|
+
port: number;
|
|
29
|
+
/** The absolute directory annotations are written to. */
|
|
30
|
+
dir: string;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** Start the receiver. Resolves once it is listening. */
|
|
34
|
+
export declare function startDevReceiver(options: DevReceiverOptions): Promise<DevReceiver>;
|
|
35
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev/server.ts"],"names":[],"mappings":"AAgDA,iDAAiD;AACjD,eAAO,MAAM,QAAQ,cAAc,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAIlD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,8CAA8C;IAC9C,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAsED,yDAAyD;AACzD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,WAAW,CAAC,CA8KtB"}
|