reflet-sdk 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +54 -17
  3. package/dist/desktop.d.ts +24 -0
  4. package/dist/desktop.d.ts.map +1 -0
  5. package/dist/desktop.js +118 -0
  6. package/dist/desktop.js.map +1 -0
  7. package/dist/{devtools-layer-CsIcpepI.js → devtools-layer-o1O55wu1.js} +396 -277
  8. package/dist/devtools-layer-o1O55wu1.js.map +1 -0
  9. package/dist/devtools-next.d.ts.map +1 -1
  10. package/dist/devtools-next.js +1 -1
  11. package/dist/{sessions-panel-BYM0UUgT.js → devtools-styles-TWR5Ps34.js} +2078 -1686
  12. package/dist/devtools-styles-TWR5Ps34.js.map +1 -0
  13. package/dist/devtools-vite.js +2 -1
  14. package/dist/devtools-vite.js.map +1 -1
  15. package/dist/feedback.d.ts +7 -3
  16. package/dist/feedback.d.ts.map +1 -1
  17. package/dist/feedback.js +21 -12
  18. package/dist/feedback.js.map +1 -1
  19. package/dist/{handler-17FmwoSX.js → handler-ndF3gZBx.js} +11 -416
  20. package/dist/handler-ndF3gZBx.js.map +1 -0
  21. package/dist/reflet-proxy-BW6q9xds.js +467 -0
  22. package/dist/reflet-proxy-BW6q9xds.js.map +1 -0
  23. package/dist/{submit-Dl0rm6TV.js → submit-DJIKxRUp.js} +2 -2
  24. package/dist/submit-DJIKxRUp.js.map +1 -0
  25. package/dist/{team-layer-Dvjkh-_y.js → team-layer-_It1_oSo.js} +159 -95
  26. package/dist/team-layer-_It1_oSo.js.map +1 -0
  27. package/dist/team-tools-bridge-C51PpkUI.d.ts +31 -0
  28. package/dist/team-tools-bridge-C51PpkUI.d.ts.map +1 -0
  29. package/package.json +5 -1
  30. package/dist/devtools-layer-CsIcpepI.js.map +0 -1
  31. package/dist/handler-17FmwoSX.js.map +0 -1
  32. package/dist/sessions-panel-BYM0UUgT.js.map +0 -1
  33. package/dist/submit-Dl0rm6TV.js.map +0 -1
  34. package/dist/team-layer-Dvjkh-_y.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ All notable changes to `reflet-sdk` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.5] - 2026-10-17
9
+
10
+ ### Added
11
+
12
+ - Team tools in desktop apps: `createTeamToolsHost` from `reflet-sdk/desktop` runs them in an Electron main process, and `<RefletFeedback teamTools={bridge} />` takes the bridge it returns. Pages loaded from `file://` can now record sessions: sign-in opens the system browser and returns to a loopback listener, and the token stays in `~/.reflet/devtools/`, out of the page.
13
+
14
+ ## [0.7.4] - 2026-10-15
15
+
16
+ ### Changed
17
+
18
+ - Devtools: one panel button replaces the separate Board and Notes buttons. It reopens the panel on the tab you used last, even after a reload, and shows how many notes aren't on the board yet. The microphone opens Sessions directly. The bar steps aside while the panel is open, and the panel opens wider (480 px).
19
+ - Devtools: the panel's tabs carry icons, in the order Board, Notes, Sessions, Code. Your Reflet account (avatar, name, organization) sits in the panel's footer, with Connect and Disconnect; the Board tab no longer has a connection bar, and Sessions no longer says "Recording as…".
20
+ - Devtools: secondary choices are menus, not a second row of tabs: This page / Whole board on the Board, On screen / Details on a session's intentions. Every menu and surface follows one radius scale.
21
+ - Devtools: opening a session replaces the tabs with a back button to Sessions; the brief adds one more level back to the session. The links back inside the content, except "All evidence" from one screenshot, are gone. Moving between levels puts keyboard focus on the new view's heading, or on the view itself while it loads, and the session keeps its tab and On screen / Details choice when you come back from the brief.
22
+ - Sessions: on first use, "What a session records" shows above Start session instead of below it. Deleting an intention or a screenshot now says exactly which saved briefs go with it.
23
+ - Devtools: when connecting isn't possible, the panel says what it needs: a public key and a local address (localhost, *.localhost or *.test), or REFLET_SECRET_KEY. Error messages point to the panel's footer instead of the old Board tab.
24
+ - Sessions: "Hand off N confirmed" is now "Prepare brief". Nothing is shared until you copy the brief or download its `.zip`; "Copied or exported briefs can't be recalled" is gone. Earlier briefs list the intention they carried, when, and whether you copied or downloaded them. Dates read "Today, 14:02", "Yesterday, 14:02" or "12 Oct, 14:02".
25
+ - Notes: "Send to board" is now "Add to board". The empty Notes tab has a Pick an element button.
26
+
27
+ ### Added
28
+
29
+ - Sessions: add an intention to the board from its card, or every confirmed intention not on the board yet from the review's footer. Intentions on the board show "On the board".
30
+ - Devtools: the account footer shows your Reflet avatar. Requires the matching Reflet backend; older backends show initials.
31
+
32
+ ### Removed
33
+
34
+ - Devtools: the pick button in the panel header (the bar and the Notes tab have it), and the press-shrink on rows and cards.
35
+
8
36
  ## [0.7.3] - 2026-10-14
