fauna-api 1.0.765 → 1.0.771
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/dist/src/authentification.d.ts +2 -0
- package/dist/src/authentification.js +5 -2
- package/dist/src/calendar.d.ts +6 -0
- package/dist/src/calendar.js +1 -0
- package/dist/src/cicada/inboxes.d.ts +5 -1
- package/dist/src/cicada/inboxes.js +1 -0
- package/dist/src/groups.d.ts +33 -2
- package/dist/src/groups.js +26 -2
- package/dist/src/marmot/web.d.ts +59 -25
- package/dist/src/marmot/web.js +333 -203
- package/dist/src/parrot/room.d.ts +3 -2
- package/dist/src/parrot/room.js +14 -3
- package/dist/src/payments.d.ts +19 -4
- package/dist/src/payments.js +8 -3
- package/dist/src/termite/os.d.ts +14 -6
- package/dist/src/termite/os.js +9 -4
- package/dist/src/ui/browser.js +35 -10
- package/dist/src/ui/chat.js +2 -2
- package/dist/src/ui/chatPanel.d.ts +3 -3
- package/dist/src/ui/chatPanel.js +23 -9
- package/dist/src/view.d.ts +8 -4
- package/dist/src/view.js +5 -5
- package/dist/src/webhooks.d.ts +3 -2
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { Room, Presence, Authorization } from "../shared/server.js";
|
|
|
4
4
|
import { ChatTarget, AppendResult, Attachment } from "./share.js";
|
|
5
5
|
import type { Repository } from "../termite/versioning.js";
|
|
6
6
|
import type { Group } from "../groups.js";
|
|
7
|
+
import type { Target } from "../view.js";
|
|
7
8
|
export { Attachment } from "./share.js";
|
|
8
9
|
/**
|
|
9
10
|
* One durable message in a room transcript. Parts keep their file refs as
|
|
@@ -80,8 +81,8 @@ export type RoomHooks = Partial<{
|
|
|
80
81
|
onRenamed(room: ChatRoom, name: string): void;
|
|
81
82
|
/** The chat's repository was attached, swapped or detached. */
|
|
82
83
|
onRepository(room: ChatRoom): void;
|
|
83
|
-
/** The reply pointed everyone to a file (Parrot's `open`): show it. */
|
|
84
|
-
onOpenFile(room: ChatRoom,
|
|
84
|
+
/** The reply pointed everyone to a file (Parrot's `open`): show it. A chat attachment, or a file in the chat's repository, live. */
|
|
85
|
+
onOpenFile(room: ChatRoom, file: Target): void;
|
|
85
86
|
/** The chat was deleted/closed by its owner (or the host disconnected). */
|
|
86
87
|
onClosed(room: ChatRoom): void;
|
|
87
88
|
/** The roster changed (someone joined/left or updated presence/status). */
|
package/dist/src/parrot/room.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Role } from "./generative.js";
|
|
|
2
2
|
import { Room, Presence, connect } from "../shared/server.js";
|
|
3
3
|
import { Parrot } from "./index.js";
|
|
4
4
|
import { appendRoomMessage, steerRoom, unqueueRoomMessage, pageMessages, setRoomRepository, listRoomAttachments, Attachment, } from "./share.js";
|
|
5
|
+
import { File as FaunaFile } from "../drive.js";
|
|
5
6
|
export { Attachment } from "./share.js";
|
|
6
7
|
/**
|
|
7
8
|
* A live, domain-agnostic chat room. `ChatRoom extends Room` and owns everything
|
|
@@ -412,9 +413,19 @@ export class ChatRoom extends Room {
|
|
|
412
413
|
return true;
|
|
413
414
|
}
|
|
414
415
|
case "chat-open-file": {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
const attachment = packet.data?.attachment;
|
|
417
|
+
const repository = packet.data?.repository;
|
|
418
|
+
if (attachment) {
|
|
419
|
+
this.hooks.onOpenFile?.(this, Attachment.fromJSON(attachment, this.getAuthorization()));
|
|
420
|
+
}
|
|
421
|
+
else if (repository) {
|
|
422
|
+
this.hooks.onOpenFile?.(this, FaunaFile.fromJSON(repository, {
|
|
423
|
+
authorization: this.getAuthorization(),
|
|
424
|
+
repository: Number(repository.id),
|
|
425
|
+
path: String(repository.path),
|
|
426
|
+
...(repository.group ? { group: String(repository.group) } : {}),
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
418
429
|
return true;
|
|
419
430
|
}
|
|
420
431
|
case "chat-tool-call": {
|
package/dist/src/payments.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ export type Payment = {
|
|
|
5
5
|
id: string;
|
|
6
6
|
status: PaymentStatus;
|
|
7
7
|
amount: number;
|
|
8
|
-
/**
|
|
8
|
+
/** The whole fee, taken from what the payee receives: the card cost, Fauna's cut and the app's fee. */
|
|
9
9
|
fee: number;
|
|
10
|
+
/** The app's part of `fee`, paid to the account that owns the API key that asked for the payment. */
|
|
11
|
+
appFee: number;
|
|
10
12
|
refunded: number;
|
|
11
13
|
currency: string;
|
|
12
14
|
/** The payee's email. */
|
|
@@ -28,6 +30,12 @@ export type PaymentRequest = {
|
|
|
28
30
|
currency?: string;
|
|
29
31
|
description?: string;
|
|
30
32
|
reference?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The app's own fee, a percent of the amount (at most 30), paid to the account that owns
|
|
35
|
+
* the API key once the payment succeeds. Only an API key can ask for one, and its account
|
|
36
|
+
* needs payouts set up.
|
|
37
|
+
*/
|
|
38
|
+
appFee?: number;
|
|
31
39
|
/** Where Checkout sends the payer back, with ?payment=paid or ?payment=cancelled. */
|
|
32
40
|
returnUrl?: string;
|
|
33
41
|
cancelUrl?: string;
|
|
@@ -39,10 +47,11 @@ export type Payouts = {
|
|
|
39
47
|
payoutsEnabled: boolean;
|
|
40
48
|
detailsSubmitted: boolean;
|
|
41
49
|
};
|
|
42
|
-
/** What a payment of `amount` costs:
|
|
50
|
+
/** What a payment of `amount` costs: the whole fee, the app's part of it and what the payee receives. */
|
|
43
51
|
export type Quote = {
|
|
44
52
|
amount: number;
|
|
45
53
|
fee: number;
|
|
54
|
+
appFee: number;
|
|
46
55
|
payout: number;
|
|
47
56
|
};
|
|
48
57
|
/** Start or continue setting up payouts. Open the returned URL; Stripe sends you back with ?payouts=connected. */
|
|
@@ -56,7 +65,10 @@ export declare function payouts(credentials: Credentials): Promise<Payouts>;
|
|
|
56
65
|
export declare function dashboard(credentials: Credentials): Promise<{
|
|
57
66
|
url: string;
|
|
58
67
|
}>;
|
|
59
|
-
|
|
68
|
+
/** What a payment of `amount` would cost, with the app's fee (`appFee`, a percent) when an API key asks for one. */
|
|
69
|
+
export declare function quote(authorization: Credentials | ApiKey, amount: number, options?: {
|
|
70
|
+
appFee?: number;
|
|
71
|
+
}): Promise<Quote>;
|
|
60
72
|
/**
|
|
61
73
|
* Ask for a payment to `payee`. Send the payer to the returned `url`. With an API
|
|
62
74
|
* key, that key's webhooks hear `payment.succeeded`, `payment.expired`,
|
|
@@ -69,5 +81,8 @@ export declare function get(authorization: Credentials | ApiKey, id: string): Pr
|
|
|
69
81
|
export declare function list(authorization: Credentials | ApiKey, query?: {
|
|
70
82
|
reference?: string;
|
|
71
83
|
}): Promise<Payment[]>;
|
|
72
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Refund a completed payment in full. The payee gets back the whole fee except Stripe's own
|
|
86
|
+
* fee on the charge, the app's fee included. Whoever made it, or its payee, can.
|
|
87
|
+
*/
|
|
73
88
|
export declare function refund(authorization: Credentials | ApiKey, id: string): Promise<Payment>;
|
package/dist/src/payments.js
CHANGED
|
@@ -39,8 +39,10 @@ export async function dashboard(credentials) {
|
|
|
39
39
|
throw new Error(await response.text());
|
|
40
40
|
return await response.json();
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
/** What a payment of `amount` would cost, with the app's fee (`appFee`, a percent) when an API key asks for one. */
|
|
43
|
+
export async function quote(authorization, amount, options = {}) {
|
|
44
|
+
const appFee = options.appFee !== undefined ? `&appFee=${encodeURIComponent(String(options.appFee))}` : '';
|
|
45
|
+
const response = await fetch(`${Fauna.getURL()}/payments/quote?amount=${encodeURIComponent(String(amount))}${appFee}`, {
|
|
44
46
|
method: "GET",
|
|
45
47
|
headers: {
|
|
46
48
|
"Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
|
|
@@ -93,7 +95,10 @@ export async function list(authorization, query = {}) {
|
|
|
93
95
|
throw new Error(await response.text());
|
|
94
96
|
return (await response.json()).map(hydrate);
|
|
95
97
|
}
|
|
96
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Refund a completed payment in full. The payee gets back the whole fee except Stripe's own
|
|
100
|
+
* fee on the charge, the app's fee included. Whoever made it, or its payee, can.
|
|
101
|
+
*/
|
|
97
102
|
export async function refund(authorization, id) {
|
|
98
103
|
const response = await fetch(`${Fauna.getURL()}/payments/${encodeURIComponent(id)}/refund`, {
|
|
99
104
|
method: "POST",
|
package/dist/src/termite/os.d.ts
CHANGED
|
@@ -103,12 +103,18 @@ export type ProcessTool = {
|
|
|
103
103
|
pid: number;
|
|
104
104
|
signal?: 'TERM' | 'KILL';
|
|
105
105
|
};
|
|
106
|
+
/** How the machine reaches the internet: `private` only from a residential address, billed per
|
|
107
|
+
* GB to whoever pays for the machine, or `public` from its own. */
|
|
108
|
+
export type InternetTool = {
|
|
109
|
+
kind: 'internet';
|
|
110
|
+
mode: 'private' | 'public';
|
|
111
|
+
};
|
|
106
112
|
/** Pointer and keyboard input on the desktop. No executeJavascript, which would run on the
|
|
107
113
|
* desktop page itself, and no search or exit, which belong to a web page's agent. */
|
|
108
114
|
export type DesktopTool = Exclude<BrowserTool, {
|
|
109
115
|
kind: 'executeJavascript' | 'search' | 'exit';
|
|
110
116
|
}>;
|
|
111
|
-
export type MachineTool = DesktopTool | WindowTool | FileTool | ProcessTool;
|
|
117
|
+
export type MachineTool = DesktopTool | WindowTool | FileTool | ProcessTool | InternetTool;
|
|
112
118
|
/** Metadata for one desktop window. Its pixels live in the desktop HTML. */
|
|
113
119
|
export type Window = {
|
|
114
120
|
/** Same id as the element's data-window and the window tools. */
|
|
@@ -153,7 +159,7 @@ export type MachineRoomHooks = BrowserRoomHooks<DesktopTool> & Partial<{
|
|
|
153
159
|
/** A Files window's folder or view changed. */
|
|
154
160
|
onFiles(room: MachineRoom, window: number, files: FilesState): void;
|
|
155
161
|
onStatus(room: MachineRoom): void;
|
|
156
|
-
/** Private turned on or off,
|
|
162
|
+
/** Private turned on or off, paused while it reconnects (`privateReason` says why), or came back. */
|
|
157
163
|
onPrivate(room: MachineRoom): void;
|
|
158
164
|
/** An agent holding the machine took a step. */
|
|
159
165
|
onAgentStep(room: MachineRoom, step: AgentStep): void;
|
|
@@ -180,7 +186,7 @@ export type AgentStep = {
|
|
|
180
186
|
};
|
|
181
187
|
};
|
|
182
188
|
/**
|
|
183
|
-
* Every machine tool for a model: desktop input, windows, files, and exec.
|
|
189
|
+
* Every machine tool for a model: desktop input, windows, files, processes, the internet switch, and exec.
|
|
184
190
|
* `invoke` runs a tool; `exec` runs a shell command and returns its result as text.
|
|
185
191
|
*/
|
|
186
192
|
export declare function parrotTools(invoke: (tool: MachineTool) => Promise<string>, exec: (command: string, timeoutMs?: number) => Promise<string>): Tool[];
|
|
@@ -239,9 +245,11 @@ export declare class MachineRoom extends PageRoom<DesktopTool> {
|
|
|
239
245
|
error: string;
|
|
240
246
|
/** The repository's name, so a guest who joined by code knows what it is. */
|
|
241
247
|
name: string;
|
|
242
|
-
/** Its connections go out from a residential address, billed per GB to whoever pays for
|
|
248
|
+
/** Its connections go out only from a residential address, billed per GB to whoever pays for
|
|
249
|
+
* the machine. Stays true while private is paused. */
|
|
243
250
|
private: boolean;
|
|
244
|
-
/** Why private is
|
|
251
|
+
/** Why private is paused while it reconnects, like a payer out of balance. Its internet is
|
|
252
|
+
* cut until then. Empty while private carries traffic, or is off. */
|
|
245
253
|
privateReason: string;
|
|
246
254
|
private machineHooks;
|
|
247
255
|
private calls;
|
|
@@ -292,7 +300,7 @@ export declare class MachineRoom extends PageRoom<DesktopTool> {
|
|
|
292
300
|
group?: Group;
|
|
293
301
|
}): Promise<void>;
|
|
294
302
|
/** Turn private on or off for a repository's live machine, for its owner or group members.
|
|
295
|
-
* Resolves to the machine's private state and,
|
|
303
|
+
* Resolves to the machine's private state and, while it's paused, why. */
|
|
296
304
|
static setPrivate(authorization: Credentials | ApiKey, target: {
|
|
297
305
|
repository: number;
|
|
298
306
|
}, on: boolean, opts?: {
|
package/dist/src/termite/os.js
CHANGED
|
@@ -46,7 +46,7 @@ const CALL_MS = 60000;
|
|
|
46
46
|
const EXEC_MS = 120000;
|
|
47
47
|
const STOP_MS = 10 * 60000;
|
|
48
48
|
/**
|
|
49
|
-
* Every machine tool for a model: desktop input, windows, files, and exec.
|
|
49
|
+
* Every machine tool for a model: desktop input, windows, files, processes, the internet switch, and exec.
|
|
50
50
|
* `invoke` runs a tool; `exec` runs a shell command and returns its result as text.
|
|
51
51
|
*/
|
|
52
52
|
export function parrotTools(invoke, exec) {
|
|
@@ -156,6 +156,9 @@ export function parrotTools(invoke, exec) {
|
|
|
156
156
|
pid: { type: "NUMBER", description: "Process id from processes" },
|
|
157
157
|
signal: { type: "STRING", enum: ["TERM", "KILL"], description: "TERM (default) or KILL" },
|
|
158
158
|
}, ["pid"], (p) => ({ kind: 'kill', pid: Number(p.pid), ...(p.signal === 'KILL' || p.signal === 'TERM' ? { signal: p.signal } : {}) }));
|
|
159
|
+
add("internet", "Choose how the machine reaches the internet. private sends every connection out from a residential address, billed per GB; public uses the machine's own address. While private reconnects the internet is paused and connections are refused; switch to public if the work can't wait.", {
|
|
160
|
+
mode: { type: "STRING", enum: ["private", "public"], description: "private or public" },
|
|
161
|
+
}, ["mode"], (p) => ({ kind: 'internet', mode: p.mode === 'public' ? 'public' : 'private' }));
|
|
159
162
|
tools.push({
|
|
160
163
|
function: {
|
|
161
164
|
name: "exec",
|
|
@@ -295,14 +298,16 @@ export class MachineRoom extends PageRoom {
|
|
|
295
298
|
writable: true,
|
|
296
299
|
value: ''
|
|
297
300
|
});
|
|
298
|
-
/** Its connections go out from a residential address, billed per GB to whoever pays for
|
|
301
|
+
/** Its connections go out only from a residential address, billed per GB to whoever pays for
|
|
302
|
+
* the machine. Stays true while private is paused. */
|
|
299
303
|
Object.defineProperty(this, "private", {
|
|
300
304
|
enumerable: true,
|
|
301
305
|
configurable: true,
|
|
302
306
|
writable: true,
|
|
303
307
|
value: false
|
|
304
308
|
});
|
|
305
|
-
/** Why private is
|
|
309
|
+
/** Why private is paused while it reconnects, like a payer out of balance. Its internet is
|
|
310
|
+
* cut until then. Empty while private carries traffic, or is off. */
|
|
306
311
|
Object.defineProperty(this, "privateReason", {
|
|
307
312
|
enumerable: true,
|
|
308
313
|
configurable: true,
|
|
@@ -512,7 +517,7 @@ export class MachineRoom extends PageRoom {
|
|
|
512
517
|
throw new Error(await response.text());
|
|
513
518
|
}
|
|
514
519
|
/** Turn private on or off for a repository's live machine, for its owner or group members.
|
|
515
|
-
* Resolves to the machine's private state and,
|
|
520
|
+
* Resolves to the machine's private state and, while it's paused, why. */
|
|
516
521
|
static async setPrivate(authorization, target, on, opts) {
|
|
517
522
|
const response = await fetch(`${Termite.getURL()}/termite/repositories/${target.repository}/machine/private`, {
|
|
518
523
|
method: "POST",
|
package/dist/src/ui/browser.js
CHANGED
|
@@ -245,11 +245,27 @@ const MIRRORED = [
|
|
|
245
245
|
];
|
|
246
246
|
const SELECTION = "rgba(51, 144, 255, 0.38)";
|
|
247
247
|
const BLINK_MS = 1060;
|
|
248
|
+
/** The field showing the host's caret, in the replay's document or a frame inside it, with
|
|
249
|
+
* where that frame's viewport sits in the replay and the outermost frame it is in. */
|
|
250
|
+
function caretField(doc) {
|
|
251
|
+
const field = doc.querySelector("[data-fauna-caret]");
|
|
252
|
+
if (field)
|
|
253
|
+
return { field, left: 0, top: 0, frame: null };
|
|
254
|
+
for (const frame of Array.from(doc.querySelectorAll("iframe"))) {
|
|
255
|
+
const inner = frame.contentDocument ? caretField(frame.contentDocument) : null;
|
|
256
|
+
if (!inner)
|
|
257
|
+
continue;
|
|
258
|
+
const box = frame.getBoundingClientRect();
|
|
259
|
+
return { field: inner.field, left: inner.left + box.left + frame.clientLeft, top: inner.top + box.top + frame.clientTop, frame };
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
248
263
|
/**
|
|
249
264
|
* The host's caret, or its selection, drawn over the focused text field of a replay. The
|
|
250
265
|
* copy is never focused, so it has neither; the recorder streams them as
|
|
251
266
|
* `data-fauna-caret="start,end,scrollLeft,scrollTop"`. The marks sit in a layer at the end
|
|
252
|
-
* of the replay's document, outside every subtree the page mutates.
|
|
267
|
+
* of the replay's document, outside every subtree the page mutates. A field inside a frame
|
|
268
|
+
* is measured in the frame's own document, with its fonts, and its marks moved into place.
|
|
253
269
|
*/
|
|
254
270
|
function caretPainter(getDoc) {
|
|
255
271
|
let layer = null;
|
|
@@ -271,18 +287,20 @@ function caretPainter(getDoc) {
|
|
|
271
287
|
doc.documentElement.appendChild(layer);
|
|
272
288
|
painted = "";
|
|
273
289
|
}
|
|
274
|
-
const
|
|
290
|
+
const found = caretField(doc);
|
|
291
|
+
const field = found?.field;
|
|
292
|
+
const local = field?.ownerDocument;
|
|
275
293
|
const caret = field?.getAttribute("data-fauna-caret") ?? "";
|
|
276
294
|
const rect = field?.getBoundingClientRect();
|
|
277
|
-
if (!field || !caret || !rect?.width || !rect.height)
|
|
295
|
+
if (!found || !field || !local?.defaultView || !caret || !rect?.width || !rect.height)
|
|
278
296
|
return clear();
|
|
279
297
|
const [start, end, scrollLeft, scrollTop] = caret.split(",").map((n) => Number(n) || 0);
|
|
280
|
-
const style =
|
|
298
|
+
const style = local.defaultView.getComputedStyle(field);
|
|
281
299
|
const textarea = field.tagName === "TEXTAREA";
|
|
282
300
|
const value = field.value;
|
|
283
301
|
const from = Math.max(0, Math.min(start, value.length));
|
|
284
302
|
const to = Math.max(from, Math.min(end, value.length));
|
|
285
|
-
const mirror =
|
|
303
|
+
const mirror = local.createElement("div");
|
|
286
304
|
mirror.style.cssText = "all:initial;position:fixed;pointer-events:none;visibility:hidden;overflow:hidden";
|
|
287
305
|
for (const name of MIRRORED)
|
|
288
306
|
mirror.style.setProperty(name, style.getPropertyValue(name));
|
|
@@ -303,10 +321,13 @@ function caretPainter(getDoc) {
|
|
|
303
321
|
// An input centers its one line in the box.
|
|
304
322
|
if (!textarea)
|
|
305
323
|
mirror.style.lineHeight = `${inner.bottom - inner.top - px("padding-top") - px("padding-bottom")}px`;
|
|
306
|
-
const mark =
|
|
324
|
+
const mark = local.createElement("span");
|
|
307
325
|
mark.textContent = from === to ? "\u200b" : value.slice(from, to);
|
|
308
326
|
mirror.append(value.slice(0, from), mark, value.slice(to) + (textarea ? "\u200b" : ""));
|
|
309
|
-
|
|
327
|
+
if (local === doc)
|
|
328
|
+
layer.appendChild(mirror);
|
|
329
|
+
else
|
|
330
|
+
local.documentElement.appendChild(mirror);
|
|
310
331
|
mirror.scrollLeft = scrollLeft;
|
|
311
332
|
mirror.scrollTop = scrollTop;
|
|
312
333
|
const boxes = Array.from(mark.getClientRects());
|
|
@@ -322,11 +343,15 @@ function caretPainter(getDoc) {
|
|
|
322
343
|
const bottom = Math.min(box.top + (box.height + height) / 2, inner.bottom);
|
|
323
344
|
if (right <= left || bottom <= top || left < inner.left - 1 || left > inner.right)
|
|
324
345
|
continue;
|
|
325
|
-
// A window over the field covers its caret too.
|
|
326
|
-
const
|
|
346
|
+
// A window over the field covers its caret too, in its frame or over the frame.
|
|
347
|
+
const x = Math.min(left + 1, right);
|
|
348
|
+
const y = (top + bottom) / 2;
|
|
349
|
+
const under = local.elementFromPoint(x, y);
|
|
327
350
|
if (under && under !== field && !field.contains(under))
|
|
328
351
|
continue;
|
|
329
|
-
|
|
352
|
+
if (found.frame && doc.elementFromPoint(x + found.left, y + found.top) !== found.frame)
|
|
353
|
+
continue;
|
|
354
|
+
shapes.push({ left: left + found.left, top: top + found.top, width: right - left, height: bottom - top });
|
|
330
355
|
}
|
|
331
356
|
// Unchanged marks stay, so the caret keeps blinking through unrelated changes.
|
|
332
357
|
const key = `${color}|${shapes.map((s) => `${s.left},${s.top},${s.width},${s.height}`).join(";")}`;
|
package/dist/src/ui/chat.js
CHANGED
|
@@ -1727,10 +1727,10 @@ class ChatEngine {
|
|
|
1727
1727
|
this.roomStream.renderer.feedText(delta);
|
|
1728
1728
|
autoScroll(this.refs.messagesEl);
|
|
1729
1729
|
},
|
|
1730
|
-
onOpenFile: (_r,
|
|
1730
|
+
onOpenFile: (_r, file) => {
|
|
1731
1731
|
if (liveEnded || this.destroyed)
|
|
1732
1732
|
return;
|
|
1733
|
-
this.refs.panel?.point(
|
|
1733
|
+
this.refs.panel?.point(file);
|
|
1734
1734
|
},
|
|
1735
1735
|
onThinking: (_r, thinking) => {
|
|
1736
1736
|
if (liveEnded)
|
|
@@ -6,13 +6,13 @@ import type { Attachment } from "../parrot/share.js";
|
|
|
6
6
|
export type PanelBus = {
|
|
7
7
|
/** A person opened a file (a card, or a row): a tab for them only. */
|
|
8
8
|
open(attachment: Attachment): void;
|
|
9
|
-
/** The reply pointed everyone to a file: open the panel and focus its tab. */
|
|
10
|
-
point(
|
|
9
|
+
/** The reply pointed everyone to a file, an attachment or one in the chat's repository: open the panel and focus its tab. */
|
|
10
|
+
point(file: Fauna.View.Target): void;
|
|
11
11
|
/** New files landed in the chat. */
|
|
12
12
|
added(): void;
|
|
13
13
|
listen(handlers: Partial<{
|
|
14
14
|
open(a: Attachment): void;
|
|
15
|
-
point(
|
|
15
|
+
point(f: Fauna.View.Target): void;
|
|
16
16
|
added(): void;
|
|
17
17
|
}>): () => void;
|
|
18
18
|
};
|
package/dist/src/ui/chatPanel.js
CHANGED
|
@@ -23,9 +23,15 @@ export function createPanelBus() {
|
|
|
23
23
|
export function attachmentOf(room, part, message) {
|
|
24
24
|
return Fauna.Parrot.Share.Attachment.fromJSON({ ...part, chat: room.chatId ?? "", ...(message !== undefined ? { message } : {}) }, room.getAuthorization());
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
function fileOf(target) {
|
|
27
|
+
return 'ref' in target && 'chat' in target ? target.file : target;
|
|
28
|
+
}
|
|
29
|
+
function keyOf(target) {
|
|
30
|
+
const file = fileOf(target);
|
|
31
|
+
const auth = file.authorization;
|
|
32
|
+
if (auth.repository)
|
|
33
|
+
return `repository:${auth.repository}:${auth.path}`;
|
|
34
|
+
return ('ref' in target && target.ref) || auth.accessCode || file.name;
|
|
29
35
|
}
|
|
30
36
|
function iconOf(contentType, name) {
|
|
31
37
|
if (contentType.startsWith("image/"))
|
|
@@ -253,9 +259,9 @@ export function ChatPanel({ room, bus, open, onOpenChange }) {
|
|
|
253
259
|
load();
|
|
254
260
|
}
|
|
255
261
|
}, [open, load]);
|
|
256
|
-
const openTab = useCallback((
|
|
257
|
-
const key = keyOf(
|
|
258
|
-
setTabs((current) => current.some((t) => t.key === key) ? current : [...current, { key,
|
|
262
|
+
const openTab = useCallback((target, everyone) => {
|
|
263
|
+
const key = keyOf(target);
|
|
264
|
+
setTabs((current) => current.some((t) => t.key === key) ? current : [...current, { key, target, people: [] }]);
|
|
259
265
|
setActive(key);
|
|
260
266
|
slideBack();
|
|
261
267
|
if (everyone || !open)
|
|
@@ -263,7 +269,7 @@ export function ChatPanel({ room, bus, open, onOpenChange }) {
|
|
|
263
269
|
}, [open, onOpenChange, slideBack]);
|
|
264
270
|
useEffect(() => bus.listen({
|
|
265
271
|
open: (a) => openTab(a, false),
|
|
266
|
-
point: (
|
|
272
|
+
point: (f) => openTab(f, true),
|
|
267
273
|
added: () => {
|
|
268
274
|
if (!loaded.current)
|
|
269
275
|
return;
|
|
@@ -342,7 +348,10 @@ export function ChatPanel({ room, bus, open, onOpenChange }) {
|
|
|
342
348
|
};
|
|
343
349
|
if (!open)
|
|
344
350
|
return null;
|
|
345
|
-
return (_jsxs("div", { className: `chat-panel${cover ? " chat-panel-cover" : ""}`, ref: panel, style: cover ? undefined : { width: shown }, children: [!cover && _jsx("div", { className: "chat-panel-handle", onPointerDown: startResize, title: "Drag to resize" }), _jsxs("div", { className: "chat-panel-inner", style: cover ? undefined : { width: shown }, children: [_jsxs("div", { className: "chat-panel-tabs", children: [_jsxs("button", { className: `chat-panel-tab${active === "attachments" ? " chat-panel-tab-on" : ""}`, onClick: () => setActive("attachments"), children: [_jsx("span", { className: "material-symbols-outlined", children: "attach_file" }), _jsx("span", { className: "chat-panel-tab-name", children: "Attachments" })] }), tabs.map((tab) =>
|
|
351
|
+
return (_jsxs("div", { className: `chat-panel${cover ? " chat-panel-cover" : ""}`, ref: panel, style: cover ? undefined : { width: shown }, children: [!cover && _jsx("div", { className: "chat-panel-handle", onPointerDown: startResize, title: "Drag to resize" }), _jsxs("div", { className: "chat-panel-inner", style: cover ? undefined : { width: shown }, children: [_jsxs("div", { className: "chat-panel-tabs", children: [_jsxs("button", { className: `chat-panel-tab${active === "attachments" ? " chat-panel-tab-on" : ""}`, onClick: () => setActive("attachments"), children: [_jsx("span", { className: "material-symbols-outlined", children: "attach_file" }), _jsx("span", { className: "chat-panel-tab-name", children: "Attachments" })] }), tabs.map((tab) => {
|
|
352
|
+
const file = fileOf(tab.target);
|
|
353
|
+
return (_jsxs("div", { className: `chat-panel-tab${active === tab.key ? " chat-panel-tab-on" : ""}`, onClick: () => setActive(tab.key), title: file.name, children: [_jsx("span", { className: "material-symbols-outlined", children: iconOf(file.contentType, file.name) }), _jsx("span", { className: "chat-panel-tab-name", children: file.name }), tab.people.length > 0 && _jsx("span", { className: "chat-panel-tab-people", title: tab.people.map((p) => personName(p.profile)).join(", "), children: tab.people.length }), _jsx("button", { className: "material-symbols-outlined chat-panel-tab-close", title: "Close", "aria-label": `Close ${file.name}`, onClick: (e) => { e.stopPropagation(); closeTab(tab.key); }, children: "close" })] }, tab.key));
|
|
354
|
+
}), _jsx("span", { className: "chat-panel-tabs-gap" }), _jsx("button", { className: "material-symbols-outlined chat-panel-hide", title: "Hide", "aria-label": "Hide attachments", onClick: hide, children: "right_panel_close" })] }), _jsxs("div", { className: "chat-panel-body", children: [_jsx("div", { className: "chat-panel-list", style: active === "attachments" ? undefined : { display: "none" }, children: attachments === null
|
|
346
355
|
? _jsx("div", { className: "chat-panel-empty", children: failed ? "Couldn't load the attachments." : "Loading\u2026" })
|
|
347
356
|
: attachments.length === 0
|
|
348
357
|
? _jsx("div", { className: "chat-panel-empty", children: "Files sent in this chat, and files Parrot opens, show up here." })
|
|
@@ -350,7 +359,7 @@ export function ChatPanel({ room, bus, open, onOpenChange }) {
|
|
|
350
359
|
const key = keyOf(a);
|
|
351
360
|
return (_jsxs("div", { className: `chat-panel-row${selected === key ? " chat-panel-row-on" : ""}`, tabIndex: 0, onClick: () => setSelected(key), onDoubleClick: () => openTab(a, false), onKeyDown: (e) => { if (e.key === "Enter")
|
|
352
361
|
openTab(a, false); }, children: [_jsx(RowThumb, { attachment: a }), _jsxs("div", { className: "chat-panel-row-text", children: [_jsx("div", { className: "chat-panel-row-name", children: a.file.name }), _jsxs("div", { className: "chat-panel-row-meta", children: [a.author ? personName(a.author) : "Parrot", ", ", when(a.createdAt)] })] })] }, key));
|
|
353
|
-
}) }), tabs.map((tab) => (_jsx("div", { className: "chat-panel-view", style: active === tab.key ? undefined : { display: "none" }, children: _jsx(View, { target: tab.
|
|
362
|
+
}) }), tabs.map((tab) => (_jsx("div", { className: "chat-panel-view", style: active === tab.key ? undefined : { display: "none" }, children: _jsx(View, { target: tab.target, onPeople: (people) => setPeople(tab.key, people), onSaved: () => refreshPreview({ key: tab.key, file: fileOf(tab.target) }) }) }, tab.key)))] })] })] }));
|
|
354
363
|
}
|
|
355
364
|
function reducedMotion() {
|
|
356
365
|
return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
@@ -729,6 +738,11 @@ export function chatToolLabel(call) {
|
|
|
729
738
|
case "kill":
|
|
730
739
|
out = { icon: "cancel", label: pick(`Stopping process ${args.pid}`, `Stopped process ${args.pid}`, `Couldn't stop process ${args.pid}`) };
|
|
731
740
|
break;
|
|
741
|
+
case "internet": {
|
|
742
|
+
const mode = args.mode === "public" ? "public" : "private";
|
|
743
|
+
out = { icon: mode === "private" ? "shield_lock" : "public", label: pick(`Switching the internet to ${mode}`, `Switched the internet to ${mode}`, `Couldn't switch the internet to ${mode}`) };
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
732
746
|
case "open": {
|
|
733
747
|
const name = path ? baseName(path) : String(args.attachment ?? "the file");
|
|
734
748
|
out = { icon: "open_in_new", label: pick(`Opening ${name}`, `Opened ${name}`, `Couldn't open ${name}`) };
|
package/dist/src/view.d.ts
CHANGED
|
@@ -8,7 +8,10 @@ import type { Attachment } from "./parrot/share.js";
|
|
|
8
8
|
* too large to edit) is looked at together.
|
|
9
9
|
*/
|
|
10
10
|
export type Kind = 'text' | 'table' | 'image' | 'view';
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* What you may do in a file room: `read` for share links, `write` for owners, group members and
|
|
13
|
+
* chat members. A repository file is `write` only while the machine's agent user may write it.
|
|
14
|
+
*/
|
|
12
15
|
export type Role = 'read' | 'write';
|
|
13
16
|
/** Text and tables over this size open as `view`. */
|
|
14
17
|
export declare const EDIT_MAX_BYTES: number;
|
|
@@ -49,7 +52,7 @@ export declare function contentOf(kind: Kind, doc: Y.Doc): Uint8Array;
|
|
|
49
52
|
/** Base64 for document updates on the wire. */
|
|
50
53
|
export declare function toBase64(bytes: Uint8Array): string;
|
|
51
54
|
export declare function fromBase64(text: string): Uint8Array;
|
|
52
|
-
/** A file to open live: a Drive file (by path or share link) or a chat attachment. */
|
|
55
|
+
/** A file to open live: a Drive file (by path or share link), a file in a repository's /repository, or a chat attachment. */
|
|
53
56
|
export type Target = DriveFile | Attachment;
|
|
54
57
|
export type Hooks = Partial<{
|
|
55
58
|
/** Someone joined or left, or moved their pointer or selection. */
|
|
@@ -104,8 +107,9 @@ export declare class ViewRoom extends Room {
|
|
|
104
107
|
disconnect(): void;
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
107
|
-
* Join the live room for a file: a Drive file (by path or share link)
|
|
108
|
-
* attachment. Owners, group members and chat members
|
|
110
|
+
* Join the live room for a file: a Drive file (by path or share link), a file in a
|
|
111
|
+
* repository's /repository, or a chat attachment. Owners, group members and chat members
|
|
112
|
+
* can edit, repository files only where the machine's agent user could; share links look.
|
|
109
113
|
*/
|
|
110
114
|
export declare function connect(target: Target, hooks?: Hooks): Promise<{
|
|
111
115
|
room: ViewRoom;
|
package/dist/src/view.js
CHANGED
|
@@ -360,13 +360,13 @@ function authorizationFor(target) {
|
|
|
360
360
|
const auth = target.authorization;
|
|
361
361
|
if ('accessCode' in auth)
|
|
362
362
|
return { authorization: auth.authorization, accessCode: auth.accessCode };
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return { authorization: auth.authorization, server: `fauna:file:${auth.path}`, ...(auth.group ? { group: auth.group } : {}) };
|
|
363
|
+
const server = 'repository' in auth ? `fauna:file:repository:${auth.repository}/${auth.path.replace(/^\/+/, '')}` : `fauna:file:${auth.path}`;
|
|
364
|
+
return { authorization: auth.authorization, server, ...(auth.group ? { group: auth.group } : {}) };
|
|
366
365
|
}
|
|
367
366
|
/**
|
|
368
|
-
* Join the live room for a file: a Drive file (by path or share link)
|
|
369
|
-
* attachment. Owners, group members and chat members
|
|
367
|
+
* Join the live room for a file: a Drive file (by path or share link), a file in a
|
|
368
|
+
* repository's /repository, or a chat attachment. Owners, group members and chat members
|
|
369
|
+
* can edit, repository files only where the machine's agent user could; share links look.
|
|
370
370
|
*/
|
|
371
371
|
export async function connect(target, hooks = {}) {
|
|
372
372
|
let created;
|
package/dist/src/webhooks.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ApiKey, Credentials, Profile } from "./authentification.js";
|
|
2
2
|
import { GroupRef } from "./groups.js";
|
|
3
3
|
/** Event types a webhook can listen for: `chat.message` for groups and chats,
|
|
4
|
-
* `group.joined` and `group.left` for groups, payment events for API keys.
|
|
5
|
-
|
|
4
|
+
* `group.joined` and `group.left` for groups, payment events for API keys.
|
|
5
|
+
* `calendar.event.started` goes to a calendar event's own `webhook`. */
|
|
6
|
+
export type EventType = 'chat.message' | 'group.joined' | 'group.left' | 'payment.succeeded' | 'payment.expired' | 'payment.refunded' | 'payment.disputed' | 'calendar.event.started';
|
|
6
7
|
/** Who a webhook belongs to: a group, one chat, or the API key making the call. */
|
|
7
8
|
export type WebhookOwner = {
|
|
8
9
|
group: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fauna-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.771",
|
|
4
4
|
"description": "The Fauna SDK: accounts, Drive, groups, calendar, chats, payments and every Fauna app (Parrot, Marmot, Magpie, Cicada, Ant, Termite), plus React UI.",
|
|
5
5
|
"homepage": "https://docs.faunakit.com",
|
|
6
6
|
"repository": {
|