@fastbackr/core 0.0.0-stage → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,46 @@
1
+ Fastbackr SDK License
2
+
3
+ Copyright (c) 2026 OpsCom, Corp. All rights reserved.
4
+
5
+ This license covers the "@fastbackr/core" npm package and its source code (the
6
+ "Software"). The Software is not open source. By installing or using it you
7
+ accept these terms.
8
+
9
+ 1. Grant. OpsCom, Corp. grants you a free, non-exclusive, non-transferable,
10
+ non-sublicensable license to install and use the Software, solely to connect
11
+ your websites and applications to the Fastbackr service.
12
+
13
+ 2. Redistribution. You may redistribute the Software only unmodified and only
14
+ as part of your own website or application. Bundling, minifying,
15
+ transpiling or tree-shaking the Software with your build tools, as part of
16
+ building your own website or application, is not a modification under
17
+ this license.
18
+
19
+ 3. Restrictions. Except as section 2 allows, you may not:
20
+ a. modify, adapt or translate the Software, or create derivative works of
21
+ it;
22
+ b. distribute, publish or make available any modified version of the
23
+ Software;
24
+ c. use the Software with any service other than the Fastbackr service;
25
+ d. sell, rent, lease or sublicense the Software on its own;
26
+ e. remove or alter this license or any copyright notice;
27
+ f. reverse engineer, decompile or disassemble the Software, except to the
28
+ extent applicable law expressly permits it despite this restriction.
29
+
30
+ 4. Ownership. The Software is licensed, not sold. OpsCom, Corp. keeps all
31
+ rights, title and interest in the Software not expressly granted here.
32
+
33
+ 5. Termination. This license ends automatically if you breach any of its
34
+ terms. On termination you must stop using the Software and remove it from
35
+ your websites and applications.
36
+
37
+ 6. No warranty. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38
+ EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
39
+ A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
40
+
41
+ 7. Limitation of liability. IN NO EVENT SHALL OPSCOM, CORP. BE LIABLE FOR ANY
42
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
43
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
44
+ OF OR OTHER DEALINGS IN THE SOFTWARE.
45
+
46
+ 8. Governing law. This license is governed by the laws of the United States.
package/README.md CHANGED
@@ -1,3 +1,18 @@
1
- # Temporary Holding Version
1
+ # @fastbackr/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ What every Fastbackr SDK shares: the API's contract, the helper your server calls, and the browser
4
+ logic a UI package draws on. React apps install it next to `@fastbackr/react`.
5
+
6
+ | Import | Runs in | What it is |
7
+ | -------------------------- | ----------- | --------------------------------------------------------------- |
8
+ | `@fastbackr/core/server` | Your server | `createTokenHandler`, `createReviewerToken`, `FastbackrError` |
9
+ | `@fastbackr/core/contract` | Anywhere | The API's request and response types and constants |
10
+ | `@fastbackr/core/boot` | Browser | What runs before the UI loads: `+` button, gesture, CSP warning |
11
+ | `@fastbackr/core` | Browser | Tokens, the API client, anchors, select mode, the Shadow layer |
12
+
13
+ `boot` is kept small because it loads on every page view; the main entry arrives with the lazy UI.
14
+ Neither draws panels: each UI package draws its own. See `packages/react/README.md` for setup.
15
+
16
+ ## License
17
+
18
+ Free to install and use with the Fastbackr service. Modifying it is not allowed. See `LICENSE`.
package/dist/boot.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { $ as DEFAULT_ENDPOINT, et as normalizeEndpoint } from "./contract-DuT5YjZ3.js";
2
+ import { a as rememberHidden, c as watchRevealGesture, n as GetToken, o as rememberVisible, r as isHidden, s as shouldRevealOnLoad, t as FastbackrOptions } from "./types-BV61V0KT.js";
3
+ //#region src/csp.d.ts
4
+ export declare function watchCsp(endpointOrigin: string, signal: AbortSignal): void;
5
+ //#endregion
6
+ //#region src/fab.d.ts
7
+ export declare class Fab {
8
+ private readonly host;
9
+ private readonly root;
10
+ private wanted;
11
+ private readonly glow;
12
+ constructor(click: () => void);
13
+ show(): void;
14
+ flash(): void;
15
+ private place;
16
+ hide(): void;
17
+ }
18
+ //#endregion
19
+ //#region src/token-route.d.ts
20
+ export declare function tokenRoute(url?: string): GetToken;
21
+ //#endregion
22
+ export { DEFAULT_ENDPOINT, type FastbackrOptions, type GetToken, isHidden, normalizeEndpoint, rememberHidden, rememberVisible, shouldRevealOnLoad, watchRevealGesture };
package/dist/boot.js ADDED
@@ -0,0 +1,94 @@
1
+ import { g as normalizeEndpoint, h as DEFAULT_ENDPOINT, n as DEFAULT_TOKEN_ROUTE } from "./contract-CzTIqzyE.js";
2
+ import { n as errorFromResponse } from "./error-rNSfYFOm.js";
3
+ import { a as rememberHidden, c as watchRevealGesture, o as rememberVisible, r as isHidden, s as shouldRevealOnLoad } from "./gesture-CSkNZJqY.js";
4
+ //#region src/csp.ts
5
+ function blocksEndpoint(event, endpointOrigin) {
6
+ if (event.effectiveDirective !== "connect-src" && event.effectiveDirective !== "default-src") return false;
7
+ try {
8
+ return new URL(event.blockedURI).origin === endpointOrigin;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+ function cspMessage(endpointOrigin) {
14
+ return `Fastbackr: this page's Content-Security-Policy blocks ${endpointOrigin}, so feedback cannot load. Add it to connect-src, for example: connect-src 'self' ${endpointOrigin}`;
15
+ }
16
+ function watchCsp(endpointOrigin, signal) {
17
+ let logged = false;
18
+ document.addEventListener("securitypolicyviolation", (event) => {
19
+ if (logged || !blocksEndpoint(event, endpointOrigin)) return;
20
+ logged = true;
21
+ console.error(cspMessage(endpointOrigin));
22
+ }, { signal });
23
+ }
24
+ //#endregion
25
+ //#region src/fab.ts
26
+ const CSS = ":host{all:initial}button{position:fixed;right:16px;bottom:16px;z-index:2147483646;box-sizing:border-box;width:52px;height:52px;padding:8px;border:0;border-radius:50%;background:#fff;cursor:pointer;box-shadow:0 0 0 1px #090b0c1a,0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}span{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:#9ae600;color:#35530e}svg{width:24px;height:24px}button:hover span{background:#b4f033}button{animation:fade-in .25s ease-out}@keyframes fade-in{from{opacity:0}}button:focus-visible{outline:3px solid #9ca8ab80;outline-offset:0}i{position:fixed;right:14px;bottom:14px;z-index:2147483645;width:56px;height:56px;border-radius:50%;pointer-events:none;animation:glow .3s linear both}i::before,i::after{content:'';position:absolute;inset:0;border-radius:50%;background:conic-gradient(transparent,#ff3b30 4%,#ff9500 7%,#ffcc00 10%,#34c759 13%,#007aff 16%,#af52de 19%,transparent 23%)}i::before{filter:blur(10px);opacity:.75}i.flash{animation-duration:2s}@keyframes glow{from{rotate:0deg;opacity:0}15%,80%{opacity:1}to{rotate:360deg;opacity:0}}@media (min-width:640px){button{right:22px;bottom:22px}i{right:20px;bottom:20px}}@media (prefers-reduced-motion:reduce){button{animation:none}i{display:none}}@media print{button,i{display:none}}";
27
+ const PLUS = "<span><svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M11 11V5H13V11H19V13H13V19H11V13H5V11H11Z\"/></svg></span>";
28
+ var Fab = class {
29
+ host = document.createElement("div");
30
+ root;
31
+ wanted = false;
32
+ glow = document.createElement("i");
33
+ constructor(click) {
34
+ this.host.dataset.fastbackrRoot = "";
35
+ this.root = this.host.attachShadow({ mode: "open" });
36
+ const style = document.createElement("style");
37
+ style.textContent = CSS;
38
+ const button = document.createElement("button");
39
+ button.type = "button";
40
+ button.innerHTML = PLUS;
41
+ button.setAttribute("aria-label", "Leave feedback");
42
+ button.title = "Leave feedback";
43
+ button.addEventListener("click", click);
44
+ this.glow.addEventListener("animationend", () => this.glow.remove());
45
+ this.root.append(style, this.glow, button);
46
+ }
47
+ show() {
48
+ this.wanted = true;
49
+ if (!document.body) {
50
+ document.addEventListener("DOMContentLoaded", () => this.place(), { once: true });
51
+ return;
52
+ }
53
+ this.place();
54
+ }
55
+ flash() {
56
+ this.glow.remove();
57
+ this.glow.className = "flash";
58
+ this.root.prepend(this.glow);
59
+ }
60
+ place() {
61
+ if (this.wanted && !this.host.isConnected) document.body.append(this.host);
62
+ }
63
+ hide() {
64
+ this.wanted = false;
65
+ this.glow.remove();
66
+ this.host.remove();
67
+ }
68
+ };
69
+ //#endregion
70
+ //#region src/token-route.ts
71
+ function tokenRoute(url = DEFAULT_TOKEN_ROUTE) {
72
+ return async (guest) => {
73
+ const body = guest ? { guest: {
74
+ id: guest.id,
75
+ name: guest.name
76
+ } } : {};
77
+ const response = await fetch(url, {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify(body),
81
+ cache: "no-store",
82
+ credentials: "same-origin"
83
+ });
84
+ if (response.status === 401) return null;
85
+ if (!response.ok) {
86
+ const error = await errorFromResponse(response);
87
+ if (error.code === "guest_access_off") return null;
88
+ throw error;
89
+ }
90
+ return await response.json();
91
+ };
92
+ }
93
+ //#endregion
94
+ export { DEFAULT_ENDPOINT, Fab, isHidden, normalizeEndpoint, rememberHidden, rememberVisible, shouldRevealOnLoad, tokenRoute, watchCsp, watchRevealGesture };
@@ -0,0 +1,82 @@
1
+ //#region src/endpoint.ts
2
+ const DEFAULT_ENDPOINT = "https://api.fastbackr.com";
3
+ function normalizeEndpoint(value) {
4
+ let url;
5
+ try {
6
+ url = new URL(value);
7
+ } catch {
8
+ throw new TypeError(`Fastbackr endpoint must be an absolute URL, got "${value}".`);
9
+ }
10
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new TypeError(`Fastbackr endpoint must use http or https, got "${url.protocol}".`);
11
+ if (url.search || url.hash) throw new TypeError("Fastbackr endpoint must not carry a query or hash.");
12
+ return `${url.origin}${url.pathname}`.replace(/\/+$/, "");
13
+ }
14
+ //#endregion
15
+ //#region src/contract.ts
16
+ const API_VERSION = "v1";
17
+ const ROUTES = {
18
+ tokens: "/v1/tokens",
19
+ threads: "/v1/threads",
20
+ thread: "/v1/threads/:id",
21
+ messages: "/v1/threads/:id/messages",
22
+ relocate: "/v1/threads/:id/relocate",
23
+ suggestion: "/v1/threads/:id/suggestion",
24
+ screenshot: "/v1/threads/:id/screenshot"
25
+ };
26
+ const paths = {
27
+ tokens: () => ROUTES.tokens,
28
+ threads: () => ROUTES.threads,
29
+ thread: (id) => `${ROUTES.threads}/${encodeURIComponent(id)}`,
30
+ messages: (id) => `${ROUTES.threads}/${encodeURIComponent(id)}/messages`,
31
+ relocate: (id) => `${ROUTES.threads}/${encodeURIComponent(id)}/relocate`,
32
+ suggestion: (id) => `${ROUTES.threads}/${encodeURIComponent(id)}/suggestion`,
33
+ screenshot: (id) => `${ROUTES.threads}/${encodeURIComponent(id)}/screenshot`
34
+ };
35
+ const LIMITS = {
36
+ bodyChars: 1e4,
37
+ anchorBytes: 32768,
38
+ pathChars: 2048,
39
+ titleChars: 300,
40
+ shortChars: 200,
41
+ requestBytes: 65536,
42
+ relocateBytes: 2097152,
43
+ screenshotBytes: 1048576,
44
+ pathSteps: 100,
45
+ stepClasses: 6,
46
+ listItems: 500,
47
+ ancestorHints: 6,
48
+ selectorCandidates: 4,
49
+ selectorChars: 1e3,
50
+ tagNameChars: 60,
51
+ reviewerIdChars: 200,
52
+ reviewerNameChars: 200,
53
+ emailChars: 320,
54
+ excerptChars: 140
55
+ };
56
+ const SENSITIVE_QUERY_KEY = /token|password|secret|credential|authorization|__done_/i;
57
+ const GUEST_ACCESS = [
58
+ "off",
59
+ "write-only",
60
+ "read-write"
61
+ ];
62
+ const TOKEN_ALGORITHM = "HS256";
63
+ const TOKEN_TTL_SECONDS = 3600;
64
+ const DEFAULT_TOKEN_ROUTE = "/api/fastbackr";
65
+ const THREAD_STATUSES = ["open", "done"];
66
+ const SUGGESTION_DECISIONS = ["accept", "reject"];
67
+ const SCREENSHOT_TYPES = ["image/webp", "image/jpeg"];
68
+ const SCREENSHOT_WINDOW_SECONDS = 600;
69
+ const ERROR_CODES = [
70
+ "invalid_request",
71
+ "invalid_content_type",
72
+ "request_too_large",
73
+ "unauthorized",
74
+ "forbidden",
75
+ "origin_denied",
76
+ "guest_access_off",
77
+ "not_found",
78
+ "rate_limited",
79
+ "internal"
80
+ ];
81
+ //#endregion
82
+ export { LIMITS as a, SCREENSHOT_WINDOW_SECONDS as c, THREAD_STATUSES as d, TOKEN_ALGORITHM as f, normalizeEndpoint as g, DEFAULT_ENDPOINT as h, GUEST_ACCESS as i, SENSITIVE_QUERY_KEY as l, paths as m, DEFAULT_TOKEN_ROUTE as n, ROUTES as o, TOKEN_TTL_SECONDS as p, ERROR_CODES as r, SCREENSHOT_TYPES as s, API_VERSION as t, SUGGESTION_DECISIONS as u };
@@ -0,0 +1,230 @@
1
+ //#region src/endpoint.d.ts
2
+ declare const DEFAULT_ENDPOINT = "https://api.fastbackr.com";
3
+ declare function normalizeEndpoint(value: string): string;
4
+ //#endregion
5
+ //#region src/contract.d.ts
6
+ declare const API_VERSION = "v1";
7
+ declare const ROUTES: {
8
+ readonly tokens: "/v1/tokens";
9
+ readonly threads: "/v1/threads";
10
+ readonly thread: "/v1/threads/:id";
11
+ readonly messages: "/v1/threads/:id/messages";
12
+ readonly relocate: "/v1/threads/:id/relocate";
13
+ readonly suggestion: "/v1/threads/:id/suggestion";
14
+ readonly screenshot: "/v1/threads/:id/screenshot";
15
+ };
16
+ type Route = (typeof ROUTES)[keyof typeof ROUTES];
17
+ declare const paths: {
18
+ readonly tokens: () => string;
19
+ readonly threads: () => string;
20
+ readonly thread: (id: string) => string;
21
+ readonly messages: (id: string) => string;
22
+ readonly relocate: (id: string) => string;
23
+ readonly suggestion: (id: string) => string;
24
+ readonly screenshot: (id: string) => string;
25
+ };
26
+ declare const LIMITS: {
27
+ readonly bodyChars: 10000;
28
+ readonly anchorBytes: number;
29
+ readonly pathChars: 2048;
30
+ readonly titleChars: 300;
31
+ readonly shortChars: 200;
32
+ readonly requestBytes: number;
33
+ readonly relocateBytes: number;
34
+ readonly screenshotBytes: number;
35
+ readonly pathSteps: 100;
36
+ readonly stepClasses: 6;
37
+ readonly listItems: 500;
38
+ readonly ancestorHints: 6;
39
+ readonly selectorCandidates: 4;
40
+ readonly selectorChars: 1000;
41
+ readonly tagNameChars: 60;
42
+ readonly reviewerIdChars: 200;
43
+ readonly reviewerNameChars: 200;
44
+ readonly emailChars: 320;
45
+ readonly excerptChars: 140;
46
+ };
47
+ declare const SENSITIVE_QUERY_KEY: RegExp;
48
+ declare const GUEST_ACCESS: readonly ["off", "write-only", "read-write"];
49
+ type GuestAccess = (typeof GUEST_ACCESS)[number];
50
+ type Reviewer = Readonly<{
51
+ id: string;
52
+ name: string;
53
+ email?: string;
54
+ verified: boolean;
55
+ }>;
56
+ type GuestIdentity = Readonly<{
57
+ id: string;
58
+ name: string;
59
+ }>;
60
+ declare const TOKEN_ALGORITHM = "HS256";
61
+ declare const TOKEN_TTL_SECONDS: number;
62
+ type TokenClaims = Readonly<{
63
+ sub: string;
64
+ pid: string;
65
+ name: string;
66
+ email?: string;
67
+ verified: boolean;
68
+ iat: number;
69
+ exp: number;
70
+ }>;
71
+ type CreateTokenRequest = Readonly<{
72
+ user: Reviewer;
73
+ }>;
74
+ type Features = Readonly<{
75
+ screenshots: boolean;
76
+ }>;
77
+ type CreateTokenResponse = Readonly<{
78
+ token: string;
79
+ expiresAt: string;
80
+ user: Reviewer;
81
+ features?: Features;
82
+ }>;
83
+ declare const DEFAULT_TOKEN_ROUTE = "/api/fastbackr";
84
+ type TokenRouteRequest = Readonly<{
85
+ guest?: GuestIdentity;
86
+ }>;
87
+ type PageContext = Readonly<{
88
+ key: string;
89
+ path: string;
90
+ title: string;
91
+ }>;
92
+ type AnchorHint = Readonly<{
93
+ tagName: string;
94
+ stableId?: string;
95
+ label?: string;
96
+ }>;
97
+ type AnchorRegion = Readonly<{
98
+ xRatio: number;
99
+ yRatio: number;
100
+ widthRatio: number;
101
+ heightRatio: number;
102
+ }>;
103
+ type ElementSignature = Readonly<{
104
+ tag: string;
105
+ id?: string;
106
+ stableId?: string;
107
+ role?: string;
108
+ classes?: readonly string[];
109
+ textHash?: string;
110
+ }>;
111
+ type PathStep = ElementSignature & Readonly<{
112
+ index: number;
113
+ }>;
114
+ type SkeletonNode = ElementSignature & Readonly<{
115
+ children?: readonly SkeletonNode[];
116
+ }>;
117
+ type Anchor = Readonly<{
118
+ version: 1;
119
+ kind: "element";
120
+ stableId?: string;
121
+ componentLabel?: string;
122
+ tagName: string;
123
+ role?: string;
124
+ accessibleLabel?: string;
125
+ textExcerpt?: string;
126
+ elementId?: string;
127
+ ancestorHints: readonly AnchorHint[];
128
+ selectorCandidates: readonly string[];
129
+ boundary: "element" | "marked-component";
130
+ clickOffset: Readonly<{
131
+ xRatio: number;
132
+ yRatio: number;
133
+ }>;
134
+ capturedRect: Readonly<{
135
+ x: number;
136
+ y: number;
137
+ width: number;
138
+ height: number;
139
+ }>;
140
+ region?: AnchorRegion;
141
+ viewport?: Readonly<{
142
+ width: number;
143
+ height: number;
144
+ }>;
145
+ path?: readonly PathStep[];
146
+ }>;
147
+ declare const THREAD_STATUSES: readonly ["open", "done"];
148
+ type ThreadStatus = (typeof THREAD_STATUSES)[number];
149
+ type Thread = Readonly<{
150
+ id: string;
151
+ number: number;
152
+ pageKey: string;
153
+ path: string;
154
+ title: string;
155
+ anchor: Anchor;
156
+ status: ThreadStatus;
157
+ createdAt: string;
158
+ updatedAt: string;
159
+ authorName: string;
160
+ authorVerified: boolean;
161
+ excerpt: string;
162
+ messageCount: number;
163
+ hasScreenshot: boolean;
164
+ suggestion?: AnchorSuggestion;
165
+ }>;
166
+ type AnchorSuggestion = Readonly<{
167
+ id: string;
168
+ anchor: Anchor;
169
+ confidence: number;
170
+ }>;
171
+ type Message = Readonly<{
172
+ id: string;
173
+ authorName: string;
174
+ authorVerified: boolean;
175
+ body: string;
176
+ createdAt: string;
177
+ }>;
178
+ type ListThreadsQuery = Readonly<{
179
+ status?: ThreadStatus | "all";
180
+ page?: string;
181
+ }>;
182
+ type ListThreadsResponse = Readonly<{
183
+ items: readonly Thread[];
184
+ }>;
185
+ type CreateThreadRequest = Readonly<{
186
+ page: PageContext;
187
+ anchor: Anchor;
188
+ body: string;
189
+ }>;
190
+ type CreateThreadResponse = Thread;
191
+ type ThreadDetail = Readonly<{
192
+ thread: Thread;
193
+ messages: readonly Message[];
194
+ }>;
195
+ type GetThreadResponse = ThreadDetail;
196
+ type UpdateThreadRequest = Readonly<{
197
+ status: ThreadStatus;
198
+ }>;
199
+ type UpdateThreadResponse = Thread;
200
+ type CreateMessageRequest = Readonly<{
201
+ body: string;
202
+ }>;
203
+ type CreateMessageResponse = Message;
204
+ type RelocateRequest = Readonly<{
205
+ breakAt: number;
206
+ skeleton: SkeletonNode;
207
+ }>;
208
+ type RelocateResponse = Readonly<{
209
+ suggestion: AnchorSuggestion | null;
210
+ }>;
211
+ declare const SUGGESTION_DECISIONS: readonly ["accept", "reject"];
212
+ type SuggestionDecision = (typeof SUGGESTION_DECISIONS)[number];
213
+ type DecideSuggestionRequest = Readonly<{
214
+ suggestionId: string;
215
+ decision: SuggestionDecision;
216
+ }>;
217
+ type DecideSuggestionResponse = Thread;
218
+ declare const SCREENSHOT_TYPES: readonly ["image/webp", "image/jpeg"];
219
+ type ScreenshotType = (typeof SCREENSHOT_TYPES)[number];
220
+ declare const SCREENSHOT_WINDOW_SECONDS: number;
221
+ declare const ERROR_CODES: readonly ["invalid_request", "invalid_content_type", "request_too_large", "unauthorized", "forbidden", "origin_denied", "guest_access_off", "not_found", "rate_limited", "internal"];
222
+ type ErrorCode = (typeof ERROR_CODES)[number];
223
+ type ErrorEnvelope = Readonly<{
224
+ error: Readonly<{
225
+ code: ErrorCode;
226
+ message: string;
227
+ }>;
228
+ }>;
229
+ //#endregion
230
+ export { DEFAULT_ENDPOINT as $, ROUTES as A, SkeletonNode as B, GuestIdentity as C, Message as D, ListThreadsResponse as E, SCREENSHOT_TYPES as F, Thread as G, THREAD_STATUSES as H, SCREENSHOT_WINDOW_SECONDS as I, TokenClaims as J, ThreadDetail as K, SENSITIVE_QUERY_KEY as L, RelocateResponse as M, Reviewer as N, PageContext as O, Route as P, paths as Q, SUGGESTION_DECISIONS as R, GuestAccess as S, ListThreadsQuery as T, TOKEN_ALGORITHM as U, SuggestionDecision as V, TOKEN_TTL_SECONDS as W, UpdateThreadRequest as X, TokenRouteRequest as Y, UpdateThreadResponse as Z, ErrorCode as _, AnchorSuggestion as a, GUEST_ACCESS as b, CreateThreadRequest as c, CreateTokenResponse as d, normalizeEndpoint as et, DEFAULT_TOKEN_ROUTE as f, ElementSignature as g, ERROR_CODES as h, AnchorRegion as i, RelocateRequest as j, PathStep as k, CreateThreadResponse as l, DecideSuggestionResponse as m, Anchor as n, CreateMessageRequest as o, DecideSuggestionRequest as p, ThreadStatus as q, AnchorHint as r, CreateMessageResponse as s, API_VERSION as t, CreateTokenRequest as u, ErrorEnvelope as v, LIMITS as w, GetThreadResponse as x, Features as y, ScreenshotType as z };
@@ -0,0 +1,2 @@
1
+ import { $ as DEFAULT_ENDPOINT, A as ROUTES, B as SkeletonNode, C as GuestIdentity, D as Message, E as ListThreadsResponse, F as SCREENSHOT_TYPES, G as Thread, H as THREAD_STATUSES, I as SCREENSHOT_WINDOW_SECONDS, J as TokenClaims, K as ThreadDetail, L as SENSITIVE_QUERY_KEY, M as RelocateResponse, N as Reviewer, O as PageContext, P as Route, Q as paths, R as SUGGESTION_DECISIONS, S as GuestAccess, T as ListThreadsQuery, U as TOKEN_ALGORITHM, V as SuggestionDecision, W as TOKEN_TTL_SECONDS, X as UpdateThreadRequest, Y as TokenRouteRequest, Z as UpdateThreadResponse, _ as ErrorCode, a as AnchorSuggestion, b as GUEST_ACCESS, c as CreateThreadRequest, d as CreateTokenResponse, f as DEFAULT_TOKEN_ROUTE, g as ElementSignature, h as ERROR_CODES, i as AnchorRegion, j as RelocateRequest, k as PathStep, l as CreateThreadResponse, m as DecideSuggestionResponse, n as Anchor, o as CreateMessageRequest, p as DecideSuggestionRequest, q as ThreadStatus, r as AnchorHint, s as CreateMessageResponse, t as API_VERSION, u as CreateTokenRequest, v as ErrorEnvelope, w as LIMITS, x as GetThreadResponse, y as Features, z as ScreenshotType } from "./contract-DuT5YjZ3.js";
2
+ export { API_VERSION, Anchor, AnchorHint, AnchorRegion, AnchorSuggestion, CreateMessageRequest, CreateMessageResponse, CreateThreadRequest, CreateThreadResponse, CreateTokenRequest, CreateTokenResponse, DEFAULT_ENDPOINT, DEFAULT_TOKEN_ROUTE, DecideSuggestionRequest, DecideSuggestionResponse, ERROR_CODES, ElementSignature, ErrorCode, ErrorEnvelope, Features, GUEST_ACCESS, GetThreadResponse, GuestAccess, GuestIdentity, LIMITS, ListThreadsQuery, ListThreadsResponse, Message, PageContext, PathStep, ROUTES, RelocateRequest, RelocateResponse, Reviewer, Route, SCREENSHOT_TYPES, SCREENSHOT_WINDOW_SECONDS, SENSITIVE_QUERY_KEY, SUGGESTION_DECISIONS, ScreenshotType, SkeletonNode, SuggestionDecision, THREAD_STATUSES, TOKEN_ALGORITHM, TOKEN_TTL_SECONDS, Thread, ThreadDetail, ThreadStatus, TokenClaims, TokenRouteRequest, UpdateThreadRequest, UpdateThreadResponse, paths };
@@ -0,0 +1,2 @@
1
+ import { a as LIMITS, c as SCREENSHOT_WINDOW_SECONDS, d as THREAD_STATUSES, f as TOKEN_ALGORITHM, h as DEFAULT_ENDPOINT, i as GUEST_ACCESS, l as SENSITIVE_QUERY_KEY, m as paths, n as DEFAULT_TOKEN_ROUTE, o as ROUTES, p as TOKEN_TTL_SECONDS, r as ERROR_CODES, s as SCREENSHOT_TYPES, t as API_VERSION, u as SUGGESTION_DECISIONS } from "./contract-CzTIqzyE.js";
2
+ export { API_VERSION, DEFAULT_ENDPOINT, DEFAULT_TOKEN_ROUTE, ERROR_CODES, GUEST_ACCESS, LIMITS, ROUTES, SCREENSHOT_TYPES, SCREENSHOT_WINDOW_SECONDS, SENSITIVE_QUERY_KEY, SUGGESTION_DECISIONS, THREAD_STATUSES, TOKEN_ALGORITHM, TOKEN_TTL_SECONDS, paths };
@@ -0,0 +1,36 @@
1
+ import { r as ERROR_CODES } from "./contract-CzTIqzyE.js";
2
+ //#region src/error.ts
3
+ var FastbackrError = class extends Error {
4
+ status;
5
+ code;
6
+ constructor(status, code, message, options) {
7
+ super(message, options);
8
+ this.name = "FastbackrError";
9
+ this.status = status;
10
+ this.code = code;
11
+ }
12
+ };
13
+ const KNOWN_CODES = new Set(ERROR_CODES);
14
+ function isErrorCode(value) {
15
+ return typeof value === "string" && KNOWN_CODES.has(value);
16
+ }
17
+ function codeForStatus(status) {
18
+ switch (status) {
19
+ case 400: return "invalid_request";
20
+ case 401: return "unauthorized";
21
+ case 403: return "forbidden";
22
+ case 404: return "not_found";
23
+ case 413: return "request_too_large";
24
+ case 415: return "invalid_content_type";
25
+ case 429: return "rate_limited";
26
+ default: return "internal";
27
+ }
28
+ }
29
+ async function errorFromResponse(response) {
30
+ const envelope = (await response.json().catch(() => null))?.error;
31
+ const code = isErrorCode(envelope?.code) ? envelope.code : codeForStatus(response.status);
32
+ const message = typeof envelope?.message === "string" && envelope.message ? envelope.message : `Fastbackr answered ${response.status}.`;
33
+ return new FastbackrError(response.status, code, message);
34
+ }
35
+ //#endregion
36
+ export { errorFromResponse as n, FastbackrError as t };
@@ -0,0 +1,60 @@
1
+ //#region src/gesture.ts
2
+ function editable(event) {
3
+ return event.composedPath().some((node) => node instanceof Element && (node.matches("input,textarea,select,[role=\"textbox\"]") || node instanceof HTMLElement && node.isContentEditable));
4
+ }
5
+ function plainKey(event) {
6
+ return !(event.repeat || event.isComposing || editable(event) || event.ctrlKey || event.metaKey || event.altKey);
7
+ }
8
+ const GESTURE_GAP_MS = 2e3;
9
+ const GESTURE_KEY = "f";
10
+ const GESTURE_COUNT = 3;
11
+ function watchRevealGesture(reveal, signal) {
12
+ let count = 0;
13
+ let last = 0;
14
+ document.addEventListener("keydown", (event) => {
15
+ if (!plainKey(event) || event.key.toLowerCase() !== GESTURE_KEY) {
16
+ count = 0;
17
+ return;
18
+ }
19
+ const now = performance.now();
20
+ count = now - last > GESTURE_GAP_MS ? 1 : count + 1;
21
+ last = now;
22
+ if (count === GESTURE_COUNT) {
23
+ count = 0;
24
+ reveal();
25
+ }
26
+ }, { signal });
27
+ window.addEventListener("blur", () => {
28
+ count = 0;
29
+ }, { signal });
30
+ }
31
+ const THREAD_LINK_KEY = "__done_thread";
32
+ const VISIBLE_KEY = "fastbackr:visible";
33
+ function rememberVisible(visible) {
34
+ try {
35
+ if (visible) sessionStorage.setItem(VISIBLE_KEY, "1");
36
+ else sessionStorage.removeItem(VISIBLE_KEY);
37
+ } catch {}
38
+ }
39
+ const HIDDEN_KEY = "fastbackr:hidden";
40
+ function rememberHidden(hidden) {
41
+ try {
42
+ if (hidden) localStorage.setItem(HIDDEN_KEY, "1");
43
+ else localStorage.removeItem(HIDDEN_KEY);
44
+ } catch {}
45
+ }
46
+ function isHidden() {
47
+ try {
48
+ return localStorage.getItem(HIDDEN_KEY) === "1";
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ function shouldRevealOnLoad() {
54
+ try {
55
+ if (sessionStorage.getItem(VISIBLE_KEY) === "1") return true;
56
+ } catch {}
57
+ return new URL(location.href).searchParams.has(THREAD_LINK_KEY);
58
+ }
59
+ //#endregion
60
+ export { rememberHidden as a, watchRevealGesture as c, plainKey as i, editable as n, rememberVisible as o, isHidden as r, shouldRevealOnLoad as s, THREAD_LINK_KEY as t };