9
37
 
10
38
  ### Added
package/README.md CHANGED
@@ -167,7 +167,7 @@ builds do; production builds strip React's debug data).
167
167
  | `categories` | `("bug" \| "idea" \| "question")[]` | all three | Category chips to show. |
168
168
  | `labels` | `Partial<FeedbackWidgetLabels>` | — | Override any string for i18n. |
169
169
  | `devtools` | `boolean` | `true` | Dev-only team tools; see below. Never loaded outside `NODE_ENV=development`. |
170
- | `teamTools` | `boolean` | `true` | Lets signed-in members of your Reflet organization record sessions in production; see below. |
170
+ | `teamTools` | `boolean \| TeamToolsBridge` | `true` | Lets signed-in members of your Reflet organization record sessions in production; see below. Desktop apps pass a bridge. |
171
171
  | `appUrl` | `string` | `"https://www.reflet.app"` | Reflet web app origin team tools sign in on. |
172
172
  | `onSubmit` | `(result: { feedbackId: string }) => void` | — | Fires after a successful send. |
173
173
 
@@ -180,13 +180,16 @@ canvas content may come out blank.
180
180
  #### Devtools for your team (development only)
181
181
 
182
182
  In `NODE_ENV=development`, `RefletFeedback` also mounts a small devtools bar
183
- above its launcher; drag it by its grip anywhere, double-click the grip to put
184
- it back. Its panel opens full height: drag its header to move it (double-click
185
- to dock it back), drag its inner edge, bottom edge or corner to resize it
186
- (double-click a handle to reset). Position and size are remembered and refit
187
- whenever the window changes size. Production builds drop it at bundle time —
188
- the devtools chunk is never shipped to your users. Pass `devtools={false}` to
189
- turn it off.
183
+ above its launcher: **Pick**, **Sessions** (the microphone) and the panel
184
+ button, which reopens the panel on the tab you used last. Drag the bar by its
185
+ grip anywhere, double-click the grip to put it back. While the panel is open
186
+ the bar steps aside. The panel opens full height: drag its header to move it
187
+ (double-click to dock it back), drag its inner edge, bottom edge or corner to
188
+ resize it (double-click a handle to reset). Position and size are remembered
189
+ and refit whenever the window changes size. Your Reflet account and
190
+ organization sit in the panel's footer, with Connect and Disconnect. Production
191
+ builds drop it at bundle time — the devtools chunk is never shipped to your
192
+ users. Pass `devtools={false}` to turn it off.
190
193
 
191
194
  - **Pick** — click an element to write a note on it. The note card has a
192
195
  "View code" button, and Shift+click (or Shift+Enter) skips the note and opens
@@ -204,24 +207,27 @@ turn it off.
204
207
  to retry.
205
208
  - **Copy as prompt** — every note, with its source location, component stack,
206
209
  selector and markup, as one Markdown prompt for a coding agent.
207
- - **Send to board** — notes become internal feedback: members see them with an
210
+ - **Add to board** — notes become internal feedback: members see them with an
208
211
  "Internal" badge, the public board never does, and an admin can make one
209
212
  public from the dashboard.
210
- - **Board** — the board's feedback for the current route, or the whole board,
211
- with "Show on page", the reporter's source location, and a code search for
212
- the element's text.
213
+ - **Board** — the board's feedback for the current route, or the whole board
214
+ (the view menu next to the count), with "Show on page", the reporter's
215
+ source location, and a code search for the element's text.
213
216
  - **Sessions** — talk through the app while you use it: Reflet records your
