@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.
- 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 +29 -79
- package/dist/error-capture.js +77 -295
- 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 +24 -15
- package/dist/submit.js +30 -47
- 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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The Expo **Reporter session** store (ADR-0032) — the storage binding only.
|
|
4
|
+
*
|
|
5
|
+
* After a Connect the SDK holds a compact signed token per origin and forwards it on
|
|
6
|
+
* every boot; the boot answer hands back a refreshed one (30-day sliding). On React
|
|
7
|
+
* Native the token is a real credential, so it lives in **Expo SecureStore**
|
|
8
|
+
* (Keychain / Keystore), not AsyncStorage — under a key **scoped by publishable key**
|
|
9
|
+
* (so two Projects in one app keep separate sessions), the key format shared with every
|
|
10
|
+
* SDK (`@fixback/sdk-core`). Persistence is best-effort: an unavailable or failing
|
|
11
|
+
* secure store simply means no stored session — the SDK falls back to anonymous rather
|
|
12
|
+
* than throwing, so it never disturbs the host app.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.readReporterSession = readReporterSession;
|
|
16
|
+
exports.writeReporterSession = writeReporterSession;
|
|
17
|
+
exports.clearReporterSession = clearReporterSession;
|
|
18
|
+
const sdk_core_1 = require("@fixback/sdk-core");
|
|
19
|
+
/** Read the stored Reporter session token for this Project, or `null`. Best-effort. */
|
|
20
|
+
async function readReporterSession(store, publishableKey) {
|
|
21
|
+
if (!store)
|
|
22
|
+
return null;
|
|
23
|
+
try {
|
|
24
|
+
const value = await store.getItemAsync((0, sdk_core_1.reporterSessionStorageKey)(publishableKey));
|
|
25
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Persist a Reporter session token for this Project. Best-effort. */
|
|
32
|
+
async function writeReporterSession(store, publishableKey, token) {
|
|
33
|
+
if (!store)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
await store.setItemAsync((0, sdk_core_1.reporterSessionStorageKey)(publishableKey), token);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Secure store blocked — the session lives only for this launch, which still works.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Clear the stored Reporter session token for this Project (sign out). Best-effort. */
|
|
43
|
+
async function clearReporterSession(store, publishableKey) {
|
|
44
|
+
if (!store)
|
|
45
|
+
return;
|
|
46
|
+
try {
|
|
47
|
+
await store.deleteItemAsync((0, sdk_core_1.reporterSessionStorageKey)(publishableKey));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Nothing to do — an unreadable store holds nothing to clear.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=reporter-session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter-session-store.js","sourceRoot":"","sources":["../src/reporter-session-store.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAeH,kDAWC;AAGD,oDAWC;AAGD,oDAUC;AAnDD,gDAA8D;AAY9D,uFAAuF;AAChF,KAAK,UAAU,mBAAmB,CACvC,KAA2C,EAC3C,cAAsB;IAEtB,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,IAAA,oCAAyB,EAAC,cAAc,CAAC,CAAC,CAAC;QAClF,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,sEAAsE;AAC/D,KAAK,UAAU,oBAAoB,CACxC,KAA2C,EAC3C,cAAsB,EACtB,KAAa;IAEb,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,YAAY,CAAC,IAAA,oCAAyB,EAAC,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,oFAAoF;IACtF,CAAC;AACH,CAAC;AAED,wFAAwF;AACjF,KAAK,UAAU,oBAAoB,CACxC,KAA2C,EAC3C,cAAsB;IAEtB,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,eAAe,CAAC,IAAA,oCAAyB,EAAC,cAAc,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;IAChE,CAAC;AACH,CAAC"}
|
package/dist/shake.d.ts
CHANGED
|
@@ -49,3 +49,29 @@ export interface ShakeDetector {
|
|
|
49
49
|
* every sample, so memory stays bounded however long the app runs.
|
|
50
50
|
*/
|
|
51
51
|
export declare function createShakeDetector(options: ShakeDetectorOptions): ShakeDetector;
|
|
52
|
+
/** How often the accelerometer reports while the gesture is armed. */
|
|
53
|
+
export declare const DEFAULT_SHAKE_SAMPLE_INTERVAL_MS = 80;
|
|
54
|
+
/** The shake gesture's init options: the tuning plus an off switch. */
|
|
55
|
+
export interface ShakeOptions extends Partial<ShakeTuning> {
|
|
56
|
+
/** Arm the shake gesture. Defaults to `true`. */
|
|
57
|
+
readonly enabled?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The accelerometer update interval to request, in ms. Defaults to
|
|
60
|
+
* {@link DEFAULT_SHAKE_SAMPLE_INTERVAL_MS}. The interval is global to the
|
|
61
|
+
* sensor in `expo-sensors` — the SDK only sets it when no other listener is
|
|
62
|
+
* registered, and apps that drive the accelerometer themselves should align
|
|
63
|
+
* this with their own setting.
|
|
64
|
+
*/
|
|
65
|
+
readonly sampleIntervalMs?: number;
|
|
66
|
+
}
|
|
67
|
+
/** The resolved shake configuration the provider arms the gesture with. */
|
|
68
|
+
export interface ResolvedShakeConfig extends ShakeTuning {
|
|
69
|
+
readonly enabled: boolean;
|
|
70
|
+
readonly sampleIntervalMs: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Fill every shake knob from the init options, defaulting each independently —
|
|
74
|
+
* so `shake: { thresholdG: 3 }` retunes only the threshold and keeps the rest of
|
|
75
|
+
* spec 0004 §B's numbers.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveShakeConfig(shake?: ShakeOptions): ResolvedShakeConfig;
|
package/dist/shake.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* The pure **shake detector** — the mobile launcher's brain (spec 0004 §B,
|
|
3
4
|
* ADR-0021). It consumes raw accelerometer samples (in g, gravity included, as
|
|
@@ -9,8 +10,12 @@
|
|
|
9
10
|
* Kept free of any React Native import so the recogniser is unit-tested
|
|
10
11
|
* deterministically; the sensor wiring lives in `adapters.ts`.
|
|
11
12
|
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.DEFAULT_SHAKE_SAMPLE_INTERVAL_MS = exports.DEFAULT_SHAKE_TUNING = void 0;
|
|
15
|
+
exports.createShakeDetector = createShakeDetector;
|
|
16
|
+
exports.resolveShakeConfig = resolveShakeConfig;
|
|
12
17
|
/** The default tuning: a deliberate shake, not a bump or a phone put down hard. */
|
|
13
|
-
|
|
18
|
+
exports.DEFAULT_SHAKE_TUNING = {
|
|
14
19
|
thresholdG: 2.0,
|
|
15
20
|
minPeaks: 3,
|
|
16
21
|
windowMs: 900,
|
|
@@ -26,12 +31,12 @@ function tuned(value, fallback) {
|
|
|
26
31
|
* Create a shake recogniser. The peak list is pruned to the sliding window on
|
|
27
32
|
* every sample, so memory stays bounded however long the app runs.
|
|
28
33
|
*/
|
|
29
|
-
|
|
30
|
-
const thresholdG = tuned(options.thresholdG, DEFAULT_SHAKE_TUNING.thresholdG);
|
|
31
|
-
const minPeaks = Math.max(1, Math.floor(tuned(options.minPeaks, DEFAULT_SHAKE_TUNING.minPeaks)));
|
|
32
|
-
const windowMs = tuned(options.windowMs, DEFAULT_SHAKE_TUNING.windowMs);
|
|
33
|
-
const minGapMs = tuned(options.minGapMs, DEFAULT_SHAKE_TUNING.minGapMs);
|
|
34
|
-
const cooldownMs = tuned(options.cooldownMs, DEFAULT_SHAKE_TUNING.cooldownMs);
|
|
34
|
+
function createShakeDetector(options) {
|
|
35
|
+
const thresholdG = tuned(options.thresholdG, exports.DEFAULT_SHAKE_TUNING.thresholdG);
|
|
36
|
+
const minPeaks = Math.max(1, Math.floor(tuned(options.minPeaks, exports.DEFAULT_SHAKE_TUNING.minPeaks)));
|
|
37
|
+
const windowMs = tuned(options.windowMs, exports.DEFAULT_SHAKE_TUNING.windowMs);
|
|
38
|
+
const minGapMs = tuned(options.minGapMs, exports.DEFAULT_SHAKE_TUNING.minGapMs);
|
|
39
|
+
const cooldownMs = tuned(options.cooldownMs, exports.DEFAULT_SHAKE_TUNING.cooldownMs);
|
|
35
40
|
/** Epoch-ms times of the peaks inside the current window. */
|
|
36
41
|
let peaks = [];
|
|
37
42
|
let lastPeakAt = Number.NEGATIVE_INFINITY;
|
|
@@ -77,3 +82,23 @@ export function createShakeDetector(options) {
|
|
|
77
82
|
},
|
|
78
83
|
};
|
|
79
84
|
}
|
|
85
|
+
// --- The gesture's init options ------------------------------------------------
|
|
86
|
+
/** How often the accelerometer reports while the gesture is armed. */
|
|
87
|
+
exports.DEFAULT_SHAKE_SAMPLE_INTERVAL_MS = 80;
|
|
88
|
+
/**
|
|
89
|
+
* Fill every shake knob from the init options, defaulting each independently —
|
|
90
|
+
* so `shake: { thresholdG: 3 }` retunes only the threshold and keeps the rest of
|
|
91
|
+
* spec 0004 §B's numbers.
|
|
92
|
+
*/
|
|
93
|
+
function resolveShakeConfig(shake = {}) {
|
|
94
|
+
return {
|
|
95
|
+
enabled: shake.enabled !== false,
|
|
96
|
+
sampleIntervalMs: shake.sampleIntervalMs ?? exports.DEFAULT_SHAKE_SAMPLE_INTERVAL_MS,
|
|
97
|
+
thresholdG: shake.thresholdG ?? exports.DEFAULT_SHAKE_TUNING.thresholdG,
|
|
98
|
+
minPeaks: shake.minPeaks ?? exports.DEFAULT_SHAKE_TUNING.minPeaks,
|
|
99
|
+
windowMs: shake.windowMs ?? exports.DEFAULT_SHAKE_TUNING.windowMs,
|
|
100
|
+
minGapMs: shake.minGapMs ?? exports.DEFAULT_SHAKE_TUNING.minGapMs,
|
|
101
|
+
cooldownMs: shake.cooldownMs ?? exports.DEFAULT_SHAKE_TUNING.cooldownMs,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=shake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shake.js","sourceRoot":"","sources":["../src/shake.ts"],"names":[],"mappings":"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"}
|
package/dist/submit.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The multipart feedback submission — the transport half of the capture loop
|
|
3
|
-
*
|
|
2
|
+
* The multipart feedback submission — the transport half of the capture loop.
|
|
3
|
+
*
|
|
4
|
+
* The endpoint, the identity compaction, the backpressure window, and the `fetch`
|
|
5
|
+
* surface are all shared with the browser SDK through `@fixback/sdk-core`
|
|
6
|
+
* (ADR-0028); what stays here is the mobile envelope.
|
|
4
7
|
*
|
|
5
8
|
* `POST /api/ingest/feedback` is a multipart request: a single text part
|
|
6
9
|
* `payload` carrying the JSON (publishable key + identity evidence + content)
|
|
@@ -14,13 +17,12 @@
|
|
|
14
17
|
* The content-type header is deliberately not set, so the runtime writes the
|
|
15
18
|
* `multipart/form-data` boundary itself. Like `requestBoot`, this never throws:
|
|
16
19
|
* an unreachable Fixback or a refusal is a quiet result, so a Fixback problem
|
|
17
|
-
* never breaks the host app. Automatic reports (`source:
|
|
20
|
+
* never breaks the host app. Automatic reports (`source: error`) honour ingest's
|
|
18
21
|
* `429` + `Retry-After` backpressure; manual reports are never gated.
|
|
19
22
|
*/
|
|
20
|
-
import { AutoReportBackoff } from "
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
import type { ReportContent } from "./report";
|
|
23
|
+
import { AutoReportBackoff, feedbackEndpoint, type FetchLike, type IdentityInputs, type ReportContent, type ReporterTier } from "@fixback/sdk-core";
|
|
24
|
+
import { type FormDataFactory } from "./http";
|
|
25
|
+
export { feedbackEndpoint };
|
|
24
26
|
/** What ingest returns for an accepted submission (vendored server shape). */
|
|
25
27
|
export interface RecordedFeedback {
|
|
26
28
|
readonly feedbackId: string;
|
|
@@ -41,8 +43,6 @@ export interface ScreenshotFile {
|
|
|
41
43
|
/** Everything a single feedback submission carries. */
|
|
42
44
|
export interface SubmitInput {
|
|
43
45
|
readonly key: string;
|
|
44
|
-
/** The configured origin, sent as the `Origin` header (spec 0004 §A). */
|
|
45
|
-
readonly origin: string;
|
|
46
46
|
readonly identity?: IdentityInputs;
|
|
47
47
|
readonly content: ReportContent;
|
|
48
48
|
readonly screenshot?: ScreenshotFile | null;
|
|
@@ -57,7 +57,7 @@ export type SubmitResult = {
|
|
|
57
57
|
readonly status?: number;
|
|
58
58
|
} | {
|
|
59
59
|
/**
|
|
60
|
-
* A `source:
|
|
60
|
+
* A `source: error` report shed under ingest backpressure — either held
|
|
61
61
|
* locally because the window is still open, or answered `429` by ingest,
|
|
62
62
|
* which opened/extended the window. Manual reports never produce this.
|
|
63
63
|
*/
|
|
@@ -72,16 +72,25 @@ export type SubmitResult = {
|
|
|
72
72
|
* The abandoned request keeps running in the background harmlessly.
|
|
73
73
|
*/
|
|
74
74
|
export declare const DEFAULT_SUBMIT_TIMEOUT_MS = 30000;
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Injectable transport collaborators, defaulted to the real implementations —
|
|
77
|
+
* the same deps-object shape `requestBoot` takes, `origin` included.
|
|
78
|
+
*/
|
|
76
79
|
export interface SubmitDeps {
|
|
77
|
-
|
|
80
|
+
/** The `fetch` to call. Defaults to the runtime's global. */
|
|
81
|
+
readonly fetch?: FetchLike;
|
|
82
|
+
/** Creates the multipart form. Defaults to React Native's `FormData`. */
|
|
78
83
|
readonly formData?: FormDataFactory;
|
|
84
|
+
/**
|
|
85
|
+
* The origin to declare explicitly (spec 0004 §A): React Native attaches no
|
|
86
|
+
* `Origin` header of its own, so the SDK sends the configured one and the server
|
|
87
|
+
* matches it against the Project's allowlist.
|
|
88
|
+
*/
|
|
89
|
+
readonly origin?: string;
|
|
79
90
|
readonly backoff?: AutoReportBackoff;
|
|
80
91
|
/** Overall request timeout in ms; `0` disables. Defaults to {@link DEFAULT_SUBMIT_TIMEOUT_MS}. */
|
|
81
92
|
readonly timeoutMs?: number;
|
|
82
93
|
}
|
|
83
|
-
/** Join an API base URL with the feedback path, tolerating a trailing slash. */
|
|
84
|
-
export declare function feedbackEndpoint(apiUrl: string): string;
|
|
85
94
|
/**
|
|
86
95
|
* Submit a report to ingest. Assembles the `payload` JSON (key + identity +
|
|
87
96
|
* content, with an explicit `source` stamped) and the optional screenshot file
|
|
@@ -89,7 +98,7 @@ export declare function feedbackEndpoint(apiUrl: string): string;
|
|
|
89
98
|
* and resolves to the recorded Feedback on success or a named failure
|
|
90
99
|
* otherwise.
|
|
91
100
|
*
|
|
92
|
-
* A `source:
|
|
101
|
+
* A `source: error` report first consults the shared backpressure window: while
|
|
93
102
|
* it is open the report is dropped without a request. Ingest's `429` +
|
|
94
103
|
* `Retry-After` opens/extends that window. Manual reports (`source: reporter`,
|
|
95
104
|
* the default) never consult the window and a non-2xx for them stays a plain
|
package/dist/submit.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
|
-
* The multipart feedback submission — the transport half of the capture loop
|
|
3
|
-
*
|
|
3
|
+
* The multipart feedback submission — the transport half of the capture loop.
|
|
4
|
+
*
|
|
5
|
+
* The endpoint, the identity compaction, the backpressure window, and the `fetch`
|
|
6
|
+
* surface are all shared with the browser SDK through `@fixback/sdk-core`
|
|
7
|
+
* (ADR-0028); what stays here is the mobile envelope.
|
|
4
8
|
*
|
|
5
9
|
* `POST /api/ingest/feedback` is a multipart request: a single text part
|
|
6
10
|
* `payload` carrying the JSON (publishable key + identity evidence + content)
|
|
@@ -14,11 +18,15 @@
|
|
|
14
18
|
* The content-type header is deliberately not set, so the runtime writes the
|
|
15
19
|
* `multipart/form-data` boundary itself. Like `requestBoot`, this never throws:
|
|
16
20
|
* an unreachable Fixback or a refusal is a quiet result, so a Fixback problem
|
|
17
|
-
* never breaks the host app. Automatic reports (`source:
|
|
21
|
+
* never breaks the host app. Automatic reports (`source: error`) honour ingest's
|
|
18
22
|
* `429` + `Retry-After` backpressure; manual reports are never gated.
|
|
19
23
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.DEFAULT_SUBMIT_TIMEOUT_MS = exports.feedbackEndpoint = void 0;
|
|
26
|
+
exports.submitReport = submitReport;
|
|
27
|
+
const sdk_core_1 = require("@fixback/sdk-core");
|
|
28
|
+
Object.defineProperty(exports, "feedbackEndpoint", { enumerable: true, get: function () { return sdk_core_1.feedbackEndpoint; } });
|
|
29
|
+
const http_1 = require("./http");
|
|
22
30
|
/** The multipart field the screenshot binary is sent under (mirrors the server). */
|
|
23
31
|
const SCREENSHOT_FIELD = "screenshot";
|
|
24
32
|
/**
|
|
@@ -27,38 +35,12 @@ const SCREENSHOT_FIELD = "screenshot";
|
|
|
27
35
|
* timeout, so without this a stalled connection would hang a report forever.
|
|
28
36
|
* The abandoned request keeps running in the background harmlessly.
|
|
29
37
|
*/
|
|
30
|
-
|
|
38
|
+
exports.DEFAULT_SUBMIT_TIMEOUT_MS = 30_000;
|
|
31
39
|
/**
|
|
32
|
-
* The shared `source:
|
|
40
|
+
* The shared `source: error` backpressure window for the app. One instance so a
|
|
33
41
|
* `429` from an auto-report holds the auto-reports that follow it.
|
|
34
42
|
*/
|
|
35
|
-
const defaultBackoff = new AutoReportBackoff();
|
|
36
|
-
/** Join an API base URL with the feedback path, tolerating a trailing slash. */
|
|
37
|
-
export function feedbackEndpoint(apiUrl) {
|
|
38
|
-
return `${apiUrl.replace(/\/+$/, "")}/api/ingest/feedback`;
|
|
39
|
-
}
|
|
40
|
-
/** Drop `undefined` identity fields so the payload carries only what was given. */
|
|
41
|
-
function compactIdentity(identity) {
|
|
42
|
-
if (!identity)
|
|
43
|
-
return {};
|
|
44
|
-
const out = {};
|
|
45
|
-
if (identity.signedIdentity)
|
|
46
|
-
out.signedIdentity = identity.signedIdentity;
|
|
47
|
-
if (identity.reporterId)
|
|
48
|
-
out.reporterId = identity.reporterId;
|
|
49
|
-
if (identity.anonymousId)
|
|
50
|
-
out.anonymousId = identity.anonymousId;
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
/** Read one response header defensively — `null` when unavailable or on any throw. */
|
|
54
|
-
function safeHeader(response, name) {
|
|
55
|
-
try {
|
|
56
|
-
return response.headers?.get?.(name) ?? null;
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
43
|
+
const defaultBackoff = new sdk_core_1.AutoReportBackoff();
|
|
62
44
|
/**
|
|
63
45
|
* Submit a report to ingest. Assembles the `payload` JSON (key + identity +
|
|
64
46
|
* content, with an explicit `source` stamped) and the optional screenshot file
|
|
@@ -66,32 +48,32 @@ function safeHeader(response, name) {
|
|
|
66
48
|
* and resolves to the recorded Feedback on success or a named failure
|
|
67
49
|
* otherwise.
|
|
68
50
|
*
|
|
69
|
-
* A `source:
|
|
51
|
+
* A `source: error` report first consults the shared backpressure window: while
|
|
70
52
|
* it is open the report is dropped without a request. Ingest's `429` +
|
|
71
53
|
* `Retry-After` opens/extends that window. Manual reports (`source: reporter`,
|
|
72
54
|
* the default) never consult the window and a non-2xx for them stays a plain
|
|
73
55
|
* refusal. The `deps` seams exist purely so the call is testable.
|
|
74
56
|
*/
|
|
75
|
-
|
|
57
|
+
async function submitReport(apiUrl, input, deps = {}) {
|
|
76
58
|
const source = input.content.source ?? "reporter";
|
|
77
|
-
const
|
|
59
|
+
const isError = source === "error";
|
|
78
60
|
const backoff = deps.backoff ?? defaultBackoff;
|
|
79
61
|
// Client-side backpressure: shed auto-reports while the hold window is open,
|
|
80
62
|
// without touching the network. Manual reports are never gated.
|
|
81
|
-
if (
|
|
63
|
+
if (isError && backoff.isPaused()) {
|
|
82
64
|
return {
|
|
83
65
|
ok: false,
|
|
84
66
|
reason: "backpressure",
|
|
85
67
|
retryAfterSeconds: backoff.retryAfterSeconds(),
|
|
86
68
|
};
|
|
87
69
|
}
|
|
88
|
-
const doFetch = deps.
|
|
89
|
-
const makeForm = deps.formData ?? globalFormData();
|
|
70
|
+
const doFetch = deps.fetch ?? (0, sdk_core_1.resolveFetch)();
|
|
71
|
+
const makeForm = deps.formData ?? (0, http_1.globalFormData)();
|
|
90
72
|
if (!doFetch || !makeForm)
|
|
91
73
|
return { ok: false, reason: "unreachable" };
|
|
92
74
|
const payload = {
|
|
93
75
|
key: input.key,
|
|
94
|
-
...compactIdentity(input.identity),
|
|
76
|
+
...(0, sdk_core_1.compactIdentity)(input.identity),
|
|
95
77
|
...input.content,
|
|
96
78
|
source,
|
|
97
79
|
};
|
|
@@ -107,13 +89,13 @@ export async function submitReport(apiUrl, input, deps = {}) {
|
|
|
107
89
|
type: input.screenshot.type ?? "image/png",
|
|
108
90
|
});
|
|
109
91
|
}
|
|
110
|
-
const timeoutMs = deps.timeoutMs ?? DEFAULT_SUBMIT_TIMEOUT_MS;
|
|
92
|
+
const timeoutMs = deps.timeoutMs ?? exports.DEFAULT_SUBMIT_TIMEOUT_MS;
|
|
111
93
|
let timer;
|
|
112
94
|
let response;
|
|
113
95
|
try {
|
|
114
|
-
const request = doFetch(feedbackEndpoint(apiUrl), {
|
|
96
|
+
const request = doFetch((0, sdk_core_1.feedbackEndpoint)(apiUrl), {
|
|
115
97
|
method: "POST",
|
|
116
|
-
headers: { origin:
|
|
98
|
+
headers: deps.origin ? { origin: deps.origin } : {},
|
|
117
99
|
body: form,
|
|
118
100
|
});
|
|
119
101
|
response =
|
|
@@ -137,14 +119,14 @@ export async function submitReport(apiUrl, input, deps = {}) {
|
|
|
137
119
|
// A `429` for an auto-report is backpressure, not a refusal: open/extend
|
|
138
120
|
// the hold window from `Retry-After` and report the wait. Manual reports
|
|
139
121
|
// never 429 here, so any non-2xx for them stays a plain refusal.
|
|
140
|
-
if (
|
|
141
|
-
const retryAfterSeconds = backoff.hold(safeHeader(response, "Retry-After"));
|
|
122
|
+
if (isError && response.status === 429) {
|
|
123
|
+
const retryAfterSeconds = backoff.hold((0, sdk_core_1.safeHeader)(response, "Retry-After"));
|
|
142
124
|
return { ok: false, reason: "backpressure", retryAfterSeconds };
|
|
143
125
|
}
|
|
144
126
|
return { ok: false, reason: "refused", status: response.status };
|
|
145
127
|
}
|
|
146
128
|
try {
|
|
147
|
-
const feedback = (await response.json());
|
|
129
|
+
const feedback = (await response.json?.());
|
|
148
130
|
return { ok: true, feedback };
|
|
149
131
|
}
|
|
150
132
|
catch {
|
|
@@ -152,3 +134,4 @@ export async function submitReport(apiUrl, input, deps = {}) {
|
|
|
152
134
|
return { ok: true, feedback: null };
|
|
153
135
|
}
|
|
154
136
|
}
|
|
137
|
+
//# sourceMappingURL=submit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submit.js","sourceRoot":"","sources":["../src/submit.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signal = void 0;
|
|
1
4
|
/**
|
|
2
5
|
* A vendored slice of the **Signal** design tokens for the mobile composer.
|
|
3
6
|
*
|
|
@@ -11,7 +14,7 @@
|
|
|
11
14
|
* (the composer passes `Platform.select` for that — this module stays free of
|
|
12
15
|
* React Native imports so pure code can use it too).
|
|
13
16
|
*/
|
|
14
|
-
|
|
17
|
+
exports.signal = {
|
|
15
18
|
/* --fb-color-accent / tints */
|
|
16
19
|
accent: "#2f6fed",
|
|
17
20
|
accentTint: "#eaf1fe",
|
|
@@ -44,3 +47,4 @@ export const signal = {
|
|
|
44
47
|
onEmphasis: "#ffffff",
|
|
45
48
|
scrim: "rgba(15, 23, 32, 0.42)",
|
|
46
49
|
};
|
|
50
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;GAYG;AACU,QAAA,MAAM,GAAG;IACpB,+BAA+B;IAC/B,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,SAAS;IACrB,YAAY,EAAE,SAAS;IAEvB,2BAA2B;IAC3B,IAAI,EAAE,SAAS;IACf,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,SAAS;IAEpB,wBAAwB;IACxB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;IACjB,aAAa,EAAE,SAAS;IACxB,YAAY,EAAE,SAAS;IAEvB,yFAAyF;IACzF,MAAM,EAAE,SAAS;IACjB,aAAa,EAAE,SAAS;IACxB,YAAY,EAAE,SAAS;IACvB,OAAO,EAAE,SAAS;IAClB,cAAc,EAAE,SAAS;IACzB,aAAa,EAAE,SAAS;IACxB,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,SAAS;IACtB,UAAU,EAAE,SAAS;IAErB,cAAc;IACd,UAAU,EAAE,SAAS;IACrB,KAAK,EAAE,wBAAwB;CACvB,CAAC"}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXPO_SDK_VERSION = void 0;
|
|
1
4
|
/**
|
|
2
5
|
* The Expo SDK's own version string, reported to ingest as
|
|
3
6
|
* `environment.sdkVersion`. Kept as an inlined constant rather than a runtime
|
|
@@ -10,4 +13,5 @@
|
|
|
10
13
|
* ever drift. Do not hand-edit this line to a value other than `package.json`'s
|
|
11
14
|
* version.
|
|
12
15
|
*/
|
|
13
|
-
|
|
16
|
+
exports.EXPO_SDK_VERSION = "0.3.0";
|
|
17
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;GAWG;AACU,QAAA,gBAAgB,GAAG,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fixback/expo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The Fixback capture SDK for Expo / React Native apps — shake the device to report feedback with trace Evidence.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,13 +11,12 @@
|
|
|
11
11
|
"LICENSE"
|
|
12
12
|
],
|
|
13
13
|
"main": "./dist/index.js",
|
|
14
|
-
"module": "./dist/index.js",
|
|
15
14
|
"types": "./dist/index.d.ts",
|
|
16
15
|
"exports": {
|
|
17
16
|
".": {
|
|
18
17
|
"types": "./dist/index.d.ts",
|
|
19
18
|
"import": "./dist/index.js",
|
|
20
|
-
"
|
|
19
|
+
"require": "./dist/index.js"
|
|
21
20
|
},
|
|
22
21
|
"./package.json": "./package.json"
|
|
23
22
|
},
|
|
@@ -35,27 +34,35 @@
|
|
|
35
34
|
"url": "git+https://github.com/wemuda/fixback.git",
|
|
36
35
|
"directory": "packages/expo"
|
|
37
36
|
},
|
|
38
|
-
"homepage": "https://
|
|
39
|
-
"bugs": "https://
|
|
37
|
+
"homepage": "https://docs.fixback.dev/sdk/expo",
|
|
38
|
+
"bugs": "https://docs.fixback.dev",
|
|
40
39
|
"publishConfig": {
|
|
41
40
|
"access": "public"
|
|
42
41
|
},
|
|
43
42
|
"peerDependencies": {
|
|
44
43
|
"@react-native-async-storage/async-storage": ">=1.23.0",
|
|
44
|
+
"expo-secure-store": ">=13.0.0",
|
|
45
45
|
"expo-sensors": ">=13.0.0",
|
|
46
|
+
"expo-web-browser": ">=13.0.0",
|
|
46
47
|
"react": ">=18.0.0",
|
|
47
48
|
"react-native": ">=0.74.0",
|
|
48
49
|
"react-native-view-shot": ">=3.8.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
|
-
"@types/react": "^19.
|
|
52
|
+
"@types/react": "^19.2.18",
|
|
52
53
|
"react": "19.2.8",
|
|
53
54
|
"react-native": "^0.81.0",
|
|
54
55
|
"typescript": "5.9.3",
|
|
55
56
|
"vitest": "^4.1.10"
|
|
56
57
|
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@fixback/sdk-core": "0.3.0"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=20.12"
|
|
63
|
+
},
|
|
57
64
|
"scripts": {
|
|
58
|
-
"build": "tsc -p tsconfig.build.json",
|
|
65
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/finalize-cjs.mjs dist",
|
|
59
66
|
"lint": "eslint .",
|
|
60
67
|
"typecheck": "tsc --noEmit",
|
|
61
68
|
"test": "vitest run"
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client-side backpressure for **automatic** error reports — a verbatim port of
|
|
3
|
-
* `packages/sdk/src/auto-report-backoff.ts` (spec 0003 §E/§H). When ingest
|
|
4
|
-
* sheds `source: auto` load it answers `429` with a `Retry-After`; the SDK
|
|
5
|
-
* honours it by holding a pause window during which further `source: auto`
|
|
6
|
-
* reports are dropped without touching the network. **Manual** reports — a
|
|
7
|
-
* human tapping Send — never consult this gate.
|
|
8
|
-
*/
|
|
9
|
-
/** A source of the current time in epoch milliseconds — injectable for tests. */
|
|
10
|
-
export type Clock = () => number;
|
|
11
|
-
/** The hold window applied when a `429` carries no usable `Retry-After`. */
|
|
12
|
-
export declare const DEFAULT_RETRY_AFTER_SECONDS = 60;
|
|
13
|
-
/**
|
|
14
|
-
* Parse a `Retry-After` header into whole seconds to hold for. Handles both
|
|
15
|
-
* HTTP forms — a delta-seconds integer and an HTTP-date (measured from `now`,
|
|
16
|
-
* rounded up and clamped at zero) — and falls back to
|
|
17
|
-
* {@link DEFAULT_RETRY_AFTER_SECONDS} when the header is absent, blank, or
|
|
18
|
-
* unparseable.
|
|
19
|
-
*/
|
|
20
|
-
export declare function parseRetryAfter(header: string | null | undefined, now: number): number;
|
|
21
|
-
/**
|
|
22
|
-
* A single pause window for `source: auto` reports. `hold` opens (or extends)
|
|
23
|
-
* it from a `429`'s `Retry-After`; `isPaused` reports whether it is still open.
|
|
24
|
-
* The default instance in `submit.ts` is shared across the app's reports so the
|
|
25
|
-
* hold persists across successive auto submissions.
|
|
26
|
-
*/
|
|
27
|
-
export declare class AutoReportBackoff {
|
|
28
|
-
private readonly now;
|
|
29
|
-
/** Epoch ms until which `source: auto` reports are held; `0` when clear. */
|
|
30
|
-
private pausedUntil;
|
|
31
|
-
constructor(now?: Clock);
|
|
32
|
-
/** Is the `source: auto` pause window currently open? */
|
|
33
|
-
isPaused(): boolean;
|
|
34
|
-
/** Whole seconds remaining in the pause window (`0` when clear). */
|
|
35
|
-
retryAfterSeconds(): number;
|
|
36
|
-
/**
|
|
37
|
-
* Open (or extend) the window from a `429`'s `Retry-After` value, returning
|
|
38
|
-
* the seconds it will hold for. The window only ever grows — a shorter later
|
|
39
|
-
* hold never clips a longer one already in effect.
|
|
40
|
-
*/
|
|
41
|
-
hold(retryAfterHeader: string | null | undefined): number;
|
|
42
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client-side backpressure for **automatic** error reports — a verbatim port of
|
|
3
|
-
* `packages/sdk/src/auto-report-backoff.ts` (spec 0003 §E/§H). When ingest
|
|
4
|
-
* sheds `source: auto` load it answers `429` with a `Retry-After`; the SDK
|
|
5
|
-
* honours it by holding a pause window during which further `source: auto`
|
|
6
|
-
* reports are dropped without touching the network. **Manual** reports — a
|
|
7
|
-
* human tapping Send — never consult this gate.
|
|
8
|
-
*/
|
|
9
|
-
/** The hold window applied when a `429` carries no usable `Retry-After`. */
|
|
10
|
-
export const DEFAULT_RETRY_AFTER_SECONDS = 60;
|
|
11
|
-
/**
|
|
12
|
-
* Parse a `Retry-After` header into whole seconds to hold for. Handles both
|
|
13
|
-
* HTTP forms — a delta-seconds integer and an HTTP-date (measured from `now`,
|
|
14
|
-
* rounded up and clamped at zero) — and falls back to
|
|
15
|
-
* {@link DEFAULT_RETRY_AFTER_SECONDS} when the header is absent, blank, or
|
|
16
|
-
* unparseable.
|
|
17
|
-
*/
|
|
18
|
-
export function parseRetryAfter(header, now) {
|
|
19
|
-
if (header == null)
|
|
20
|
-
return DEFAULT_RETRY_AFTER_SECONDS;
|
|
21
|
-
const value = header.trim();
|
|
22
|
-
if (value === "")
|
|
23
|
-
return DEFAULT_RETRY_AFTER_SECONDS;
|
|
24
|
-
if (/^\d+$/.test(value)) {
|
|
25
|
-
return Number(value);
|
|
26
|
-
}
|
|
27
|
-
const when = Date.parse(value);
|
|
28
|
-
if (!Number.isNaN(when)) {
|
|
29
|
-
return Math.max(0, Math.ceil((when - now) / 1000));
|
|
30
|
-
}
|
|
31
|
-
return DEFAULT_RETRY_AFTER_SECONDS;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* A single pause window for `source: auto` reports. `hold` opens (or extends)
|
|
35
|
-
* it from a `429`'s `Retry-After`; `isPaused` reports whether it is still open.
|
|
36
|
-
* The default instance in `submit.ts` is shared across the app's reports so the
|
|
37
|
-
* hold persists across successive auto submissions.
|
|
38
|
-
*/
|
|
39
|
-
export class AutoReportBackoff {
|
|
40
|
-
now;
|
|
41
|
-
/** Epoch ms until which `source: auto` reports are held; `0` when clear. */
|
|
42
|
-
pausedUntil = 0;
|
|
43
|
-
constructor(now = Date.now) {
|
|
44
|
-
this.now = now;
|
|
45
|
-
}
|
|
46
|
-
/** Is the `source: auto` pause window currently open? */
|
|
47
|
-
isPaused() {
|
|
48
|
-
return this.now() < this.pausedUntil;
|
|
49
|
-
}
|
|
50
|
-
/** Whole seconds remaining in the pause window (`0` when clear). */
|
|
51
|
-
retryAfterSeconds() {
|
|
52
|
-
return Math.max(0, Math.ceil((this.pausedUntil - this.now()) / 1000));
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Open (or extend) the window from a `429`'s `Retry-After` value, returning
|
|
56
|
-
* the seconds it will hold for. The window only ever grows — a shorter later
|
|
57
|
-
* hold never clips a longer one already in effect.
|
|
58
|
-
*/
|
|
59
|
-
hold(retryAfterHeader) {
|
|
60
|
-
const now = this.now();
|
|
61
|
-
const seconds = parseRetryAfter(retryAfterHeader, now);
|
|
62
|
-
const until = now + seconds * 1000;
|
|
63
|
-
if (until > this.pausedUntil)
|
|
64
|
-
this.pausedUntil = until;
|
|
65
|
-
return seconds;
|
|
66
|
-
}
|
|
67
|
-
}
|