@fixback/expo 0.2.0 → 0.3.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/README.md +72 -8
- package/dist/FeedbackModal.d.ts +10 -2
- package/dist/FeedbackModal.js +84 -36
- package/dist/FeedbackModal.js.map +1 -0
- package/dist/FixbackProvider.d.ts +12 -0
- package/dist/FixbackProvider.js +46 -23
- package/dist/FixbackProvider.js.map +1 -0
- package/dist/adapters.js +73 -23
- package/dist/adapters.js.map +1 -0
- package/dist/breadcrumbs.d.ts +21 -222
- package/dist/breadcrumbs.js +45 -605
- package/dist/breadcrumbs.js.map +1 -0
- package/dist/client.d.ts +65 -67
- package/dist/client.js +288 -151
- package/dist/client.js.map +1 -0
- package/dist/connect.d.ts +60 -0
- package/dist/connect.js +63 -0
- package/dist/connect.js.map +1 -0
- package/dist/error-capture.d.ts +28 -78
- package/dist/error-capture.js +76 -294
- package/dist/error-capture.js.map +1 -0
- package/dist/http.d.ts +10 -25
- package/dist/http.js +15 -12
- package/dist/http.js.map +1 -0
- package/dist/identity.d.ts +11 -11
- package/dist/identity.js +23 -28
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +22 -5
- package/dist/index.js.map +1 -0
- package/dist/origin.d.ts +21 -0
- package/dist/origin.js +45 -0
- package/dist/origin.js.map +1 -0
- package/dist/package.json +3 -0
- package/dist/report.d.ts +28 -63
- package/dist/report.js +33 -25
- package/dist/report.js.map +1 -0
- package/dist/reporter-session-store.d.ts +27 -0
- package/dist/reporter-session-store.js +53 -0
- package/dist/reporter-session-store.js.map +1 -0
- package/dist/shake.d.ts +26 -0
- package/dist/shake.js +32 -7
- package/dist/shake.js.map +1 -0
- package/dist/submit.d.ts +21 -12
- package/dist/submit.js +24 -41
- package/dist/submit.js.map +1 -0
- package/dist/tokens.js +5 -1
- package/dist/tokens.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +5 -1
- package/dist/version.js.map +1 -0
- package/package.json +14 -7
- package/dist/auto-report-backoff.d.ts +0 -42
- package/dist/auto-report-backoff.js +0 -67
- package/dist/boot.d.ts +0 -65
- package/dist/boot.js +0 -60
- package/dist/scrub.d.ts +0 -55
- package/dist/scrub.js +0 -184
package/dist/boot.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The ingest **boot** wire-contract, vendored — kept file-parallel with
|
|
3
|
-
* `packages/sdk/src/boot.ts` (ADR-0021: a server contract change must be
|
|
4
|
-
* mirrored in both clients).
|
|
5
|
-
*
|
|
6
|
-
* The one mobile difference: a native HTTP stack attaches no `Origin` header,
|
|
7
|
-
* so the SDK sends the configured `origin` explicitly — the server reads it to
|
|
8
|
-
* decide `originAllowed` exactly as it does for a browser request (spec 0004 §A).
|
|
9
|
-
*/
|
|
10
|
-
import { globalFetch } from "./http";
|
|
11
|
-
/** Join an API base URL with the boot path, tolerating a trailing slash. */
|
|
12
|
-
export function bootEndpoint(apiUrl) {
|
|
13
|
-
return `${apiUrl.replace(/\/+$/, "")}/api/ingest/boot`;
|
|
14
|
-
}
|
|
15
|
-
/** Narrow an unknown JSON body to a `BootAnswer` before the SDK trusts it. */
|
|
16
|
-
function isBootAnswer(value) {
|
|
17
|
-
if (typeof value !== "object" || value === null)
|
|
18
|
-
return false;
|
|
19
|
-
const v = value;
|
|
20
|
-
return (typeof v.originAllowed === "boolean" &&
|
|
21
|
-
typeof v.canSubmit === "boolean" &&
|
|
22
|
-
typeof v.gate === "string" &&
|
|
23
|
-
(v.tier === null || typeof v.tier === "string"));
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Ask ingest whether a submission would be accepted for this key / origin /
|
|
27
|
-
* Gate. Resolves to the boot answer, or `null` when Fixback could not be
|
|
28
|
-
* reached, the key was refused, or the response was not a boot answer. It never
|
|
29
|
-
* throws: any non-answer is treated by the caller as "stay dormant", so a
|
|
30
|
-
* Fixback outage stays invisible to the host app.
|
|
31
|
-
*
|
|
32
|
-
* Unlike a browser, React Native attaches no `Origin` header of its own — the
|
|
33
|
-
* configured origin is sent explicitly so the server's allowlist check works.
|
|
34
|
-
*/
|
|
35
|
-
export async function requestBoot(apiUrl, request, origin, fetchImpl) {
|
|
36
|
-
const doFetch = fetchImpl ?? globalFetch();
|
|
37
|
-
if (!doFetch)
|
|
38
|
-
return null;
|
|
39
|
-
let response;
|
|
40
|
-
try {
|
|
41
|
-
response = await doFetch(bootEndpoint(apiUrl), {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: { "content-type": "application/json", origin },
|
|
44
|
-
body: JSON.stringify(request),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return null; // network error / Fixback unreachable
|
|
49
|
-
}
|
|
50
|
-
if (!response.ok)
|
|
51
|
-
return null; // 401 unknown key, or any other refusal
|
|
52
|
-
let body;
|
|
53
|
-
try {
|
|
54
|
-
body = await response.json();
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
return isBootAnswer(body) ? body : null;
|
|
60
|
-
}
|
package/dist/scrub.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The single client-side **scrub choke point** every report passes through
|
|
3
|
-
* before transport — a verbatim port of `packages/sdk/src/scrub.ts` (spec 0003
|
|
4
|
-
* §C; ADR-0021 keeps the two clients file-parallel).
|
|
5
|
-
*
|
|
6
|
-
* Masking is the SDK's job, done on the device before anything leaves it. The
|
|
7
|
-
* default scrubbers are **on**: they strip credentials, query strings, and
|
|
8
|
-
* bearer tokens from URLs, and redact obvious PII (emails, long digit runs,
|
|
9
|
-
* bearer tokens) from crumb and error text. The result is then handed to an
|
|
10
|
-
* optional per-project hook that can mutate it further or drop the whole report
|
|
11
|
-
* by returning `null`. The hook is **synchronous and network-free** by
|
|
12
|
-
* contract, and both manual (composer) and automatic (error-capture) reports
|
|
13
|
-
* run through the very same choke point.
|
|
14
|
-
*/
|
|
15
|
-
import type { ReportContent } from "./report";
|
|
16
|
-
/** The per-project client scrub hook. Return `null` to drop the whole report. */
|
|
17
|
-
export type BeforeSend = (draft: ReportContent) => ReportContent | null;
|
|
18
|
-
/** Options for {@link runBeforeSend}. */
|
|
19
|
-
export interface BeforeSendOptions {
|
|
20
|
-
/** The per-project hook, run **after** the default scrubbers. */
|
|
21
|
-
readonly hook?: BeforeSend | null;
|
|
22
|
-
/** Run the built-in default scrubbers first. Defaults to `true`. */
|
|
23
|
-
readonly scrub?: boolean;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Redact obvious PII from free text: email addresses, `Bearer <token>` /
|
|
27
|
-
* `token <value>` pairs, and long digit runs. Conservative by design — it keeps
|
|
28
|
-
* the shape of the message readable while removing the sensitive spans.
|
|
29
|
-
*/
|
|
30
|
-
export declare function redactPii(text: string): string;
|
|
31
|
-
/**
|
|
32
|
-
* Strip the sensitive parts of a URL: userinfo credentials, the entire query
|
|
33
|
-
* string, a token-bearing fragment (one that carries `key=value`), and PII in
|
|
34
|
-
* the **path segments**. Plain hash routes (`#/checkout`) are kept. Works on
|
|
35
|
-
* absolute and relative URLs alike, with no dependency and no throw. The host
|
|
36
|
-
* (authority) is never redacted.
|
|
37
|
-
*/
|
|
38
|
-
export declare function scrubUrl(url: string): string;
|
|
39
|
-
/**
|
|
40
|
-
* Apply the built-in default scrubbers to a report draft: strip the report URL,
|
|
41
|
-
* and scrub every crumb's URLs and redact PII from its text. The Reporter's own
|
|
42
|
-
* `comment` is intentionally left untouched — it is authored on purpose, not
|
|
43
|
-
* scraped. The screenshot is handled elsewhere (composer preview + removal,
|
|
44
|
-
* spec 0004 §C); this is the final URL/PII sweep.
|
|
45
|
-
*/
|
|
46
|
-
export declare function applyDefaultScrub(draft: ReportContent): ReportContent;
|
|
47
|
-
/**
|
|
48
|
-
* Run the report draft through the client scrub choke point: the default
|
|
49
|
-
* scrubbers first (unless `scrub` is `false`), then the optional per-project
|
|
50
|
-
* hook. Returns the scrubbed (and possibly hook-mutated) draft, or `null` when
|
|
51
|
-
* the hook drops the report. A hook that throws is treated as a no-op — the
|
|
52
|
-
* already-scrubbed draft is kept, so a buggy hook never breaks the report path
|
|
53
|
-
* nor leaks unscrubbed data.
|
|
54
|
-
*/
|
|
55
|
-
export declare function runBeforeSend(draft: ReportContent, options?: BeforeSendOptions): ReportContent | null;
|
package/dist/scrub.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The single client-side **scrub choke point** every report passes through
|
|
3
|
-
* before transport — a verbatim port of `packages/sdk/src/scrub.ts` (spec 0003
|
|
4
|
-
* §C; ADR-0021 keeps the two clients file-parallel).
|
|
5
|
-
*
|
|
6
|
-
* Masking is the SDK's job, done on the device before anything leaves it. The
|
|
7
|
-
* default scrubbers are **on**: they strip credentials, query strings, and
|
|
8
|
-
* bearer tokens from URLs, and redact obvious PII (emails, long digit runs,
|
|
9
|
-
* bearer tokens) from crumb and error text. The result is then handed to an
|
|
10
|
-
* optional per-project hook that can mutate it further or drop the whole report
|
|
11
|
-
* by returning `null`. The hook is **synchronous and network-free** by
|
|
12
|
-
* contract, and both manual (composer) and automatic (error-capture) reports
|
|
13
|
-
* run through the very same choke point.
|
|
14
|
-
*/
|
|
15
|
-
/** A digit run at least this long is treated as sensitive (phone, card, id). */
|
|
16
|
-
const MIN_DIGIT_RUN = 7;
|
|
17
|
-
const EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
18
|
-
const DIGIT_RUN_RE = new RegExp(`\\d{${MIN_DIGIT_RUN},}`, "g");
|
|
19
|
-
const BEARER_RE = /\b(bearer|token)\s+[\w.\-~+/]+=*/gi;
|
|
20
|
-
/** An http(s) URL embedded in free text (e.g. a console log line). */
|
|
21
|
-
const URL_IN_TEXT_RE = /\bhttps?:\/\/[^\s"'<>]+/gi;
|
|
22
|
-
/**
|
|
23
|
-
* Redact obvious PII from free text: email addresses, `Bearer <token>` /
|
|
24
|
-
* `token <value>` pairs, and long digit runs. Conservative by design — it keeps
|
|
25
|
-
* the shape of the message readable while removing the sensitive spans.
|
|
26
|
-
*/
|
|
27
|
-
export function redactPii(text) {
|
|
28
|
-
if (typeof text !== "string" || text.length === 0)
|
|
29
|
-
return text;
|
|
30
|
-
return text
|
|
31
|
-
.replace(EMAIL_RE, "[redacted-email]")
|
|
32
|
-
.replace(BEARER_RE, (_match, keyword) => `${keyword} [redacted]`)
|
|
33
|
-
.replace(DIGIT_RUN_RE, "[redacted-number]");
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Redact PII from the **path** portion of a URL: emails and long digit runs in
|
|
37
|
-
* path (or fragment-route) segments. Deliberately narrower than
|
|
38
|
-
* {@link redactPii}: no bearer/token rule and only ever applied to the
|
|
39
|
-
* post-authority remainder, so a host or port with digits is never touched.
|
|
40
|
-
*/
|
|
41
|
-
function redactPathPii(pathAndBeyond) {
|
|
42
|
-
return pathAndBeyond
|
|
43
|
-
.replace(EMAIL_RE, "[redacted-email]")
|
|
44
|
-
.replace(DIGIT_RUN_RE, "[redacted-number]");
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Strip the sensitive parts of a URL: userinfo credentials, the entire query
|
|
48
|
-
* string, a token-bearing fragment (one that carries `key=value`), and PII in
|
|
49
|
-
* the **path segments**. Plain hash routes (`#/checkout`) are kept. Works on
|
|
50
|
-
* absolute and relative URLs alike, with no dependency and no throw. The host
|
|
51
|
-
* (authority) is never redacted.
|
|
52
|
-
*/
|
|
53
|
-
export function scrubUrl(url) {
|
|
54
|
-
if (typeof url !== "string" || url.length === 0)
|
|
55
|
-
return url;
|
|
56
|
-
let out = url;
|
|
57
|
-
// Drop userinfo credentials: scheme://user:pass@host → scheme://host
|
|
58
|
-
out = out.replace(/(^[a-z][a-z0-9+.-]*:\/\/)[^/@?#]*@/i, "$1");
|
|
59
|
-
// Drop the query string entirely (everything from '?' up to a '#').
|
|
60
|
-
out = out.replace(/\?[^#]*/, "");
|
|
61
|
-
// Drop a token-bearing fragment (it carries '='); keep plain hash routes.
|
|
62
|
-
out = out.replace(/#.*$/, (fragment) => fragment.includes("=") ? "" : fragment);
|
|
63
|
-
// Redact PII in the path (and any kept fragment route), never in the authority.
|
|
64
|
-
const absolute = out.match(/^([a-z][a-z0-9+.-]*:\/\/[^/?#]*)([\s\S]*)$/i);
|
|
65
|
-
if (absolute)
|
|
66
|
-
return absolute[1] + redactPathPii(absolute[2] ?? "");
|
|
67
|
-
const protocolRelative = out.match(/^(\/\/[^/?#]*)([\s\S]*)$/);
|
|
68
|
-
if (protocolRelative) {
|
|
69
|
-
return protocolRelative[1] + redactPathPii(protocolRelative[2] ?? "");
|
|
70
|
-
}
|
|
71
|
-
// A relative URL is all path — redact the whole thing.
|
|
72
|
-
return redactPathPii(out);
|
|
73
|
-
}
|
|
74
|
-
function scrubCrumbData(data) {
|
|
75
|
-
const next = { ...data };
|
|
76
|
-
if (typeof next.url === "string")
|
|
77
|
-
next.url = scrubUrl(next.url);
|
|
78
|
-
if (typeof next.from === "string")
|
|
79
|
-
next.from = scrubUrl(next.from);
|
|
80
|
-
if (typeof next.to === "string")
|
|
81
|
-
next.to = scrubUrl(next.to);
|
|
82
|
-
return next;
|
|
83
|
-
}
|
|
84
|
-
/** Scrub a crumb's free-text message: strip URL query strings, then redact PII. */
|
|
85
|
-
function scrubMessage(message) {
|
|
86
|
-
return redactPii(message.replace(URL_IN_TEXT_RE, (url) => scrubUrl(url)));
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Scrub the string leaves of a structured console-arg value — the value a
|
|
90
|
-
* `json` arg carries. Walks arrays and plain objects, applying the same
|
|
91
|
-
* URL-strip + PII redaction as a crumb message to every nested string.
|
|
92
|
-
*/
|
|
93
|
-
function scrubArgValue(value) {
|
|
94
|
-
if (typeof value === "string")
|
|
95
|
-
return scrubMessage(value);
|
|
96
|
-
if (Array.isArray(value))
|
|
97
|
-
return value.map(scrubArgValue);
|
|
98
|
-
if (value && typeof value === "object") {
|
|
99
|
-
const out = {};
|
|
100
|
-
for (const [key, v] of Object.entries(value)) {
|
|
101
|
-
out[key] = scrubArgValue(v);
|
|
102
|
-
}
|
|
103
|
-
return out;
|
|
104
|
-
}
|
|
105
|
-
return value;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Scrub one structured console argument: redact PII / strip URL query strings
|
|
109
|
-
* from a `string` value, from every string leaf of a `json` value, and from an
|
|
110
|
-
* `error` value's `message` and `stack`. Other tags carry no free text.
|
|
111
|
-
*/
|
|
112
|
-
function scrubConsoleArg(arg) {
|
|
113
|
-
switch (arg.t) {
|
|
114
|
-
case "string":
|
|
115
|
-
return typeof arg.v === "string" ? { t: "string", v: scrubMessage(arg.v) } : arg;
|
|
116
|
-
case "json":
|
|
117
|
-
return { t: "json", v: scrubArgValue(arg.v) };
|
|
118
|
-
case "error": {
|
|
119
|
-
if (!arg.v || typeof arg.v !== "object")
|
|
120
|
-
return arg;
|
|
121
|
-
const v = arg.v;
|
|
122
|
-
const next = { ...v };
|
|
123
|
-
if (typeof next.message === "string")
|
|
124
|
-
next.message = scrubMessage(next.message);
|
|
125
|
-
if (typeof next.stack === "string")
|
|
126
|
-
next.stack = scrubMessage(next.stack);
|
|
127
|
-
return { t: "error", v: next };
|
|
128
|
-
}
|
|
129
|
-
default:
|
|
130
|
-
return arg;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function scrubCrumb(crumb) {
|
|
134
|
-
const next = { ...crumb };
|
|
135
|
-
if (typeof next.message === "string")
|
|
136
|
-
next.message = scrubMessage(next.message);
|
|
137
|
-
if (next.data)
|
|
138
|
-
next.data = scrubCrumbData(next.data);
|
|
139
|
-
// A rich network crumb carries its scrubbed URL at the top level — sweep it
|
|
140
|
-
// again here (idempotent) so the choke point holds wherever the URL rode.
|
|
141
|
-
if (typeof next.url === "string")
|
|
142
|
-
next.url = scrubUrl(next.url);
|
|
143
|
-
// A console crumb's structured args carry the same free text as its preview.
|
|
144
|
-
if (Array.isArray(next.args))
|
|
145
|
-
next.args = next.args.map(scrubConsoleArg);
|
|
146
|
-
return next;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Apply the built-in default scrubbers to a report draft: strip the report URL,
|
|
150
|
-
* and scrub every crumb's URLs and redact PII from its text. The Reporter's own
|
|
151
|
-
* `comment` is intentionally left untouched — it is authored on purpose, not
|
|
152
|
-
* scraped. The screenshot is handled elsewhere (composer preview + removal,
|
|
153
|
-
* spec 0004 §C); this is the final URL/PII sweep.
|
|
154
|
-
*/
|
|
155
|
-
export function applyDefaultScrub(draft) {
|
|
156
|
-
const next = { ...draft };
|
|
157
|
-
if (typeof next.url === "string")
|
|
158
|
-
next.url = scrubUrl(next.url);
|
|
159
|
-
if (next.trace && next.trace.length > 0) {
|
|
160
|
-
next.trace = next.trace.map(scrubCrumb);
|
|
161
|
-
}
|
|
162
|
-
return next;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Run the report draft through the client scrub choke point: the default
|
|
166
|
-
* scrubbers first (unless `scrub` is `false`), then the optional per-project
|
|
167
|
-
* hook. Returns the scrubbed (and possibly hook-mutated) draft, or `null` when
|
|
168
|
-
* the hook drops the report. A hook that throws is treated as a no-op — the
|
|
169
|
-
* already-scrubbed draft is kept, so a buggy hook never breaks the report path
|
|
170
|
-
* nor leaks unscrubbed data.
|
|
171
|
-
*/
|
|
172
|
-
export function runBeforeSend(draft, options = {}) {
|
|
173
|
-
const current = options.scrub === false ? draft : applyDefaultScrub(draft);
|
|
174
|
-
const hook = options.hook;
|
|
175
|
-
if (!hook)
|
|
176
|
-
return current;
|
|
177
|
-
try {
|
|
178
|
-
const result = hook(current);
|
|
179
|
-
return result ?? null;
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
return current;
|
|
183
|
-
}
|
|
184
|
-
}
|