214
217
  voice, what you point at and mark, and a screenshot at the start, at each
215
218
  mark and on each page change. Finish, and Reflet transcribes the session and
216
- proposes the changes you asked for. Keep, edit or dismiss each proposal, then
217
- hand the kept ones off as a brief to copy into a coding agent or export as a
218
- `.zip` with its screenshots. Sessions are recorded under your own name, so
219
- they need Connect rather than `REFLET_SECRET_KEY`.
219
+ proposes the intentions behind what you said. Confirm, edit or dismiss each
220
+ one. Like notes, confirmed intentions have two ways out: **Prepare brief**
221
+ builds a brief you copy into a coding agent or download as a `.zip` with its
222
+ screenshots (nothing is shared until you copy or download it), and **Add to
223
+ board** turns them into internal feedback, one by one or all at once.
224
+ Sessions are recorded under your own name, so they need Connect rather than
225
+ `REFLET_SECRET_KEY`.
220
226
 
221
227
  Code view and the board go through one dev-only route that reads files from
222
228
  your repository and holds the board credential server-side.
223
229
 
224
- To reach the board, click **Connect to Reflet** in the Board tab: sign in on
230
+ To reach the board, click **Connect to Reflet** in the panel's footer: sign in on
225
231
  reflet.app, approve the organization that owns the widget's key, and the dev
226
232
  server keeps a revocable token in `~/.reflet/devtools/`,
227
233
  outside the repository. That token acts as you, only reaches the devtools
@@ -293,6 +299,37 @@ transcript is saved, within 24 hours at most. Pausing stops all collection;
293
299
  keystrokes are never recorded, form fields appear empty in screenshots, and
294
300
  password fields and elements marked `data-reflet-redact` are hidden from them.
295
301
 
302
+ ##### Desktop apps (Electron)
303
+
304
+ A page loaded from `file://` has no origin Reflet can bind a sign-in to, so the
305
+ browser flow above can't work there. Run team tools from the app's main
306
+ process instead: sign-in opens the system browser and comes back to a one-shot
307
+ listener on `127.0.0.1`, like the devtools' Connect, and the token stays on disk
308
+ in `~/.reflet/devtools/`, never in the page. Members approve it on reflet.app as
309
+ a local connection; revoke it from Account → Devtools. The key needs no allowed
310
+ domain for this.
311
+
312
+ ```ts
313
+ // main process
314
+ import { app, ipcMain, shell } from 'electron';
315
+ import { createTeamToolsHost } from 'reflet-sdk/desktop';
316
+
317
+ const teamTools = createTeamToolsHost({
318
+ appDataDirectory: app.getPath('userData'),
319
+ openExternal: (url) => shell.openExternal(url),
320
+ publicKey: 'fb_pub_xxx',
321
+ });
322
+ ipcMain.handle('reflet-team-tools:account', () => teamTools.account());
323
+ ipcMain.handle('reflet-team-tools:request', (_event, call) => teamTools.request(call));
324
+ ipcMain.handle('reflet-team-tools:sign-in', () => teamTools.signIn());
325
+ ipcMain.handle('reflet-team-tools:sign-out', () => teamTools.signOut());
326
+ ```
327
+
328
+ Expose the four calls from the preload script, then pass them as the bridge:
329
+ `<RefletFeedback publicKey="fb_pub_xxx" teamTools={bridge} />`. Team tools show
330
+ at once, so only pass the bridge in builds meant for your team. `request` only
331
+ forwards the session and feedback endpoints the devtools use.
332
+
296
333
  ### 5. In-App Surveys
297
334
 
