fauna-api 1.0.682 → 1.0.692
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 +34 -0
- package/dist/src/authentification.js +135 -0
- package/dist/src/calendar.d.ts +5 -2
- package/dist/src/calendar.js +5 -2
- package/dist/src/cicada/contacts.d.ts +19 -25
- package/dist/src/cicada/contacts.js +17 -5
- package/dist/src/cicada/inboxes.d.ts +16 -0
- package/dist/src/cicada/inboxes.js +10 -0
- package/dist/src/extensions/web/session.d.ts +1 -3
- package/dist/src/extensions/web/session.js +0 -1
- package/dist/src/groups.d.ts +167 -20
- package/dist/src/groups.js +287 -31
- package/dist/src/marmot/web.d.ts +27 -9
- package/dist/src/marmot/web.js +118 -79
- package/dist/src/parrot/room.d.ts +6 -2
- package/dist/src/parrot/room.js +11 -0
- package/dist/src/parrot/share.d.ts +4 -1
- package/dist/src/parrot/share.js +4 -2
- package/dist/src/rooms.d.ts +37 -11
- package/dist/src/rooms.js +46 -14
- package/dist/src/shared/server.d.ts +23 -0
- package/dist/src/shared/server.js +41 -0
- package/dist/src/ui/authentification.d.ts +6 -2
- package/dist/src/ui/authentification.js +36 -10
- package/dist/src/ui/browser.js +1 -1
- package/dist/src/ui/chat.js +8 -6
- package/dist/src/ui/chatSequence.d.ts +3 -2
- package/dist/src/ui/chatSequence.js +7 -2
- package/dist/src/ui/drive.js +1 -1
- package/dist/src/ui/index.d.ts +2 -0
- package/dist/src/ui/index.js +2 -0
- package/dist/src/ui/integrations.js +74 -0
- package/dist/src/ui/maps.d.ts +1 -1
- package/dist/src/ui/maps.js +3 -1
- package/dist/src/ui/markdown.d.ts +5 -0
- package/dist/src/ui/markdown.js +33 -0
- package/dist/src/ui/toast.d.ts +2 -2
- package/dist/src/ui/toast.js +5 -5
- package/dist/src/ui/ui.css +137 -0
- package/dist/src/webhooks.d.ts +2 -0
- package/package.json +2 -2
package/dist/src/marmot/web.js
CHANGED
|
@@ -726,8 +726,9 @@ function frameScopedRule(rule) {
|
|
|
726
726
|
return `${text.slice(0, open)}{ ${inner.join("\n")} }`;
|
|
727
727
|
}
|
|
728
728
|
/**
|
|
729
|
-
* Re-render a
|
|
730
|
-
*
|
|
729
|
+
* Re-render a shared page into a frame inside `mount` and apply incoming `WebMutation`s.
|
|
730
|
+
* The page is someone else's, so the frame is sandboxed without scripts: the replayer
|
|
731
|
+
* writes into it from this page, and nothing the page carries ever runs. The replayer
|
|
731
732
|
* keeps its own id->Node map mirroring the recorder's synthetic ids.
|
|
732
733
|
*/
|
|
733
734
|
export class DomReplayer {
|
|
@@ -756,8 +757,8 @@ export class DomReplayer {
|
|
|
756
757
|
writable: true,
|
|
757
758
|
value: null
|
|
758
759
|
});
|
|
759
|
-
// Mutations that arrive while a
|
|
760
|
-
//
|
|
760
|
+
// Mutations that arrive while a frame loads. They follow its snapshot, so they
|
|
761
|
+
// apply once its ids are bound; a newer snapshot drops them.
|
|
761
762
|
Object.defineProperty(this, "queued", {
|
|
762
763
|
enumerable: true,
|
|
763
764
|
configurable: true,
|
|
@@ -806,64 +807,65 @@ export class DomReplayer {
|
|
|
806
807
|
return this.mount;
|
|
807
808
|
}
|
|
808
809
|
/**
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
810
|
+
* A fresh frame for a replay, loaded with `srcdoc`. Same origin without scripts: this
|
|
811
|
+
* page can write into it, and the shared page can't run anything there. Null when a
|
|
812
|
+
* newer replay started while it loaded.
|
|
813
|
+
*/
|
|
814
|
+
async openFrame(srcdoc, load) {
|
|
815
|
+
this.queued = [];
|
|
816
|
+
this.mount.replaceChildren();
|
|
817
|
+
const iframe = this.mount.ownerDocument.createElement("iframe");
|
|
818
|
+
iframe.setAttribute("sandbox", "allow-same-origin");
|
|
819
|
+
iframe.style.cssText = "border:0;width:100%;height:100%;background:#fff;display:block;";
|
|
820
|
+
this.mount.appendChild(iframe);
|
|
821
|
+
this.iframe = iframe;
|
|
822
|
+
try {
|
|
823
|
+
await new Promise((resolve, reject) => {
|
|
824
|
+
iframe.onload = () => resolve();
|
|
825
|
+
iframe.onerror = () => reject(new Error("Failed to load shared page frame."));
|
|
826
|
+
iframe.srcdoc = srcdoc;
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
catch (err) {
|
|
830
|
+
if (load === this.loads)
|
|
831
|
+
this.queued = null;
|
|
832
|
+
throw err;
|
|
833
|
+
}
|
|
834
|
+
return load === this.loads ? iframe : null;
|
|
835
|
+
}
|
|
836
|
+
flushQueued() {
|
|
837
|
+
const queued = this.queued ?? [];
|
|
838
|
+
this.queued = null;
|
|
839
|
+
for (const mutation of queued)
|
|
840
|
+
this.mutate(mutation);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Apply a cleaned HTML document, or a fragment, which the frame parses into one.
|
|
844
|
+
* The `<html>/<head>/<body>` structure and DFS ids match the host's.
|
|
812
845
|
*/
|
|
813
846
|
async applyHtml(html, viewport) {
|
|
814
847
|
this.nodes.clear();
|
|
815
848
|
this._viewport = viewport ?? null;
|
|
816
|
-
const looksFull = /<html[\s>]/i.test(html);
|
|
817
849
|
const load = ++this.loads;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
// sandbox console errors (no allow-scripts by design). Script preloads stay as
|
|
830
|
-
// inert links: the host numbers them, so removing one would shift every id after.
|
|
831
|
-
const inert = html
|
|
832
|
-
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
833
|
-
.replace(/<script\b[^>]*\/>/gi, "")
|
|
834
|
-
.replace(/<link\b[^>]*rel=["'][^"']*preload[^"']*["'][^>]*>/gi, (tag) => /\bas=["'](script|module)["']/i.test(tag) || /rel=["']modulepreload["']/i.test(tag)
|
|
835
|
-
? tag.replace(/rel=["'][^"']*["']/i, 'rel="fauna-inert"')
|
|
836
|
-
: tag);
|
|
837
|
-
try {
|
|
838
|
-
await new Promise((resolve, reject) => {
|
|
839
|
-
iframe.onload = () => resolve();
|
|
840
|
-
iframe.onerror = () => reject(new Error("Failed to load shared page frame."));
|
|
841
|
-
iframe.srcdoc = inert;
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
catch (err) {
|
|
845
|
-
if (load === this.loads)
|
|
846
|
-
this.queued = null;
|
|
847
|
-
throw err;
|
|
848
|
-
}
|
|
849
|
-
if (load !== this.loads)
|
|
850
|
-
return;
|
|
851
|
-
this.bindIds();
|
|
852
|
-
this.restyle();
|
|
853
|
-
// A textarea shows its text, not its value attribute.
|
|
854
|
-
for (const field of Array.from(iframe.contentDocument?.querySelectorAll("textarea[value]") ?? [])) {
|
|
855
|
-
field.value = field.getAttribute("value") ?? "";
|
|
856
|
-
}
|
|
857
|
-
const queued = this.queued ?? [];
|
|
858
|
-
this.queued = null;
|
|
859
|
-
for (const mutation of queued)
|
|
860
|
-
this.mutate(mutation);
|
|
850
|
+
// Drop scripts that survived capture so Chrome doesn't log a sandbox error for each.
|
|
851
|
+
// Script preloads stay as inert links: the host numbers them, so removing one would
|
|
852
|
+
// shift every id after.
|
|
853
|
+
const inert = html
|
|
854
|
+
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
855
|
+
.replace(/<script\b[^>]*\/>/gi, "")
|
|
856
|
+
.replace(/<link\b[^>]*rel=["'][^"']*preload[^"']*["'][^>]*>/gi, (tag) => /\bas=["'](script|module)["']/i.test(tag) || /rel=["']modulepreload["']/i.test(tag)
|
|
857
|
+
? tag.replace(/rel=["'][^"']*["']/i, 'rel="fauna-inert"')
|
|
858
|
+
: tag);
|
|
859
|
+
const iframe = await this.openFrame(inert, load);
|
|
860
|
+
if (!iframe)
|
|
861
861
|
return;
|
|
862
|
-
}
|
|
863
|
-
this.queued = null;
|
|
864
|
-
this.iframe = null;
|
|
865
|
-
this.mount.innerHTML = html;
|
|
866
862
|
this.bindIds();
|
|
863
|
+
this.restyle();
|
|
864
|
+
// A textarea shows its text, not its value attribute.
|
|
865
|
+
for (const field of Array.from(iframe.contentDocument?.querySelectorAll("textarea[value]") ?? [])) {
|
|
866
|
+
field.value = field.getAttribute("value") ?? "";
|
|
867
|
+
}
|
|
868
|
+
this.flushQueued();
|
|
867
869
|
}
|
|
868
870
|
/** Bind synthetic ids: from the ids a recorded snapshot carries, else in DFS order to
|
|
869
871
|
* match a recorder that numbers in that order. */
|
|
@@ -1056,16 +1058,24 @@ export class DomReplayer {
|
|
|
1056
1058
|
ownerDoc() {
|
|
1057
1059
|
return this.iframe?.contentDocument ?? this.mount.ownerDocument;
|
|
1058
1060
|
}
|
|
1059
|
-
|
|
1061
|
+
/** `seen` keeps a subtree that names a node twice (or itself) from building forever. */
|
|
1062
|
+
build(id, map, seen = new Set()) {
|
|
1060
1063
|
const spec = map[id];
|
|
1061
|
-
if (!spec)
|
|
1064
|
+
if (!spec || seen.has(id))
|
|
1062
1065
|
return null;
|
|
1066
|
+
seen.add(id);
|
|
1063
1067
|
if (spec.kind === "text") {
|
|
1064
1068
|
const text = this.ownerDoc().createTextNode(spec.text);
|
|
1065
1069
|
this.nodes.set(id, text);
|
|
1066
1070
|
return text;
|
|
1067
1071
|
}
|
|
1068
|
-
|
|
1072
|
+
let el;
|
|
1073
|
+
try {
|
|
1074
|
+
el = this.ownerDoc().createElement(spec.tag);
|
|
1075
|
+
}
|
|
1076
|
+
catch {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1069
1079
|
for (const [name, value] of Object.entries(spec.attributes)) {
|
|
1070
1080
|
try {
|
|
1071
1081
|
el.setAttribute(name, value);
|
|
@@ -1074,7 +1084,7 @@ export class DomReplayer {
|
|
|
1074
1084
|
}
|
|
1075
1085
|
this.nodes.set(id, el);
|
|
1076
1086
|
for (const childId of spec.children) {
|
|
1077
|
-
const child = this.build(childId, map);
|
|
1087
|
+
const child = this.build(childId, map, seen);
|
|
1078
1088
|
if (child)
|
|
1079
1089
|
el.appendChild(child);
|
|
1080
1090
|
}
|
|
@@ -1087,16 +1097,20 @@ export class DomReplayer {
|
|
|
1087
1097
|
return this.build(node.id, map);
|
|
1088
1098
|
}
|
|
1089
1099
|
/** Render a fresh snapshot, replacing whatever the mount currently holds. */
|
|
1090
|
-
apply(snapshot) {
|
|
1091
|
-
this.loads++;
|
|
1092
|
-
this.queued = null;
|
|
1100
|
+
async apply(snapshot) {
|
|
1093
1101
|
this.nodes.clear();
|
|
1094
|
-
this.iframe = null;
|
|
1095
|
-
this.mount.innerHTML = "";
|
|
1096
1102
|
this._viewport = snapshot.viewport ?? null;
|
|
1103
|
+
const load = ++this.loads;
|
|
1104
|
+
const iframe = await this.openFrame("<!doctype html><html><head></head><body></body></html>", load);
|
|
1105
|
+
if (!iframe)
|
|
1106
|
+
return;
|
|
1107
|
+
const doc = iframe.contentDocument;
|
|
1097
1108
|
const root = this.build(snapshot.root, snapshot.nodes);
|
|
1098
|
-
if (root)
|
|
1099
|
-
|
|
1109
|
+
if (root?.nodeName === "HTML")
|
|
1110
|
+
doc.replaceChild(root, doc.documentElement);
|
|
1111
|
+
else if (root)
|
|
1112
|
+
doc.body.replaceChildren(root);
|
|
1113
|
+
this.flushQueued();
|
|
1100
1114
|
}
|
|
1101
1115
|
/** Apply one incremental mutation to the rendered tree. */
|
|
1102
1116
|
mutate(mutation) {
|
|
@@ -1370,6 +1384,11 @@ export function pageToHtml(root = document.documentElement) {
|
|
|
1370
1384
|
wrap.appendChild(frameClone);
|
|
1371
1385
|
return wrap;
|
|
1372
1386
|
}
|
|
1387
|
+
// The host streams this frame's document into the wrapper itself.
|
|
1388
|
+
if (recorder?.remote(el)) {
|
|
1389
|
+
wrap.setAttribute("data-fauna-iframe", "remote");
|
|
1390
|
+
return wrap;
|
|
1391
|
+
}
|
|
1373
1392
|
wrap.setAttribute("data-fauna-iframe", "blocked");
|
|
1374
1393
|
wrap.setAttribute("style", "display:flex;align-items:center;justify-content:center;min-height:48px;background:#f3f3f3;color:#666;font:12px/1.4 system-ui,sans-serif;border:1px dashed #bbb;");
|
|
1375
1394
|
wrap.appendChild(markedText("Embedded frame unavailable in share", recorder?.placeholder(el)));
|
|
@@ -1487,10 +1506,18 @@ export function pageToHtml(root = document.documentElement) {
|
|
|
1487
1506
|
cssUrls: frozen.urls,
|
|
1488
1507
|
};
|
|
1489
1508
|
}
|
|
1490
|
-
/** Install a MutationObserver that posts WebMutations to `window[pushName]
|
|
1509
|
+
/** Install a MutationObserver that posts WebMutations to `window[pushName]` as
|
|
1510
|
+
* `{ token, mutation }`, so the host knows which recorder sent each one.
|
|
1491
1511
|
* Framed documents are walked, observed, and re-inlined on navigation, with
|
|
1492
|
-
* their html, head, and body addressed as the divs `pageToHtml` emits.
|
|
1493
|
-
|
|
1512
|
+
* their html, head, and body addressed as the divs `pageToHtml` emits.
|
|
1513
|
+
* With `remoteFrames`, a frame whose document this page can't read is left to
|
|
1514
|
+
* the host, which streams it from the frame itself: its wrapper says
|
|
1515
|
+
* `data-fauna-iframe="remote"` and holds nothing. With `framed`, this document
|
|
1516
|
+
* is such a frame, recorded the way a parent would inline it, and
|
|
1517
|
+
* `__faunaRecorder.snapshot()` serializes it. */
|
|
1518
|
+
export function pageInstallRecorder(pushName, options) {
|
|
1519
|
+
const framed = options.framed === true;
|
|
1520
|
+
const remoteFrames = options.remoteFrames === true;
|
|
1494
1521
|
const BLOCKED_ATTR_PREFIXES = ["on"];
|
|
1495
1522
|
const BLOCKED_TAGS = new Set(["SCRIPT", "NOSCRIPT"]);
|
|
1496
1523
|
const FRAME_TAGS = new Set(["IFRAME", "FRAME", "OBJECT", "EMBED"]);
|
|
@@ -1664,7 +1691,7 @@ export function pageInstallRecorder(pushName) {
|
|
|
1664
1691
|
if (!node)
|
|
1665
1692
|
return null;
|
|
1666
1693
|
if (node.nodeType === Node.DOCUMENT_NODE)
|
|
1667
|
-
return node === document ? null : node.documentElement;
|
|
1694
|
+
return node === document && !framed ? null : node.documentElement;
|
|
1668
1695
|
return node.nodeType === Node.ELEMENT_NODE ? node : null;
|
|
1669
1696
|
};
|
|
1670
1697
|
const scrollOf = (el) => {
|
|
@@ -1715,7 +1742,8 @@ export function pageInstallRecorder(pushName) {
|
|
|
1715
1742
|
doc.removeEventListener("input", onInput, true);
|
|
1716
1743
|
doc.removeEventListener("scroll", onScroll, true);
|
|
1717
1744
|
};
|
|
1718
|
-
const
|
|
1745
|
+
const binding = window[pushName];
|
|
1746
|
+
const push = binding ? (mutation) => binding({ token: options.token, mutation }) : undefined;
|
|
1719
1747
|
const isBlockedAttr = (name) => BLOCKED_ATTR_PREFIXES.some((p) => name.toLowerCase().startsWith(p));
|
|
1720
1748
|
const idFor = (node) => {
|
|
1721
1749
|
let id = ids.get(node);
|
|
@@ -1735,7 +1763,7 @@ export function pageInstallRecorder(pushName) {
|
|
|
1735
1763
|
}
|
|
1736
1764
|
};
|
|
1737
1765
|
const frameRootTag = (el) => {
|
|
1738
|
-
if (el.ownerDocument === document)
|
|
1766
|
+
if (el.ownerDocument === document && !framed)
|
|
1739
1767
|
return "";
|
|
1740
1768
|
const tag = el.tagName.toLowerCase();
|
|
1741
1769
|
return tag === "html" || tag === "head" || tag === "body" ? tag : "";
|
|
@@ -1784,7 +1812,7 @@ export function pageInstallRecorder(pushName) {
|
|
|
1784
1812
|
// node holding its rules, like pageToHtml, so both number the same nodes. Viewers fetch
|
|
1785
1813
|
// nothing cross origin, and the copy can scope it. A sheet still loading sends its
|
|
1786
1814
|
// rules when it loads.
|
|
1787
|
-
const isFrameSheet = (el) => el.tagName === "LINK" && el.ownerDocument !== document && /\bstylesheet\b/i.test(el.getAttribute("rel") ?? "");
|
|
1815
|
+
const isFrameSheet = (el) => el.tagName === "LINK" && (el.ownerDocument !== document || framed) && /\bstylesheet\b/i.test(el.getAttribute("rel") ?? "");
|
|
1788
1816
|
const sheetCss = (link) => {
|
|
1789
1817
|
try {
|
|
1790
1818
|
const sheet = link.sheet;
|
|
@@ -1936,6 +1964,10 @@ export function pageInstallRecorder(pushName) {
|
|
|
1936
1964
|
attributes["data-fauna-iframe"] = "inlined";
|
|
1937
1965
|
return serializeNode(frame.doc.documentElement, into);
|
|
1938
1966
|
}
|
|
1967
|
+
if (remoteFrames) {
|
|
1968
|
+
attributes["data-fauna-iframe"] = "remote";
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1939
1971
|
attributes["data-fauna-iframe"] = "blocked";
|
|
1940
1972
|
attributes.style = PLACEHOLDER_STYLE;
|
|
1941
1973
|
const textId = frame.placeholder ?? nextId++;
|
|
@@ -1971,8 +2003,8 @@ export function pageInstallRecorder(pushName) {
|
|
|
1971
2003
|
attributes[attr.name] = absoluteAttr(el, attr.name, attr.value);
|
|
1972
2004
|
}
|
|
1973
2005
|
if (isFrame) {
|
|
1974
|
-
const
|
|
1975
|
-
into[id] = { id, kind: "element", tag: "div", attributes, children };
|
|
2006
|
+
const content = serializeFrameContent(watchFrame(el), into, attributes);
|
|
2007
|
+
into[id] = { id, kind: "element", tag: "div", attributes, children: content === null ? [] : [content] };
|
|
1976
2008
|
return id;
|
|
1977
2009
|
}
|
|
1978
2010
|
const children = [];
|
|
@@ -2010,7 +2042,7 @@ export function pageInstallRecorder(pushName) {
|
|
|
2010
2042
|
push?.({ kind: "attribute", id: parent, name: "data-fauna-iframe", value: wrapper["data-fauna-iframe"] ?? null });
|
|
2011
2043
|
const style = el.getAttribute("style");
|
|
2012
2044
|
push?.({ kind: "attribute", id: parent, name: "style", value: wrapper.style ?? (style === null ? null : absoluteAttr(el, "style", style)) });
|
|
2013
|
-
const node = map[childId];
|
|
2045
|
+
const node = childId === null ? undefined : map[childId];
|
|
2014
2046
|
if (node)
|
|
2015
2047
|
push?.({ kind: "insert", parent, node, subtree: Object.values(map), before: null });
|
|
2016
2048
|
};
|
|
@@ -2040,11 +2072,11 @@ export function pageInstallRecorder(pushName) {
|
|
|
2040
2072
|
if (view?.scrollX || view?.scrollY)
|
|
2041
2073
|
scrolledAtInstall.push(frame.doc.documentElement);
|
|
2042
2074
|
}
|
|
2043
|
-
else
|
|
2075
|
+
else if (!remoteFrames)
|
|
2044
2076
|
frame.placeholder = nextId++;
|
|
2045
2077
|
return;
|
|
2046
2078
|
}
|
|
2047
|
-
if (el
|
|
2079
|
+
if (el === document.documentElement ? framed && (window.scrollX || window.scrollY) : el.scrollTop || el.scrollLeft)
|
|
2048
2080
|
scrolledAtInstall.push(el);
|
|
2049
2081
|
for (const child of Array.from(el.childNodes))
|
|
2050
2082
|
walkBind(child);
|
|
@@ -2063,10 +2095,17 @@ export function pageInstallRecorder(pushName) {
|
|
|
2063
2095
|
id: (node) => ids.get(node),
|
|
2064
2096
|
sheetText: (link) => sheetTexts.get(link),
|
|
2065
2097
|
placeholder: (el) => frames.get(el)?.placeholder ?? undefined,
|
|
2098
|
+
remote: (el) => remoteFrames && frames.has(el) && !frames.get(el).doc,
|
|
2099
|
+
snapshot: () => {
|
|
2100
|
+
const nodes = {};
|
|
2101
|
+
return { root: serializeNode(document.documentElement, nodes), nodes };
|
|
2102
|
+
},
|
|
2066
2103
|
};
|
|
2067
2104
|
const observer = new MutationObserver(handle);
|
|
2068
2105
|
observer.observe(root, { subtree: true, childList: true, attributes: true, characterData: true });
|
|
2069
|
-
|
|
2106
|
+
// A frame's size is its parent's business.
|
|
2107
|
+
const onResize = () => { if (!framed)
|
|
2108
|
+
push?.({ kind: "viewport", width: window.innerWidth, height: window.innerHeight }); };
|
|
2070
2109
|
window.addEventListener("resize", onResize);
|
|
2071
2110
|
document.addEventListener("mouseover", onOver, true);
|
|
2072
2111
|
document.addEventListener("mouseout", onOut, true);
|
|
@@ -47,10 +47,11 @@ export type RoomToolState = {
|
|
|
47
47
|
result?: string;
|
|
48
48
|
/** What a running tool is waiting on, like the machine starting. */
|
|
49
49
|
progress?: string;
|
|
50
|
-
/** The live room the tool opened, to watch it: a browser, or a machine's desktop, by
|
|
50
|
+
/** The live room the tool opened, to watch it: a browser, joined with its access code, or a machine's desktop, joined by reference by the people who may use the machine. */
|
|
51
51
|
session?: {
|
|
52
52
|
kind: "browser" | "desktop";
|
|
53
|
-
|
|
53
|
+
reference: string;
|
|
54
|
+
code?: string;
|
|
54
55
|
url?: string;
|
|
55
56
|
};
|
|
56
57
|
};
|
|
@@ -96,6 +97,7 @@ export declare class ChatRoom extends Room {
|
|
|
96
97
|
private _human;
|
|
97
98
|
private _repository;
|
|
98
99
|
private _owner;
|
|
100
|
+
private _write;
|
|
99
101
|
private _reference;
|
|
100
102
|
private hooks;
|
|
101
103
|
private target;
|
|
@@ -112,6 +114,8 @@ export declare class ChatRoom extends Room {
|
|
|
112
114
|
getSystemPrompt(): string;
|
|
113
115
|
/** True for a people only chat: no model ever replies, so sends never queue. */
|
|
114
116
|
isHuman(): boolean;
|
|
117
|
+
/** False when you joined with a share link that only lets you read along. */
|
|
118
|
+
canWrite(): boolean;
|
|
115
119
|
/** The chat's repository, by id. Kept current for everyone in the room. */
|
|
116
120
|
get repository(): number | null;
|
|
117
121
|
/** The saved chat this room is bound to, by id, or null for an unbound room. */
|
package/dist/src/parrot/room.js
CHANGED
|
@@ -74,6 +74,12 @@ export class ChatRoom extends Room {
|
|
|
74
74
|
writable: true,
|
|
75
75
|
value: ""
|
|
76
76
|
});
|
|
77
|
+
Object.defineProperty(this, "_write", {
|
|
78
|
+
enumerable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
writable: true,
|
|
81
|
+
value: true
|
|
82
|
+
});
|
|
77
83
|
Object.defineProperty(this, "_reference", {
|
|
78
84
|
enumerable: true,
|
|
79
85
|
configurable: true,
|
|
@@ -134,6 +140,10 @@ export class ChatRoom extends Room {
|
|
|
134
140
|
isHuman() {
|
|
135
141
|
return this._human;
|
|
136
142
|
}
|
|
143
|
+
/** False when you joined with a share link that only lets you read along. */
|
|
144
|
+
canWrite() {
|
|
145
|
+
return this._write;
|
|
146
|
+
}
|
|
137
147
|
/** The chat's repository, by id. Kept current for everyone in the room. */
|
|
138
148
|
get repository() {
|
|
139
149
|
return this._repository;
|
|
@@ -291,6 +301,7 @@ export class ChatRoom extends Room {
|
|
|
291
301
|
this._human = state?.chat?.human === true;
|
|
292
302
|
this._repository = typeof state?.chat?.repository === "number" && state.chat.repository > 0 ? state.chat.repository : null;
|
|
293
303
|
this._owner = typeof state?.chat?.owner === "string" ? state.chat.owner : "";
|
|
304
|
+
this._write = state?.chat?.write !== false;
|
|
294
305
|
this._reference = typeof state?.room?.reference === "string" ? state.room.reference : "";
|
|
295
306
|
this.messages = Array.isArray(state?.messages) ? [...state.messages] : [];
|
|
296
307
|
this.queue = Array.isArray(state?.queue) ? [...state.queue] : [];
|
|
@@ -21,12 +21,15 @@ export declare function moveChatToGroup(credentials: Credentials, chatId: string
|
|
|
21
21
|
* chat (reference becomes `parrot:chat:{chatId}`, billed to the chat's
|
|
22
22
|
* balance-holder) and seed the newest window from durable; omit it for an
|
|
23
23
|
* unbound ephemeral room. `messages` seeds only ephemeral rooms. `share: true`
|
|
24
|
-
* also mints an access code
|
|
24
|
+
* also mints an access code; people who join with it read along, and with
|
|
25
|
+
* `write: true` they can send too (replies to them run without the chat's
|
|
26
|
+
* repository and memories). Returns the room's `reference` (+ `code` when shared).
|
|
25
27
|
*/
|
|
26
28
|
export declare function openRoom(authorization: Credentials | ApiKey, options?: {
|
|
27
29
|
chatId?: string;
|
|
28
30
|
messages?: Content[];
|
|
29
31
|
share?: boolean;
|
|
32
|
+
write?: boolean;
|
|
30
33
|
}): Promise<{
|
|
31
34
|
reference: string;
|
|
32
35
|
roomId: string;
|
package/dist/src/parrot/share.js
CHANGED
|
@@ -19,7 +19,9 @@ export async function moveChatToGroup(credentials, chatId, groupId) {
|
|
|
19
19
|
* chat (reference becomes `parrot:chat:{chatId}`, billed to the chat's
|
|
20
20
|
* balance-holder) and seed the newest window from durable; omit it for an
|
|
21
21
|
* unbound ephemeral room. `messages` seeds only ephemeral rooms. `share: true`
|
|
22
|
-
* also mints an access code
|
|
22
|
+
* also mints an access code; people who join with it read along, and with
|
|
23
|
+
* `write: true` they can send too (replies to them run without the chat's
|
|
24
|
+
* repository and memories). Returns the room's `reference` (+ `code` when shared).
|
|
23
25
|
*/
|
|
24
26
|
export async function openRoom(authorization, options = {}) {
|
|
25
27
|
const messages = options.messages ? await serializeTranscript(options.messages) : undefined;
|
|
@@ -29,7 +31,7 @@ export async function openRoom(authorization, options = {}) {
|
|
|
29
31
|
"Content-Type": "application/json",
|
|
30
32
|
"Authorization": "email" in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`,
|
|
31
33
|
},
|
|
32
|
-
body: JSON.stringify({ chatId: options.chatId, messages, share: options.share }),
|
|
34
|
+
body: JSON.stringify({ chatId: options.chatId, messages, share: options.share, write: options.write }),
|
|
33
35
|
});
|
|
34
36
|
if (!response.ok)
|
|
35
37
|
throw new Error(await response.text());
|
package/dist/src/rooms.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { Fauna } from "../index.js";
|
|
1
2
|
import { ApiKey, Credentials, Profile } from "./authentification.js";
|
|
2
3
|
import { Presence, Room, Hooks as ServerHooks } from "./shared/server.js";
|
|
3
4
|
/** What a member may do: `read` sees the state, `write` can also set it. */
|
|
4
5
|
export type Role = 'read' | 'write';
|
|
5
|
-
/** Who is in a room:
|
|
6
|
+
/** Who is in a room: everyone in a group, directly or through a nested group, or
|
|
7
|
+
* one person by email. A person's room takes groups its owner is in; a group's
|
|
8
|
+
* room takes groups nested inside it. */
|
|
6
9
|
export type Member = ({
|
|
7
10
|
group: string;
|
|
8
11
|
} | {
|
|
@@ -10,10 +13,12 @@ export type Member = ({
|
|
|
10
13
|
}) & {
|
|
11
14
|
role: Role;
|
|
12
15
|
};
|
|
13
|
-
/** A live room
|
|
16
|
+
/** A live room a person or a group owns. Everyone in it sees `state` change live. */
|
|
14
17
|
export type LiveRoom = {
|
|
15
18
|
id: string;
|
|
16
19
|
name: string;
|
|
20
|
+
/** The group that owns it; absent when a person does. */
|
|
21
|
+
group?: string;
|
|
17
22
|
members: Member[];
|
|
18
23
|
/** Any JSON, up to 256 KB, always set whole. Fauna never reads it. */
|
|
19
24
|
state: unknown;
|
|
@@ -22,39 +27,60 @@ export type LiveRoom = {
|
|
|
22
27
|
created: Date;
|
|
23
28
|
updated: Date;
|
|
24
29
|
};
|
|
30
|
+
export declare const LiveRoom: {
|
|
31
|
+
fromJSON(json: any): LiveRoom;
|
|
32
|
+
};
|
|
25
33
|
/** What `create` takes. Every field is optional. */
|
|
26
34
|
export type Draft = {
|
|
27
35
|
name?: string;
|
|
36
|
+
/** A group you are a direct admin of, to own the room. Without it, you own it. */
|
|
37
|
+
group?: string;
|
|
28
38
|
members?: Member[];
|
|
29
39
|
state?: unknown;
|
|
30
40
|
};
|
|
31
|
-
/**
|
|
41
|
+
/** One of a room's groups someone is in: `through` is the group's own id when
|
|
42
|
+
* they are directly in it, else the nested group they come in through. */
|
|
43
|
+
export type PersonGroup = {
|
|
44
|
+
id: string;
|
|
45
|
+
through: string;
|
|
46
|
+
};
|
|
47
|
+
export declare const PersonGroup: {
|
|
48
|
+
fromJSON(json: any): PersonGroup;
|
|
49
|
+
};
|
|
50
|
+
/** Someone in a room: their profile, and each of the room's groups they're in. */
|
|
32
51
|
export type Person = {
|
|
33
52
|
profile: Profile;
|
|
34
|
-
groups:
|
|
53
|
+
groups: PersonGroup[];
|
|
54
|
+
};
|
|
55
|
+
export declare const Person: {
|
|
56
|
+
fromJSON(json: any): Person;
|
|
35
57
|
};
|
|
36
58
|
/** Room hooks, plus `onState` whenever someone changes the state. */
|
|
37
59
|
export type Hooks = ServerHooks & Partial<{
|
|
38
60
|
onState(room: StateRoom, state: unknown, version: number): void;
|
|
39
61
|
}>;
|
|
40
62
|
/**
|
|
41
|
-
* Make a live room.
|
|
42
|
-
*
|
|
63
|
+
* Make a live room. You own it, also when you sign with an API key, or with
|
|
64
|
+
* `draft.group`, that group does: you must be its direct admin. Everyone in an
|
|
65
|
+
* owner group reads its room, and its admins manage it. Only the owner changes
|
|
66
|
+
* who is in it or deletes it.
|
|
43
67
|
*/
|
|
44
68
|
export declare function create(authorization: Credentials | ApiKey, draft?: Draft): Promise<LiveRoom>;
|
|
45
69
|
/** A room you own or are a member of, with its state. */
|
|
46
70
|
export declare function get(authorization: Credentials | ApiKey, id: string): Promise<LiveRoom>;
|
|
47
|
-
/** Everyone in a room
|
|
71
|
+
/** Everyone in a room: everyone in its groups (and its owner group), directly or
|
|
72
|
+
* through a nested group, and its email members. Anyone who can read the room. */
|
|
48
73
|
export declare function people(authorization: Credentials | ApiKey, id: string): Promise<Person[]>;
|
|
49
|
-
/** Replace who is in a room.
|
|
74
|
+
/** Replace who is in a room. Its owner only: the person, or for a group's room, that group's admins. */
|
|
50
75
|
export declare function setMembers(authorization: Credentials | ApiKey, id: string, members: Member[]): Promise<LiveRoom>;
|
|
51
76
|
/**
|
|
52
|
-
* Replace a room's state; everyone in it sees the change. The owner
|
|
53
|
-
* only. With `ifVersion`, it
|
|
77
|
+
* Replace a room's state; everyone in it sees the change. The owner (for a
|
|
78
|
+
* group's room, that group's admins) and writers only. With `ifVersion`, it
|
|
79
|
+
* writes only while the room is still at that version.
|
|
54
80
|
* Resolves to the new version.
|
|
55
81
|
*/
|
|
56
82
|
export declare function setState(authorization: Credentials | ApiKey, id: string, state: unknown, ifVersion?: number): Promise<number>;
|
|
57
|
-
/** Delete a room; everyone in it is disconnected.
|
|
83
|
+
/** Delete a room; everyone in it is disconnected. Its owner only: the person, or for a group's room, that group's admins. */
|
|
58
84
|
export declare function remove(authorization: Credentials | ApiKey, id: string): Promise<void>;
|
|
59
85
|
/** A live room you are in: its state, kept current, and who else is here. */
|
|
60
86
|
export declare class StateRoom extends Room {
|
package/dist/src/rooms.js
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import { Fauna } from "../index.js";
|
|
2
2
|
import { Room, connect as connectServer } from "./shared/server.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export const LiveRoom = {
|
|
4
|
+
fromJSON(json) {
|
|
5
|
+
return {
|
|
6
|
+
id: String(json.id),
|
|
7
|
+
name: String(json.name),
|
|
8
|
+
...(json.group ? { group: String(json.group) } : {}),
|
|
9
|
+
members: json.members,
|
|
10
|
+
state: json.state,
|
|
11
|
+
version: Number(json.version),
|
|
12
|
+
created: new Date(json.created),
|
|
13
|
+
updated: new Date(json.updated),
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
export const PersonGroup = {
|
|
18
|
+
fromJSON(json) {
|
|
19
|
+
return { id: String(json.id), through: String(json.through) };
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export const Person = {
|
|
23
|
+
fromJSON(json) {
|
|
24
|
+
return {
|
|
25
|
+
profile: {
|
|
26
|
+
email: String(json.profile.email),
|
|
27
|
+
...(json.profile.displayName ? { displayName: String(json.profile.displayName) } : {}),
|
|
28
|
+
...(json.profile.icon ? { icon: String(json.profile.icon) } : {}),
|
|
29
|
+
},
|
|
30
|
+
groups: json.groups.map(PersonGroup.fromJSON),
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
6
34
|
/**
|
|
7
|
-
* Make a live room.
|
|
8
|
-
*
|
|
35
|
+
* Make a live room. You own it, also when you sign with an API key, or with
|
|
36
|
+
* `draft.group`, that group does: you must be its direct admin. Everyone in an
|
|
37
|
+
* owner group reads its room, and its admins manage it. Only the owner changes
|
|
38
|
+
* who is in it or deletes it.
|
|
9
39
|
*/
|
|
10
40
|
export async function create(authorization, draft = {}) {
|
|
11
41
|
const response = await fetch(`${Fauna.getURL()}/rooms`, {
|
|
@@ -19,7 +49,7 @@ export async function create(authorization, draft = {}) {
|
|
|
19
49
|
if (!response.ok) {
|
|
20
50
|
throw new Error(await response.text());
|
|
21
51
|
}
|
|
22
|
-
return
|
|
52
|
+
return LiveRoom.fromJSON(await response.json());
|
|
23
53
|
}
|
|
24
54
|
/** A room you own or are a member of, with its state. */
|
|
25
55
|
export async function get(authorization, id) {
|
|
@@ -32,9 +62,10 @@ export async function get(authorization, id) {
|
|
|
32
62
|
if (!response.ok) {
|
|
33
63
|
throw new Error(await response.text());
|
|
34
64
|
}
|
|
35
|
-
return
|
|
65
|
+
return LiveRoom.fromJSON(await response.json());
|
|
36
66
|
}
|
|
37
|
-
/** Everyone in a room
|
|
67
|
+
/** Everyone in a room: everyone in its groups (and its owner group), directly or
|
|
68
|
+
* through a nested group, and its email members. Anyone who can read the room. */
|
|
38
69
|
export async function people(authorization, id) {
|
|
39
70
|
const response = await fetch(`${Fauna.getURL()}/rooms/${encodeURIComponent(id)}/people`, {
|
|
40
71
|
method: "GET",
|
|
@@ -45,9 +76,9 @@ export async function people(authorization, id) {
|
|
|
45
76
|
if (!response.ok) {
|
|
46
77
|
throw new Error(await response.text());
|
|
47
78
|
}
|
|
48
|
-
return await response.json();
|
|
79
|
+
return (await response.json()).map(Person.fromJSON);
|
|
49
80
|
}
|
|
50
|
-
/** Replace who is in a room.
|
|
81
|
+
/** Replace who is in a room. Its owner only: the person, or for a group's room, that group's admins. */
|
|
51
82
|
export async function setMembers(authorization, id, members) {
|
|
52
83
|
const response = await fetch(`${Fauna.getURL()}/rooms/${encodeURIComponent(id)}/members`, {
|
|
53
84
|
method: "PUT",
|
|
@@ -60,11 +91,12 @@ export async function setMembers(authorization, id, members) {
|
|
|
60
91
|
if (!response.ok) {
|
|
61
92
|
throw new Error(await response.text());
|
|
62
93
|
}
|
|
63
|
-
return
|
|
94
|
+
return LiveRoom.fromJSON(await response.json());
|
|
64
95
|
}
|
|
65
96
|
/**
|
|
66
|
-
* Replace a room's state; everyone in it sees the change. The owner
|
|
67
|
-
* only. With `ifVersion`, it
|
|
97
|
+
* Replace a room's state; everyone in it sees the change. The owner (for a
|
|
98
|
+
* group's room, that group's admins) and writers only. With `ifVersion`, it
|
|
99
|
+
* writes only while the room is still at that version.
|
|
68
100
|
* Resolves to the new version.
|
|
69
101
|
*/
|
|
70
102
|
export async function setState(authorization, id, state, ifVersion) {
|
|
@@ -81,7 +113,7 @@ export async function setState(authorization, id, state, ifVersion) {
|
|
|
81
113
|
}
|
|
82
114
|
return (await response.json()).version;
|
|
83
115
|
}
|
|
84
|
-
/** Delete a room; everyone in it is disconnected.
|
|
116
|
+
/** Delete a room; everyone in it is disconnected. Its owner only: the person, or for a group's room, that group's admins. */
|
|
85
117
|
export async function remove(authorization, id) {
|
|
86
118
|
const response = await fetch(`${Fauna.getURL()}/rooms/${encodeURIComponent(id)}`, {
|
|
87
119
|
method: "DELETE",
|