@fixback/expo 0.1.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.
Files changed (58) hide show
  1. package/README.md +72 -8
  2. package/dist/FeedbackModal.d.ts +10 -2
  3. package/dist/FeedbackModal.js +84 -36
  4. package/dist/FeedbackModal.js.map +1 -0
  5. package/dist/FixbackProvider.d.ts +12 -0
  6. package/dist/FixbackProvider.js +46 -23
  7. package/dist/FixbackProvider.js.map +1 -0
  8. package/dist/adapters.js +73 -23
  9. package/dist/adapters.js.map +1 -0
  10. package/dist/breadcrumbs.d.ts +21 -222
  11. package/dist/breadcrumbs.js +45 -605
  12. package/dist/breadcrumbs.js.map +1 -0
  13. package/dist/client.d.ts +65 -67
  14. package/dist/client.js +288 -151
  15. package/dist/client.js.map +1 -0
  16. package/dist/connect.d.ts +60 -0
  17. package/dist/connect.js +63 -0
  18. package/dist/connect.js.map +1 -0
  19. package/dist/error-capture.d.ts +29 -79
  20. package/dist/error-capture.js +77 -295
  21. package/dist/error-capture.js.map +1 -0
  22. package/dist/http.d.ts +10 -25
  23. package/dist/http.js +15 -12
  24. package/dist/http.js.map +1 -0
  25. package/dist/identity.d.ts +11 -11
  26. package/dist/identity.js +23 -28
  27. package/dist/identity.js.map +1 -0
  28. package/dist/index.d.ts +6 -5
  29. package/dist/index.js +22 -5
  30. package/dist/index.js.map +1 -0
  31. package/dist/origin.d.ts +21 -0
  32. package/dist/origin.js +45 -0
  33. package/dist/origin.js.map +1 -0
  34. package/dist/package.json +3 -0
  35. package/dist/report.d.ts +28 -63
  36. package/dist/report.js +33 -25
  37. package/dist/report.js.map +1 -0
  38. package/dist/reporter-session-store.d.ts +27 -0
  39. package/dist/reporter-session-store.js +53 -0
  40. package/dist/reporter-session-store.js.map +1 -0
  41. package/dist/shake.d.ts +26 -0
  42. package/dist/shake.js +32 -7
  43. package/dist/shake.js.map +1 -0
  44. package/dist/submit.d.ts +24 -15
  45. package/dist/submit.js +30 -47
  46. package/dist/submit.js.map +1 -0
  47. package/dist/tokens.js +5 -1
  48. package/dist/tokens.js.map +1 -0
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +5 -1
  51. package/dist/version.js.map +1 -0
  52. package/package.json +14 -7
  53. package/dist/auto-report-backoff.d.ts +0 -42
  54. package/dist/auto-report-backoff.js +0 -67
  55. package/dist/boot.d.ts +0 -65
  56. package/dist/boot.js +0 -60
  57. package/dist/scrub.d.ts +0 -55
  58. package/dist/scrub.js +0 -184
package/dist/boot.d.ts DELETED
@@ -1,65 +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 { type FetchLike } from "./http";
11
- /** A Project's Gate — who may submit. Mirrors the server's `ProjectGate`. */
12
- export type ProjectGate = "open" | "invited" | "internal";
13
- /** The trust tier a Reporter holds. Mirrors the server's `ReporterTier`. */
14
- export type ReporterTier = "public" | "invited" | "internal";
15
- /**
16
- * Optional identity evidence the SDK forwards to boot. None of it is a tier: the
17
- * server re-derives trust from this evidence and never honours a self-declared
18
- * tier, so the SDK does not send one.
19
- */
20
- export interface IdentityInputs {
21
- readonly signedIdentity?: string;
22
- readonly reporterId?: string;
23
- readonly anonymousId?: string;
24
- }
25
- /** The JSON body `POST /api/ingest/boot` accepts. */
26
- export interface BootRequest extends IdentityInputs {
27
- readonly key: string;
28
- }
29
- /**
30
- * The Project's effective console/network capture config, served on the boot
31
- * answer. Optional on the wire: an older server that does not send it (or a
32
- * malformed value) is treated as **capture on**, and init options override
33
- * whatever is served.
34
- */
35
- export interface CaptureConfig {
36
- readonly console: boolean;
37
- readonly network: boolean;
38
- }
39
- /**
40
- * The boot answer: whether this origin is allowlisted, the Project's Gate, the
41
- * caller's derived tier (`null` when a presented identity was refused), whether
42
- * a submission would be accepted right now, and the Project's capture config.
43
- * The shake gesture arms only when `canSubmit` is true.
44
- */
45
- export interface BootAnswer {
46
- readonly originAllowed: boolean;
47
- readonly gate: ProjectGate;
48
- readonly tier: ReporterTier | null;
49
- readonly canSubmit: boolean;
50
- /** The Project's console/network capture config; absent ⇒ default-on. */
51
- readonly capture?: CaptureConfig;
52
- }
53
- /** Join an API base URL with the boot path, tolerating a trailing slash. */
54
- export declare function bootEndpoint(apiUrl: string): string;
55
- /**
56
- * Ask ingest whether a submission would be accepted for this key / origin /
57
- * Gate. Resolves to the boot answer, or `null` when Fixback could not be
58
- * reached, the key was refused, or the response was not a boot answer. It never
59
- * throws: any non-answer is treated by the caller as "stay dormant", so a
60
- * Fixback outage stays invisible to the host app.
61
- *
62
- * Unlike a browser, React Native attaches no `Origin` header of its own — the
63
- * configured origin is sent explicitly so the server's allowlist check works.
64
- */
65
- export declare function requestBoot(apiUrl: string, request: BootRequest, origin: string, fetchImpl?: FetchLike): Promise<BootAnswer | null>;
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
- }