298
335
  Create surveys in the Reflet dashboard (NPS, ratings, choices, text, statements,
@@ -0,0 +1,24 @@
1
+ import { i as TeamToolsReply, n as TeamToolsBridge, r as TeamToolsCall, t as TeamToolsAccount } from "./team-tools-bridge-C51PpkUI.js";
2
+ //#region src/devtools/server/desktop.d.ts
3
+ export interface TeamToolsHostOptions {
4
+ /** Reflet API origin. Defaults to the hosted API. */
5
+ apiUrl?: string;
6
+ /** A directory only this app uses, such as Electron's `app.getPath("userData")`. Keeps its sign-in apart from other apps and dev servers. */
7
+ appDataDirectory: string;
8
+ /** Reflet web app origin sign-in happens on. Defaults to https://www.reflet.app. */
9
+ appUrl?: string;
10
+ /** Opens a URL in the system browser, such as Electron's `shell.openExternal`. */
11
+ openExternal: (url: string) => Promise<void>;
12
+ publicKey: string;
13
+ }
14
+ /**
15
+ * Runs team tools for a desktop app in its privileged process (Electron's
16
+ * main): sign-in goes through the system browser back to a one-shot loopback
17
+ * listener, and the token stays on disk outside the page, like the dev
18
+ * server's Connect. Expose the returned bridge to the page over IPC and pass it
19
+ * to `<RefletFeedback teamTools={bridge} />`.
20
+ */
21
+ export declare function createTeamToolsHost(options: TeamToolsHostOptions): TeamToolsBridge;
22
+ //#endregion
23
+ export type { TeamToolsAccount, TeamToolsBridge, TeamToolsCall, TeamToolsReply };
24
+ //# sourceMappingURL=desktop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop.d.ts","names":[],"sources":["../src/devtools/server/desktop.ts"],"mappings":";;iBA4BiB;;EAEf;;EAEA;;EAEA;;EAEA,eAAe,gBAAgB;EAC/B;;;;;;;;;wBAsGc,oBACd,SAAS,uBACR"}
@@ -0,0 +1,118 @@
1
+ import { a as connectCallbackUriFor, f as PENDING_CONNECT_TTL_MS, i as connectAuthorizeUrl, l as forgetConnection, m as readConnection, o as connectOutcomePage, p as htmlPage, r as resolveDevtoolsOptions, t as proxyWithBoardCredential, u as redeemConnectCode } from "./reflet-proxy-BW6q9xds.js";
2
+ import { createServer } from "node:http";
3
+ //#region src/devtools/server/desktop.ts
4
+ const LOOPBACK_HOST = "127.0.0.1";
5
+ const SIGN_IN_TIMED_OUT = "Reflet sign-in timed out. Sign in again.";
6
+ async function sendPage(response, page) {
7
+ response.writeHead(page.status, Object.fromEntries(page.headers));
8
+ response.end(await page.text());
9
+ }
10
+ async function listenOnLoopback(server) {
11
+ await new Promise((resolve, reject) => {
12
+ server.once("error", reject);
13
+ server.listen(0, LOOPBACK_HOST, resolve);
14
+ });
15
+ const address = server.address();
16
+ if (address === null || typeof address === "string") throw new Error("The Reflet sign-in listener has no port.");
17
+ return address.port;
18
+ }
19
+ /** Settles on the first callback carrying this sign-in's state; anything else gets a 404 and changes nothing. */
20
+ function awaitConnectCallback(server, pending) {
21
+ const callbackPath = new URL(pending.redirectUri).pathname;
22
+ return new Promise((resolve, reject) => {
23
+ const timeout = setTimeout(() => reject(/* @__PURE__ */ new Error(SIGN_IN_TIMED_OUT)), PENDING_CONNECT_TTL_MS);
24
+ let isRedeeming = false;
25
+ const redeem = async (url, response) => {
26
+ const outcome = await redeemConnectCode(pending.settings, {
27
+ code: url.searchParams.get("code"),
28
+ redirectUri: pending.redirectUri,
29
+ state: pending.state
30
+ });
31
+ await sendPage(response, connectOutcomePage(outcome));
32
+ if (!outcome.ok) throw new Error(outcome.message);
33
+ return { organizationName: outcome.organizationName };
34
+ };
35
+ server.on("request", (request, response) => {
36
+ const url = new URL(request.url ?? "/", pending.redirectUri);
37
+ const isThisSignIn = request.method === "GET" && url.pathname === callbackPath && url.searchParams.get("state") === pending.state;
38
+ if (isRedeeming || !isThisSignIn) {
39
+ sendPage(response, htmlPage("Not found", "Nothing here answers this link.", 404)).catch(() => void 0);
40
+ return;
41
+ }
42
+ isRedeeming = true;
43
+ clearTimeout(timeout);
44
+ redeem(url, response).then(resolve, reject);
45
+ });
46
+ });
47
+ }
48
+ async function startBrowserSignIn(settings, publicKey) {
49
+ const server = createServer();
50
+ const port = await listenOnLoopback(server);
51
+ const redirectUri = connectCallbackUriFor(`http://${LOOPBACK_HOST}:${port}`);
52
+ const { authorizeUrl, state } = connectAuthorizeUrl(settings, {
53
+ publicKey,
54
+ redirectUri
55
+ });
56
+ return {
57
+ account: awaitConnectCallback(server, {
58
+ redirectUri,
59
+ settings,
60
+ state
61
+ }).finally(() => server.close()),
62
+ authorizeUrl
63
+ };
64
+ }
65
+ /**
66
+ * Runs team tools for a desktop app in its privileged process (Electron's
67
+ * main): sign-in goes through the system browser back to a one-shot loopback
68
+ * listener, and the token stays on disk outside the page, like the dev
69
+ * server's Connect. Expose the returned bridge to the page over IPC and pass it
70
+ * to `<RefletFeedback teamTools={bridge} />`.
71
+ */
72
+ function createTeamToolsHost(options) {
73
+ const settings = resolveDevtoolsOptions({
74
+ apiUrl: options.apiUrl,
75
+ appUrl: options.appUrl,
76
+ root: options.appDataDirectory
77
+ }, {});
78
+ let pendingSignIn = null;
79
+ const currentSignIn = () => {
80
+ if (pendingSignIn) return pendingSignIn;
81
+ const started = startBrowserSignIn(settings, options.publicKey);
82
+ pendingSignIn = started;
83
+ const release = () => {
84
+ if (pendingSignIn === started) pendingSignIn = null;
85
+ };
86
+ started.then((signIn) => signIn.account).then(release, release);
87
+ return started;
88
+ };
89
+ return {
90
+ account: async () => {
91
+ const connection = await readConnection(settings.root, settings.apiUrl);
92
+ return connection && { organizationName: connection.organizationName };
93
+ },
94
+ request: async (call) => {
95
+ const search = call.query ? `?${new URLSearchParams(call.query)}` : "";
96
+ const response = await proxyWithBoardCredential(new Request(`${settings.apiUrl}/${search}`, {
97
+ body: call.method === "POST" ? call.body : void 0,
98
+ method: call.method
99
+ }), call.path, settings);
100
+ return {
101
+ body: await response.text(),
102
+ status: response.status
103
+ };
104
+ },
105
+ signIn: async () => {
106
+ const signIn = await currentSignIn();
107
+ await options.openExternal(signIn.authorizeUrl);
108
+ return await signIn.account;
109
+ },
110
+ signOut: async () => {
111
+ await forgetConnection(settings);
112
+ }
113
+ };
114
+ }
115
+ //#endregion
116
+ export { createTeamToolsHost };
117
+
118
+ //# sourceMappingURL=desktop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop.js","names":[],"sources":["../src/devtools/server/desktop.ts"],"sourcesContent":["import { createServer, type Server, type ServerResponse } from \"node:http\";\nimport type {\n TeamToolsAccount,\n TeamToolsBridge,\n} from \"../../sessions/team/team-tools-bridge\";\nimport {\n connectAuthorizeUrl,\n connectCallbackUriFor,\n connectOutcomePage,\n forgetConnection,\n redeemConnectCode,\n} from \"./connect/connect-routes\";\nimport { readConnection } from \"./connect/connection-store\";\nimport { htmlPage } from \"./connect/html-page\";\nimport { PENDING_CONNECT_TTL_MS } from \"./connect/pending-connects\";\nimport {\n type ResolvedDevtoolsOptions,\n resolveDevtoolsOptions,\n} from \"./options\";\nimport { proxyWithBoardCredential } from \"./reflet-proxy\";\n\nexport type {\n TeamToolsAccount,\n TeamToolsBridge,\n TeamToolsCall,\n TeamToolsReply,\n} from \"../../sessions/team/team-tools-bridge\";\n\nexport interface TeamToolsHostOptions {\n /** Reflet API origin. Defaults to the hosted API. */\n apiUrl?: string;\n /** A directory only this app uses, such as Electron's `app.getPath(\"userData\")`. Keeps its sign-in apart from other apps and dev servers. */\n appDataDirectory: string;\n /** Reflet web app origin sign-in happens on. Defaults to https://www.reflet.app. */\n appUrl?: string;\n /** Opens a URL in the system browser, such as Electron's `shell.openExternal`. */\n openExternal: (url: string) => Promise<void>;\n publicKey: string;\n}\n\ninterface BrowserSignIn {\n account: Promise<TeamToolsAccount>;\n authorizeUrl: string;\n}\n\nconst LOOPBACK_HOST = \"127.0.0.1\";\nconst SIGN_IN_TIMED_OUT = \"Reflet sign-in timed out. Sign in again.\";\n\nasync function sendPage(response: ServerResponse, page: Response) {\n response.writeHead(page.status, Object.fromEntries(page.headers));\n response.end(await page.text());\n}\n\nasync function listenOnLoopback(server: Server): Promise<number> {\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, LOOPBACK_HOST, resolve);\n });\n const address = server.address();\n if (address === null || typeof address === \"string\") {\n throw new Error(\"The Reflet sign-in listener has no port.\");\n }\n return address.port;\n}\n\n/** Settles on the first callback carrying this sign-in's state; anything else gets a 404 and changes nothing. */\nfunction awaitConnectCallback(\n server: Server,\n pending: {\n redirectUri: string;\n settings: ResolvedDevtoolsOptions;\n state: string;\n }\n): Promise<TeamToolsAccount> {\n const callbackPath = new URL(pending.redirectUri).pathname;\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(\n () => reject(new Error(SIGN_IN_TIMED_OUT)),\n PENDING_CONNECT_TTL_MS\n );\n let isRedeeming = false;\n const redeem = async (url: URL, response: ServerResponse) => {\n const outcome = await redeemConnectCode(pending.settings, {\n code: url.searchParams.get(\"code\"),\n redirectUri: pending.redirectUri,\n state: pending.state,\n });\n await sendPage(response, connectOutcomePage(outcome));\n if (!outcome.ok) {\n throw new Error(outcome.message);\n }\n return { organizationName: outcome.organizationName };\n };\n server.on(\"request\", (request, response) => {\n const url = new URL(request.url ?? \"/\", pending.redirectUri);\n const isThisSignIn =\n request.method === \"GET\" &&\n url.pathname === callbackPath &&\n url.searchParams.get(\"state\") === pending.state;\n if (isRedeeming || !isThisSignIn) {\n sendPage(\n response,\n htmlPage(\"Not found\", \"Nothing here answers this link.\", 404)\n ).catch(() => undefined);\n return;\n }\n isRedeeming = true;\n clearTimeout(timeout);\n redeem(url, response).then(resolve, reject);\n });\n });\n}\n\nasync function startBrowserSignIn(\n settings: ResolvedDevtoolsOptions,\n publicKey: string\n): Promise<BrowserSignIn> {\n const server = createServer();\n const port = await listenOnLoopback(server);\n const redirectUri = connectCallbackUriFor(`http://${LOOPBACK_HOST}:${port}`);\n const { authorizeUrl, state } = connectAuthorizeUrl(settings, {\n publicKey,\n redirectUri,\n });\n const account = awaitConnectCallback(server, {\n redirectUri,\n settings,\n state,\n }).finally(() => server.close());\n return { account, authorizeUrl };\n}\n\n/**\n * Runs team tools for a desktop app in its privileged process (Electron's\n * main): sign-in goes through the system browser back to a one-shot loopback\n * listener, and the token stays on disk outside the page, like the dev\n * server's Connect. Expose the returned bridge to the page over IPC and pass it\n * to `<RefletFeedback teamTools={bridge} />`.\n */\nexport function createTeamToolsHost(\n options: TeamToolsHostOptions\n): TeamToolsBridge {\n const settings = resolveDevtoolsOptions(\n {\n apiUrl: options.apiUrl,\n appUrl: options.appUrl,\n root: options.appDataDirectory,\n },\n {}\n );\n let pendingSignIn: Promise<BrowserSignIn> | null = null;\n\n const currentSignIn = (): Promise<BrowserSignIn> => {\n if (pendingSignIn) {\n return pendingSignIn;\n }\n const started = startBrowserSignIn(settings, options.publicKey);\n pendingSignIn = started;\n const release = () => {\n if (pendingSignIn === started) {\n pendingSignIn = null;\n }\n };\n started.then((signIn) => signIn.account).then(release, release);\n return started;\n };\n\n return {\n account: async () => {\n const connection = await readConnection(settings.root, settings.apiUrl);\n return connection && { organizationName: connection.organizationName };\n },\n request: async (call) => {\n const search = call.query ? `?${new URLSearchParams(call.query)}` : \"\";\n const response = await proxyWithBoardCredential(\n new Request(`${settings.apiUrl}/${search}`, {\n body: call.method === \"POST\" ? call.body : undefined,\n method: call.method,\n }),\n call.path,\n settings\n );\n return { body: await response.text(), status: response.status };\n },\n signIn: async () => {\n const signIn = await currentSignIn();\n await options.openExternal(signIn.authorizeUrl);\n return await signIn.account;\n },\n signOut: async () => {\n await forgetConnection(settings);\n },\n };\n}\n"],"mappings":"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"}