@buoy-gg/bug-links 0.0.0-stage → 7.0.81
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/BuoyBugLinks.podspec +18 -0
- package/LICENSE +58 -0
- package/README.md +25 -2
- package/android/build.gradle +31 -0
- package/android/consumer-rules.pro +8 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/BuoyFramesModule.kt +53 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/BuoyFramesPackage.kt +11 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/FrameEngine.kt +707 -0
- package/android/src/main/java/gg/buoy/buglinks/frames/FrameWalker.kt +181 -0
- package/ios/BuoyFrames.m +17 -0
- package/ios/BuoyFrames.swift +640 -0
- package/ios/BuoyFramesWalk.swift +213 -0
- package/lib/commonjs/components/BugLinksBody.js +1 -0
- package/lib/commonjs/components/TeamBugs.js +1 -0
- package/lib/commonjs/components/focus.js +1 -0
- package/lib/commonjs/components/overlays.js +1 -0
- package/lib/commonjs/components/topBanners.js +1 -0
- package/lib/commonjs/engine/client.js +1 -0
- package/lib/commonjs/engine/credential.js +1 -0
- package/lib/commonjs/engine/linksStore.js +1 -0
- package/lib/commonjs/engine/loader.js +1 -0
- package/lib/commonjs/engine/opener.js +1 -0
- package/lib/commonjs/engine/otherTabs.js +1 -0
- package/lib/commonjs/engine/phoneCheck.js +1 -0
- package/lib/commonjs/engine/phoneCrypto.js +1 -0
- package/lib/commonjs/engine/randomBytes.js +1 -0
- package/lib/commonjs/engine/reporter.js +1 -0
- package/lib/commonjs/engine/sandbox.js +1 -0
- package/lib/commonjs/engine/settings.js +1 -0
- package/lib/commonjs/engine/tabStorage.js +1 -0
- package/lib/commonjs/engine/teamKeys.js +1 -0
- package/lib/commonjs/engine/teamList.js +1 -0
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/media/BuoyMask.js +1 -0
- package/lib/commonjs/media/frames.js +1 -0
- package/lib/commonjs/media/nativeFrames.js +1 -0
- package/lib/commonjs/media/nativeFrames.web.js +1 -0
- package/lib/commonjs/media/webFrames.js +14 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +1 -0
- package/lib/commonjs/sync/bugLinksSyncAdapter.js +1 -0
- package/lib/module/components/BugLinksBody.js +1 -0
- package/lib/module/components/TeamBugs.js +1 -0
- package/lib/module/components/focus.js +1 -0
- package/lib/module/components/overlays.js +1 -0
- package/lib/module/components/topBanners.js +1 -0
- package/lib/module/engine/client.js +1 -0
- package/lib/module/engine/credential.js +1 -0
- package/lib/module/engine/linksStore.js +1 -0
- package/lib/module/engine/loader.js +1 -0
- package/lib/module/engine/opener.js +1 -0
- package/lib/module/engine/otherTabs.js +1 -0
- package/lib/module/engine/phoneCheck.js +1 -0
- package/lib/module/engine/phoneCrypto.js +1 -0
- package/lib/module/engine/randomBytes.js +1 -0
- package/lib/module/engine/reporter.js +1 -0
- package/lib/module/engine/sandbox.js +1 -0
- package/lib/module/engine/settings.js +1 -0
- package/lib/module/engine/tabStorage.js +1 -0
- package/lib/module/engine/teamKeys.js +1 -0
- package/lib/module/engine/teamList.js +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/media/BuoyMask.js +1 -0
- package/lib/module/media/frames.js +1 -0
- package/lib/module/media/nativeFrames.js +1 -0
- package/lib/module/media/nativeFrames.web.js +1 -0
- package/lib/module/media/webFrames.js +14 -0
- package/lib/module/preset.js +1 -0
- package/lib/module/sync/bugLinksSyncAdapter.js +1 -0
- package/lib/typescript/components/BugLinksBody.d.ts +2 -0
- package/lib/typescript/components/TeamBugs.d.ts +8 -0
- package/lib/typescript/components/focus.d.ts +6 -0
- package/lib/typescript/components/overlays.d.ts +10 -0
- package/lib/typescript/components/topBanners.d.ts +35 -0
- package/lib/typescript/engine/client.d.ts +78 -0
- package/lib/typescript/engine/credential.d.ts +11 -0
- package/lib/typescript/engine/linksStore.d.ts +132 -0
- package/lib/typescript/engine/loader.d.ts +172 -0
- package/lib/typescript/engine/opener.d.ts +21 -0
- package/lib/typescript/engine/otherTabs.d.ts +52 -0
- package/lib/typescript/engine/phoneCheck.d.ts +8 -0
- package/lib/typescript/engine/phoneCrypto.d.ts +16 -0
- package/lib/typescript/engine/randomBytes.d.ts +7 -0
- package/lib/typescript/engine/reporter.d.ts +53 -0
- package/lib/typescript/engine/sandbox.d.ts +83 -0
- package/lib/typescript/engine/settings.d.ts +42 -0
- package/lib/typescript/engine/tabStorage.d.ts +6 -0
- package/lib/typescript/engine/teamKeys.d.ts +37 -0
- package/lib/typescript/engine/teamList.d.ts +63 -0
- package/lib/typescript/index.d.ts +23 -0
- package/lib/typescript/media/BuoyMask.d.ts +19 -0
- package/lib/typescript/media/frames.d.ts +34 -0
- package/lib/typescript/media/nativeFrames.d.ts +2 -0
- package/lib/typescript/media/nativeFrames.web.d.ts +2 -0
- package/lib/typescript/media/webFrames.d.ts +178 -0
- package/lib/typescript/preset.d.ts +50 -0
- package/lib/typescript/sync/bugLinksSyncAdapter.d.ts +41 -0
- package/lib/web/index.mjs +20 -0
- package/package.json +119 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app's other browser tabs, for the loader.
|
|
3
|
+
*
|
|
4
|
+
* Every tab of a web app shares its storage, so a case loaded in one tab
|
|
5
|
+
* changes the data of all of them. Rounds 9 to 12 of the review found one race
|
|
6
|
+
* after another between tabs. A message-and-wait tab check couldn't prove a
|
|
7
|
+
* frozen or slow tab wasn't there, and each tab's own queue only ordered its
|
|
8
|
+
* own steps. So bug links uses the browser's Web Locks API, which every tab
|
|
9
|
+
* of an origin shares:
|
|
10
|
+
*
|
|
11
|
+
* - Each top-level tab holds a shared "tab" lock for its whole life, so
|
|
12
|
+
* `navigator.locks.query()` counts the app's tabs. A frame doesn't take
|
|
13
|
+
* one. Safari drops a page's locks when it goes into the back cache, so a
|
|
14
|
+
* page that comes back from there reloads (index.tsx).
|
|
15
|
+
* - Every step that changes a case (load, Restore, End, Use live requests,
|
|
16
|
+
* a late state pass) runs under one exclusive "step" lock, across all tabs.
|
|
17
|
+
*
|
|
18
|
+
* A browser without Web Locks can't do either, so a load there is refused.
|
|
19
|
+
* React Native has one app: never other tabs, and no lock needed.
|
|
20
|
+
*/
|
|
21
|
+
type LockManagerLike = {
|
|
22
|
+
request<T>(name: string, options: {
|
|
23
|
+
mode: "shared" | "exclusive";
|
|
24
|
+
}, callback: () => Promise<T> | T): Promise<T>;
|
|
25
|
+
query(): Promise<{
|
|
26
|
+
held?: {
|
|
27
|
+
name?: string;
|
|
28
|
+
clientId?: string;
|
|
29
|
+
}[];
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
/** Whether this runtime is a browser page (tests count as one when they pass locks). */
|
|
33
|
+
export declare function tabsMatter(): boolean;
|
|
34
|
+
/** Take this tab's lock for its whole life. Called at boot, so a tab is counted before it does anything. */
|
|
35
|
+
export declare function listenForOtherTabs(): Promise<void>;
|
|
36
|
+
/** Why a load can't run here because of the app's other tabs, or null. Off the web, always null. */
|
|
37
|
+
export declare function tabsProblem(): Promise<string | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Run a step under the lock every tab shares, so no two tabs change a case
|
|
40
|
+
* at once. Off the web, it just runs; a browser with no Web Locks refuses.
|
|
41
|
+
*/
|
|
42
|
+
export declare function withStepLock<T>(work: () => Promise<T>): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Run under an exclusive lock with this name, shared by every tab. Off the
|
|
45
|
+
* web, it just runs. A browser with no Web Locks can't keep two tabs from
|
|
46
|
+
* saving over each other, so shared changes are refused there.
|
|
47
|
+
*/
|
|
48
|
+
export declare function withTabLock<T>(name: string, work: () => Promise<T>): Promise<T>;
|
|
49
|
+
/** Tests: use this lock manager (null for none), and act as a frame or not. */
|
|
50
|
+
export declare function setTabLocksForTests(locks: LockManagerLike | null, frame?: boolean): void;
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=otherTabs.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type BugCase } from "@buoy-gg/shared-ui";
|
|
2
|
+
export type PhoneFacts = BugCase["app"];
|
|
3
|
+
/** Each difference as a short sentence, most likely to matter first. Empty when they match. */
|
|
4
|
+
export declare function phoneDifferences(theirs: PhoneFacts | undefined, mine: PhoneFacts): string[];
|
|
5
|
+
export declare function otherAppReason(theirs: PhoneFacts | undefined, mine: PhoneFacts): string | null;
|
|
6
|
+
/** This phone and app, the way a case writes them (capture.ts). */
|
|
7
|
+
export declare function thisPhone(): PhoneFacts;
|
|
8
|
+
//# sourceMappingURL=phoneCheck.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type BugCaseCrypto, type TeamKeyCrypto } from "@buoy-gg/shared-ui";
|
|
2
|
+
export declare function phoneCaseCrypto(): BugCaseCrypto;
|
|
3
|
+
/** WebCrypto when the runtime has it, else the plain JS lock. */
|
|
4
|
+
export declare function caseCrypto(): BugCaseCrypto;
|
|
5
|
+
/**
|
|
6
|
+
* Team keys on a phone: P-256 from @noble/curves, SHA-256 and HMAC from
|
|
7
|
+
* @noble/hashes (both audited, no dependencies), AES-GCM from @noble/ciphers.
|
|
8
|
+
* Random bytes come from our own source; noble's needs WebCrypto, which
|
|
9
|
+
* Hermes lacks.
|
|
10
|
+
*/
|
|
11
|
+
export declare function phoneTeamKeyCrypto(): TeamKeyCrypto;
|
|
12
|
+
/** WebCrypto when the runtime has it, else the plain JS one. */
|
|
13
|
+
export declare function teamKeyCrypto(): TeamKeyCrypto;
|
|
14
|
+
/** Unzip, stopping as soon as the output passes `maxBytes` (a zip bomb stops here). */
|
|
15
|
+
export declare function gunzipCapped(data: Uint8Array, maxBytes: number): Uint8Array;
|
|
16
|
+
//# sourceMappingURL=phoneCrypto.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type RandomSource = "getRandomValues" | "expo-uuid";
|
|
2
|
+
export declare function randomSource(): RandomSource | null;
|
|
3
|
+
export declare const NO_RANDOM_MESSAGE = "This app has no safe random numbers, so Buoy can't lock a bug case. Add expo-modules-core (any Expo app has it), react-native-get-random-values or react-native-quick-crypto.";
|
|
4
|
+
export declare function randomBytes(length: number): Uint8Array;
|
|
5
|
+
/** Pack the random bits of v4 UUIDs into bytes. Exported for tests. */
|
|
6
|
+
export declare function bytesFromUuids(length: number, nextUuid: () => string): Uint8Array;
|
|
7
|
+
//# sourceMappingURL=randomBytes.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type BugPriority } from "./client";
|
|
2
|
+
import { type MyBugLink } from "./linksStore";
|
|
3
|
+
export type ReportPhase = "capturing" | "sending" | "sent" | "waiting" | "failed";
|
|
4
|
+
export interface ReportState {
|
|
5
|
+
localId: string | null;
|
|
6
|
+
phase: ReportPhase;
|
|
7
|
+
message: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
removed: number;
|
|
10
|
+
copied?: boolean;
|
|
11
|
+
/** Why it is waiting or failed, for the right button ("signed_out" -> Sign in). */
|
|
12
|
+
code?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function copyWithin(text: string, ms: number): Promise<boolean>;
|
|
15
|
+
export declare function reportState(): ReportState | null;
|
|
16
|
+
export declare function subscribeReportState(listener: () => void): () => void;
|
|
17
|
+
export declare function dismissReportState(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Capture, lock, save and send a bug case. A second tap while one is going
|
|
20
|
+
* returns the same report instead of making two.
|
|
21
|
+
*/
|
|
22
|
+
export declare function reportBug(options?: {
|
|
23
|
+
note?: string;
|
|
24
|
+
priority?: BugPriority;
|
|
25
|
+
}): Promise<MyBugLink | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Send one saved case now (or its new note, or its delete). `asMe` sends a
|
|
28
|
+
* case made by another sign-in as the current one; only a person's tap does.
|
|
29
|
+
*/
|
|
30
|
+
export declare function sendLink(localId: string, options?: {
|
|
31
|
+
asMe?: boolean;
|
|
32
|
+
}): Promise<MyBugLink | null>;
|
|
33
|
+
/** Try every waiting case now: the app came back, or the person asked. */
|
|
34
|
+
export declare function retryWaiting(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Lock a sent case to the team now: its team key wasn't there or wasn't
|
|
37
|
+
* trusted when it went up. Only the wrap goes (113 bytes), with the edit
|
|
38
|
+
* token. Sending the same wrap twice is fine.
|
|
39
|
+
*/
|
|
40
|
+
export declare function wrapLater(localId: string): Promise<MyBugLink | null>;
|
|
41
|
+
/**
|
|
42
|
+
* The tester changes how soon their bug needs a fix. Not sent yet: the send
|
|
43
|
+
* takes it. Sent: it goes with the edit token, and waits to go again if the
|
|
44
|
+
* phone is offline. Leave out `priority` to retry a waiting one.
|
|
45
|
+
*/
|
|
46
|
+
export declare function setLinkPriority(localId: string, priority?: BugPriority): Promise<MyBugLink | null>;
|
|
47
|
+
/** Put a note in a case that was already made. The link stays the same. */
|
|
48
|
+
export declare function addNote(localId: string, note: string): Promise<MyBugLink | null>;
|
|
49
|
+
/** Delete a case here and on the server. Offline, the server copy goes later. */
|
|
50
|
+
export declare function deleteLink(localId: string): Promise<void>;
|
|
51
|
+
/** Tests only. */
|
|
52
|
+
export declare function resetReporterForTests(): void;
|
|
53
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn a case's recorded web calls into Network override rules: the sandbox.
|
|
3
|
+
*
|
|
4
|
+
* Loading a bug must not call real servers with a teammate's account, and it
|
|
5
|
+
* must show the data the reporter saw. So every call the app makes is
|
|
6
|
+
* answered from the recording, and anything with no recording is blocked and
|
|
7
|
+
* counted as a miss.
|
|
8
|
+
*
|
|
9
|
+
* Rules are a first-match list, so order is the whole design:
|
|
10
|
+
*
|
|
11
|
+
* 1. Exact answers. By default each call gets its NEWEST answer, the one
|
|
12
|
+
* on screen when the bug was reported: a loaded app makes fewer calls
|
|
13
|
+
* than the reporter's whole session did, and replaying that history in
|
|
14
|
+
* order would stop part way, on an old answer (found on the Bug Lab).
|
|
15
|
+
* With `sequence: true` a call made twice gets both answers in the
|
|
16
|
+
* order they came (each but the last retires after one use), for bugs
|
|
17
|
+
* in polling or retries. The last one keeps answering, for refetches.
|
|
18
|
+
* 2. Block everything else, and count it as a miss. A call the reporter
|
|
19
|
+
* never made (page 3 of a feed) is never answered with made-up data:
|
|
20
|
+
* an earlier "any query" fallback did that on the Bug Zoo, and showed
|
|
21
|
+
* page 2 again as if it were page 3.
|
|
22
|
+
*
|
|
23
|
+
* A secret the scrubber took out of a URL (`?token=[removed:secret]`) becomes
|
|
24
|
+
* `*`, so the teammate's own token still matches. So does the value of a
|
|
25
|
+
* cache-buster (`?_=1729…`, `?t=…`), which is different on every call. Calls that share one URL
|
|
26
|
+
* are told apart by their input: GraphQL by `operationName` and its
|
|
27
|
+
* variables, and REST by its JSON body (`POST /search {"q":"pie"}`), but only
|
|
28
|
+
* when that URL was called with more than one input. The most specific rules
|
|
29
|
+
* go first, so such a wildcard never answers a longer path.
|
|
30
|
+
*/
|
|
31
|
+
import type { BugCase } from "@buoy-gg/shared-ui";
|
|
32
|
+
export declare const SANDBOX_PREFIX = "Bug case: ";
|
|
33
|
+
export declare const BLOCK_RULE_NAME = "Bug case: blocked (no recorded answer)";
|
|
34
|
+
export interface SandboxRule {
|
|
35
|
+
name: string;
|
|
36
|
+
enabled: true;
|
|
37
|
+
/** Network keeps sandbox rules on without account access, and never auto-pauses them. */
|
|
38
|
+
sandbox: true;
|
|
39
|
+
urlPattern: string;
|
|
40
|
+
methods?: string[];
|
|
41
|
+
graphqlOperation?: string;
|
|
42
|
+
/** The GraphQL call's variables (sorted-key JSON), when one operation had several inputs. */
|
|
43
|
+
graphqlVariables?: string;
|
|
44
|
+
/** A REST call's JSON body (sorted-key JSON), when one URL had several bodies. */
|
|
45
|
+
bodyJson?: string;
|
|
46
|
+
graphqlSingle?: boolean;
|
|
47
|
+
kind: "respond" | "fail";
|
|
48
|
+
status?: number;
|
|
49
|
+
headers?: Record<string, string>;
|
|
50
|
+
body?: string;
|
|
51
|
+
failKind?: "network" | "timeout";
|
|
52
|
+
delayMs?: number;
|
|
53
|
+
times: number | null;
|
|
54
|
+
}
|
|
55
|
+
export interface SandboxPlan {
|
|
56
|
+
rules: SandboxRule[];
|
|
57
|
+
/** Recorded answers among the rules (all but the block rule). */
|
|
58
|
+
answers: number;
|
|
59
|
+
/** Calls with no usable recording (body too big, cut for the rule limit). */
|
|
60
|
+
skipped: {
|
|
61
|
+
method: string;
|
|
62
|
+
url: string;
|
|
63
|
+
reason: string;
|
|
64
|
+
}[];
|
|
65
|
+
}
|
|
66
|
+
/** A glob that matches the URL literally, with removed secrets and cache-busters as wildcards. */
|
|
67
|
+
export declare function urlGlob(url: string): string;
|
|
68
|
+
/** The GraphQL operation name in a request body, else nothing. */
|
|
69
|
+
export declare function graphqlOperation(requestData: unknown): string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* A GraphQL call's variables, for Network's parsed `graphqlVariables` match,
|
|
72
|
+
* so two calls of one operation with different inputs get their own answers.
|
|
73
|
+
* Missing variables are {}. None when a value was taken out: the app's real
|
|
74
|
+
* body has the real value, and it would never match.
|
|
75
|
+
*/
|
|
76
|
+
export declare function graphqlVariablesKey(requestData: unknown): string | undefined;
|
|
77
|
+
export declare function buildSandbox(kase: Pick<BugCase, "network">, options?: {
|
|
78
|
+
sequence?: boolean;
|
|
79
|
+
}): SandboxPlan;
|
|
80
|
+
export declare function isSandboxRule(rule: {
|
|
81
|
+
name?: string;
|
|
82
|
+
} | null | undefined): boolean;
|
|
83
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bug links settings: where cases go, and the app's own scrub rules.
|
|
3
|
+
*
|
|
4
|
+
* Order of strength: what a person set in the tool (saved on the phone), then
|
|
5
|
+
* what the app passed to `configureBugLinks`, then the default (buoy.gg). The
|
|
6
|
+
* person wins so a dev can point a build at a local server without a rebuild.
|
|
7
|
+
*/
|
|
8
|
+
import { type ScrubRules } from "@buoy-gg/shared-ui";
|
|
9
|
+
import { type BugLinksMediaConfig } from "../media/frames";
|
|
10
|
+
export declare const DEFAULT_SERVER = "https://buoy.gg";
|
|
11
|
+
export interface BugLinksConfig {
|
|
12
|
+
/** Server origin, like "https://buoy.gg". */
|
|
13
|
+
server?: string;
|
|
14
|
+
/** Extra fields to hide, and fields to keep, on top of Buoy's scrubber. */
|
|
15
|
+
rules?: ScrubRules;
|
|
16
|
+
/**
|
|
17
|
+
* The team key root(s) this app trusts, from the team list's admin page
|
|
18
|
+
* (one id, or several while a team moves to a new root). With none, the
|
|
19
|
+
* phone trusts the first root it sees. EXPO_PUBLIC_BUOY_TEAM_KEY works too.
|
|
20
|
+
*/
|
|
21
|
+
teamKey?: string | string[];
|
|
22
|
+
/** Pictures and video (SPEC.md S1). On by default, with every mask on. */
|
|
23
|
+
media?: BugLinksMediaConfig;
|
|
24
|
+
}
|
|
25
|
+
export declare function bugLinksSettingsRead(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* A server must be https. Plain http is allowed only for this Mac or the local
|
|
28
|
+
* network, where a dev runs the bug links dev server.
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkServer(url: string): string | null;
|
|
31
|
+
export declare function configureBugLinks(config: BugLinksConfig): void;
|
|
32
|
+
export declare function loadBugLinksSettings(): Promise<void>;
|
|
33
|
+
export declare function bugLinksServer(): string;
|
|
34
|
+
export declare function bugLinksRules(): ScrubRules | undefined;
|
|
35
|
+
/** The pinned key roots, or null when the app pins none. An empty list (only bad pins) pins nothing. */
|
|
36
|
+
export declare function teamKeyPins(): string[] | null;
|
|
37
|
+
/** Set (or with null, clear) the server a person picked. */
|
|
38
|
+
export declare function setBugLinksServer(url: string | null): Promise<string>;
|
|
39
|
+
export declare function subscribeBugLinksSettings(listener: () => void): () => void;
|
|
40
|
+
/** Tests only. */
|
|
41
|
+
export declare function resetBugLinksSettingsForTests(): void;
|
|
42
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface TeamKeyTarget {
|
|
2
|
+
owner: string;
|
|
3
|
+
teamKeyId: string;
|
|
4
|
+
keyVersion: number;
|
|
5
|
+
pub: Uint8Array;
|
|
6
|
+
}
|
|
7
|
+
export type TeamKeyPick = {
|
|
8
|
+
kind: "ok";
|
|
9
|
+
target: TeamKeyTarget;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The team list isn't on (or this server has no team keys): nothing to
|
|
13
|
+
* wrap, nothing to wait for. `owner` is the team (or account) the server
|
|
14
|
+
* says this sign-in sends for, when it said.
|
|
15
|
+
*/
|
|
16
|
+
| {
|
|
17
|
+
kind: "off";
|
|
18
|
+
owner?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Wrap later. `owner` as for "off", when the server said. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "wait";
|
|
23
|
+
code: TeamKeyWaitCode;
|
|
24
|
+
message: string;
|
|
25
|
+
teamKeyId?: string;
|
|
26
|
+
owner?: string;
|
|
27
|
+
};
|
|
28
|
+
export type TeamKeyWaitCode = "offline" | "signed_out" | "no_current" | "pin" | "new_root" | "untrusted";
|
|
29
|
+
/** The key to wrap to now, or why not. Cached for a few minutes per server and sign-in. */
|
|
30
|
+
export declare function pickTeamKey(server: string, credential: string | null, options?: {
|
|
31
|
+
fresh?: boolean;
|
|
32
|
+
}): Promise<TeamKeyPick>;
|
|
33
|
+
/** A person said yes to a new key root, in the Bug Links screen. */
|
|
34
|
+
export declare function trustTeamRoot(server: string, teamKeyId: string, credential: string | null): Promise<void>;
|
|
35
|
+
/** Tests only. */
|
|
36
|
+
export declare function resetTeamKeysForTests(): void;
|
|
37
|
+
//# sourceMappingURL=teamKeys.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type BugPriority } from "./client";
|
|
2
|
+
import { type LoadOptions, type LoadReport } from "./loader";
|
|
3
|
+
export type TeamStatus = "new" | "seen" | "fixed" | "wont_fix";
|
|
4
|
+
export type TeamView = "open" | "mine" | "all";
|
|
5
|
+
export interface TeamBug {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
priority: BugPriority;
|
|
9
|
+
status: TeamStatus;
|
|
10
|
+
assignee: string | null;
|
|
11
|
+
/** The assignee's name, or "You". */
|
|
12
|
+
assigneeName: string | null;
|
|
13
|
+
/** Who sent it, or "You". */
|
|
14
|
+
reporter: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
/** "1.0.0 (12)", from the bug's card. */
|
|
17
|
+
build: string | null;
|
|
18
|
+
error: string | null;
|
|
19
|
+
/** The first report, when this is a repeat. */
|
|
20
|
+
repeatOf: string | null;
|
|
21
|
+
/** This phone holds its key and a card that checks out: it can be loaded. */
|
|
22
|
+
canOpen: boolean;
|
|
23
|
+
rowRevision: number;
|
|
24
|
+
}
|
|
25
|
+
export type TeamBlock = "signed_out" | "no_access" | "needs_pro" | "own_kits" | "no_custody" | "offline" | "error";
|
|
26
|
+
export type TeamListState = {
|
|
27
|
+
kind: "idle";
|
|
28
|
+
} | {
|
|
29
|
+
kind: "loading";
|
|
30
|
+
}
|
|
31
|
+
/** `refreshing`: a Refresh is reading the list again. */
|
|
32
|
+
| {
|
|
33
|
+
kind: "ready";
|
|
34
|
+
bugs: TeamBug[];
|
|
35
|
+
me: string;
|
|
36
|
+
refreshing: boolean;
|
|
37
|
+
} | {
|
|
38
|
+
kind: "blocked";
|
|
39
|
+
code: TeamBlock;
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
/** Who is asking right now: the server and the person, or null when signed out. */
|
|
43
|
+
export declare function whoNow(): string | null;
|
|
44
|
+
/** The list, but only for whoever is signed in now. */
|
|
45
|
+
export declare function teamBugs(): TeamListState;
|
|
46
|
+
export declare function subscribeTeamBugs(listener: () => void): () => void;
|
|
47
|
+
/** Drops the kit, the keys and the list. The screen calls this when it closes. */
|
|
48
|
+
export declare function forgetTeamBugs(): void;
|
|
49
|
+
/** Loading answers web calls from the case, which only a dev build can do. */
|
|
50
|
+
export declare function canLoadTeamBugs(): boolean;
|
|
51
|
+
/** Reads the team list fresh. Never throws: a problem shows as a blocked state. `attempt`: a restart after a team move. */
|
|
52
|
+
export declare function loadTeamBugs(attempt?: number): Promise<void>;
|
|
53
|
+
/** One view of the list. Open and Mine: most urgent first, then newest. Repeats stay out of both. */
|
|
54
|
+
export declare function teamView(bugs: readonly TeamBug[], view: TeamView, me: string): TeamBug[];
|
|
55
|
+
/** Take it, Let go, Fixed or Still broken. Saved, then shown here at once. */
|
|
56
|
+
export declare function changeTeamBug(id: string, change: {
|
|
57
|
+
status?: TeamStatus;
|
|
58
|
+
assignee?: string | null;
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
/** Loads a team bug into this app, with no link: the key came from the team kit. */
|
|
61
|
+
export declare function loadTeamBug(id: string, options?: Omit<LoadOptions, "source">): Promise<LoadReport>;
|
|
62
|
+
export declare function resetTeamBugsForTests(): void;
|
|
63
|
+
//# sourceMappingURL=teamList.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { reportBugToolPreset, bugLinksToolPreset, createBugLinksTool, BUG_LINKS_COLOR } from "./preset";
|
|
2
|
+
export { bugLinksSyncAdapter, getBugLinksSnapshot } from "./sync/bugLinksSyncAdapter";
|
|
3
|
+
export type { BugLinksSnapshot } from "./sync/bugLinksSyncAdapter";
|
|
4
|
+
export { BugLinksBody } from "./components/BugLinksBody";
|
|
5
|
+
export { BugLinksOverlay } from "./components/overlays";
|
|
6
|
+
export { BuoyMask, BuoyUnmask } from "./media/BuoyMask";
|
|
7
|
+
export { configureMedia, startRecording, stopRecording, markBugHere, probeFrame, mediaUnavailableReason, } from "./media/frames";
|
|
8
|
+
export type { BugLinksMediaConfig } from "./media/frames";
|
|
9
|
+
export { configureBugLinks, setBugLinksServer, bugLinksServer, DEFAULT_SERVER } from "./engine/settings";
|
|
10
|
+
export type { BugLinksConfig } from "./engine/settings";
|
|
11
|
+
export { reportBug, addNote, setLinkPriority, deleteLink, retryWaiting, reportState, subscribeReportState } from "./engine/reporter";
|
|
12
|
+
export { BUG_PRIORITIES, type BugPriority } from "./engine/client";
|
|
13
|
+
export type { ReportState, ReportPhase } from "./engine/reporter";
|
|
14
|
+
export { myLinks, subscribeMyLinks } from "./engine/linksStore";
|
|
15
|
+
export type { MyBugLink, MyBugLinkStatus } from "./engine/linksStore";
|
|
16
|
+
export { loadBugCase, loadAgain, undoBugCase, leaveSandbox, loadedCase, subscribeLoadedCase } from "./engine/loader";
|
|
17
|
+
export type { LoadReport, LoadStep, LoadedCase, LoadOptions } from "./engine/loader";
|
|
18
|
+
export { openBugLink, fetchBugLink } from "./engine/opener";
|
|
19
|
+
export { buildSandbox } from "./engine/sandbox";
|
|
20
|
+
export { phoneCaseCrypto, caseCrypto } from "./engine/phoneCrypto";
|
|
21
|
+
export { randomSource } from "./engine/randomBytes";
|
|
22
|
+
export { setBugLinksCredentialProvider } from "./engine/credential";
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markers for what pictures hide (SPEC.md H6 to H8). The native frame taker
|
|
3
|
+
* finds them by nativeID, so they stay as real views (collapsable={false}).
|
|
4
|
+
*
|
|
5
|
+
* <BuoyMask> hides this part and all inside it, even with a setting off.
|
|
6
|
+
* <BuoyUnmask> shows this part, even with a setting on. Password fields
|
|
7
|
+
* inside are still hidden, and Mask inside still hides.
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactNode } from "react";
|
|
10
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
11
|
+
interface MarkerProps {
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
style?: StyleProp<ViewStyle>;
|
|
14
|
+
testID?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function BuoyMask({ children, style, testID }: MarkerProps): import("react").JSX.Element;
|
|
17
|
+
export declare function BuoyUnmask({ children, style, testID }: MarkerProps): import("react").JSX.Element;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=BuoyMask.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type BugCase } from "@buoy-gg/shared-ui";
|
|
2
|
+
export interface BugLinksMediaConfig {
|
|
3
|
+
/** A frame after each tap (internal mode). Default true. */
|
|
4
|
+
pictures?: boolean;
|
|
5
|
+
/** "manual": the tester can record. "always": also keep the last 30 s. "off": no video. */
|
|
6
|
+
video?: "manual" | "always" | "off";
|
|
7
|
+
maskAllText?: boolean;
|
|
8
|
+
maskAllImages?: boolean;
|
|
9
|
+
maskAllInputs?: boolean;
|
|
10
|
+
/** "internal" for QA, devs and employees. "customer" for the app's own users: the last frame only. */
|
|
11
|
+
mode?: "internal" | "customer";
|
|
12
|
+
}
|
|
13
|
+
/** Why there are no frames at all on this build, or null. */
|
|
14
|
+
export declare function mediaUnavailableReason(): string | null;
|
|
15
|
+
/** Turns frames on with the app's settings (S1). Safe to call more than once. */
|
|
16
|
+
export declare function configureMedia(next?: BugLinksMediaConfig): void;
|
|
17
|
+
export declare function mediaConfig(): BugLinksMediaConfig;
|
|
18
|
+
/** Starts frames with the defaults the first time Bug Links is used. */
|
|
19
|
+
export declare function ensureMediaStarted(): void;
|
|
20
|
+
export declare function startRecording(): void;
|
|
21
|
+
export declare function stopRecording(): void;
|
|
22
|
+
export declare function markBugHere(): void;
|
|
23
|
+
/** Lets new frames in again after a case was made (F8 froze them). */
|
|
24
|
+
export declare function resumeMedia(): void;
|
|
25
|
+
/** The mask zoo test hook: one frame now, with its boxes. */
|
|
26
|
+
export declare function probeFrame(): Promise<unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Adds the `media` part to a case. Frames are linked to taps by time. Anything
|
|
29
|
+
* missing goes in `missing` with a reason (C5). Never throws.
|
|
30
|
+
*/
|
|
31
|
+
export declare function addMediaToCase(kase: BugCase): Promise<BugCase>;
|
|
32
|
+
/** Makes the case smaller when it's too big to send: fewer frames, down to none. */
|
|
33
|
+
export declare function shrinkMedia(kase: BugCase, bytes: number): BugCase;
|
|
34
|
+
//# sourceMappingURL=frames.d.ts.map
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pictures in a browser (research/bug-links-2026-10/media/SPEC.md section 3,
|
|
3
|
+
* "Web"). React Native Web, React DOM, Vue, Angular, Solid and every other
|
|
4
|
+
* page get the same frame taker, through `nativeFrames.web.ts`.
|
|
5
|
+
*
|
|
6
|
+
* A page can't be read back as pixels, so a frame is made from a copy:
|
|
7
|
+
*
|
|
8
|
+
* 1. Copy the page (`cloneNode`) in one go, so the copy can't change under us.
|
|
9
|
+
* 2. Hide the private parts IN THE COPY, before anything is drawn (H1): their
|
|
10
|
+
* text is wrapped and filled, their values cleared, their pictures swapped
|
|
11
|
+
* for a blank. Buoy's own screens are taken out of the copy (F5).
|
|
12
|
+
* 3. Draw the copy into a canvas through an SVG `<foreignObject>`, then fill
|
|
13
|
+
* each hidden part's box again from where it sat on the live page (H10).
|
|
14
|
+
* 4. JPEG, 1x, quality 0.6 (F1).
|
|
15
|
+
*
|
|
16
|
+
* Because the copy itself is hidden before it is drawn, a part that lands in
|
|
17
|
+
* the wrong place (a scroll the copy can't show, a font that didn't load) is
|
|
18
|
+
* still hidden. Anything in doubt drops the frame and keeps a reason (H2).
|
|
19
|
+
*/
|
|
20
|
+
export interface WebMaskSettings {
|
|
21
|
+
text: boolean;
|
|
22
|
+
images: boolean;
|
|
23
|
+
inputs: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface Box {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
}
|
|
31
|
+
interface Part {
|
|
32
|
+
testID: string;
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
w: number;
|
|
36
|
+
h: number;
|
|
37
|
+
}
|
|
38
|
+
interface Snapshot {
|
|
39
|
+
svg: string;
|
|
40
|
+
w: number;
|
|
41
|
+
h: number;
|
|
42
|
+
rects: Box[];
|
|
43
|
+
parts: Part[];
|
|
44
|
+
/** Parts hidden (V4). */
|
|
45
|
+
boxes: number;
|
|
46
|
+
mainMs: number;
|
|
47
|
+
}
|
|
48
|
+
/** True while a Buoy screen is open (F5). Cheap: run before copying anything. */
|
|
49
|
+
export declare function buoyScreenOpen(vw?: number, vh?: number): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Copies the page and hides what's private in the copy. Throws FrameError
|
|
52
|
+
* with a reason when the copy can't be trusted.
|
|
53
|
+
*/
|
|
54
|
+
export declare function snapshotPage(settings: WebMaskSettings, withParts?: boolean): Snapshot & {
|
|
55
|
+
buoyOpen: boolean;
|
|
56
|
+
};
|
|
57
|
+
export interface WebFramesOptions {
|
|
58
|
+
pictures?: boolean;
|
|
59
|
+
video?: "manual" | "always" | "off";
|
|
60
|
+
maskAllText?: boolean;
|
|
61
|
+
maskAllImages?: boolean;
|
|
62
|
+
maskAllInputs?: boolean;
|
|
63
|
+
mode?: "internal" | "customer";
|
|
64
|
+
videoFps?: number;
|
|
65
|
+
}
|
|
66
|
+
/** The same calls as the native BuoyFrames module, for a browser. */
|
|
67
|
+
export declare class WebFrameEngine {
|
|
68
|
+
private enabled;
|
|
69
|
+
private pictures;
|
|
70
|
+
private customer;
|
|
71
|
+
private masks;
|
|
72
|
+
private videoFps;
|
|
73
|
+
private taps;
|
|
74
|
+
private video;
|
|
75
|
+
private last;
|
|
76
|
+
private marks;
|
|
77
|
+
private dropped;
|
|
78
|
+
private frozen;
|
|
79
|
+
private busy;
|
|
80
|
+
private run;
|
|
81
|
+
private slowInRow;
|
|
82
|
+
private stoppedReason;
|
|
83
|
+
private lastReason;
|
|
84
|
+
private buttonLastT;
|
|
85
|
+
private downOnBuoy;
|
|
86
|
+
private tapTimer;
|
|
87
|
+
private videoTimer;
|
|
88
|
+
private videoOn;
|
|
89
|
+
private videoAlways;
|
|
90
|
+
private videoUntil;
|
|
91
|
+
private listening;
|
|
92
|
+
/** The frame being made now, so the last frame can wait for it (F8). */
|
|
93
|
+
private inflight;
|
|
94
|
+
/** A last frame waiting for the frame before it to finish (F8). */
|
|
95
|
+
private pendingLast;
|
|
96
|
+
/** M1: after a slow frame, tap and video frames wait until this time. */
|
|
97
|
+
private easeUntil;
|
|
98
|
+
configure(o?: WebFramesOptions): void;
|
|
99
|
+
status(): Promise<{
|
|
100
|
+
available: boolean;
|
|
101
|
+
enabled: boolean;
|
|
102
|
+
mode: string;
|
|
103
|
+
stopped: string | null;
|
|
104
|
+
}>;
|
|
105
|
+
private listen;
|
|
106
|
+
/** A touch on Buoy: take the last frame now, before the dial or the sheet draws (F8). */
|
|
107
|
+
private onDown;
|
|
108
|
+
private onUp;
|
|
109
|
+
startVideo(always: boolean): void;
|
|
110
|
+
stopVideo(): void;
|
|
111
|
+
mark(): void;
|
|
112
|
+
/** F8: the last frame. Buoy is open by now, so the frame from the touch on Buoy wins while it's new. */
|
|
113
|
+
captureLast(): Promise<{
|
|
114
|
+
ok: boolean;
|
|
115
|
+
reason: string | null;
|
|
116
|
+
}>;
|
|
117
|
+
takeMedia(): Promise<{
|
|
118
|
+
frames: {
|
|
119
|
+
t: number;
|
|
120
|
+
kind: "last" | "tap" | "video";
|
|
121
|
+
w: number;
|
|
122
|
+
h: number;
|
|
123
|
+
jpeg: string;
|
|
124
|
+
boxes: number;
|
|
125
|
+
}[];
|
|
126
|
+
marks: number[];
|
|
127
|
+
dropped: number;
|
|
128
|
+
reasons: {
|
|
129
|
+
[x: string]: number;
|
|
130
|
+
};
|
|
131
|
+
stopped: string | null;
|
|
132
|
+
lastAgeMs: number | null;
|
|
133
|
+
settings: {
|
|
134
|
+
maskAllText: boolean;
|
|
135
|
+
maskAllImages: boolean;
|
|
136
|
+
maskAllInputs: boolean;
|
|
137
|
+
};
|
|
138
|
+
}>;
|
|
139
|
+
resume(): void;
|
|
140
|
+
clear(): void;
|
|
141
|
+
stopAll(): void;
|
|
142
|
+
/** Test hook for the mask zoo: one frame now, with its boxes and JPEG. */
|
|
143
|
+
probe(): Promise<{
|
|
144
|
+
ok: boolean;
|
|
145
|
+
reason: string | null;
|
|
146
|
+
failed: string | null;
|
|
147
|
+
parts: never[];
|
|
148
|
+
buoyOpen: boolean;
|
|
149
|
+
jpeg: null;
|
|
150
|
+
w: number;
|
|
151
|
+
h: number;
|
|
152
|
+
} | {
|
|
153
|
+
ok: boolean;
|
|
154
|
+
reason: null;
|
|
155
|
+
failed: null;
|
|
156
|
+
parts: Part[];
|
|
157
|
+
buoyOpen: boolean;
|
|
158
|
+
jpeg: string;
|
|
159
|
+
w: number;
|
|
160
|
+
h: number;
|
|
161
|
+
} | {
|
|
162
|
+
ok: boolean;
|
|
163
|
+
reason: string;
|
|
164
|
+
failed: null;
|
|
165
|
+
parts: Part[];
|
|
166
|
+
buoyOpen: boolean;
|
|
167
|
+
jpeg: null;
|
|
168
|
+
w: number;
|
|
169
|
+
h: number;
|
|
170
|
+
}>;
|
|
171
|
+
private drop;
|
|
172
|
+
private take;
|
|
173
|
+
private makeFrame;
|
|
174
|
+
private noteSpeed;
|
|
175
|
+
private keep;
|
|
176
|
+
}
|
|
177
|
+
export {};
|
|
178
|
+
//# sourceMappingURL=webFrames.d.ts.map
|