@protobase/client 0.59.2 → 0.60.0
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/calls/index.d.ts +2 -2
- package/dist/calls/join-call.d.ts +8 -2
- package/dist/calls/join-call.js +17 -3
- package/dist/calls/msid-tracks.js +6 -6
- package/dist/calls/peer-link.js +21 -13
- package/dist/client.d.ts +10 -1
- package/dist/client.js +16 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -1
- package/dist/record-calls.d.ts +26 -0
- package/dist/record-calls.js +49 -0
- package/dist/routed-client.d.ts +1 -1
- package/dist/routed-client.js +5 -2
- package/dist/status.d.ts +19 -0
- package/dist/status.js +36 -0
- package/dist/types.d.ts +1 -0
- package/package.json +3 -3
package/dist/calls/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { CallTrack, PublishOptions } from "./media-link.js";
|
|
2
2
|
import { Call, CallState, JoinCallOptions, joinCall } from "./join-call.js";
|
|
3
|
-
import { CallLeftReason, CallMode, CallTrackSource } from "@protobase/schema/internal";
|
|
4
|
-
export { type Call, type CallLeftReason, type CallMode, type CallState, type CallTrack, type CallTrackSource, type JoinCallOptions, type PublishOptions, joinCall };
|
|
3
|
+
import { CallLeftReason, CallMode, CallRosterMember, CallTrackSource } from "@protobase/schema/internal";
|
|
4
|
+
export { type Call, type CallLeftReason, type CallMode, type CallRosterMember, type CallState, type CallTrack, type CallTrackSource, type JoinCallOptions, type PublishOptions, joinCall };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CallTrack, PublishOptions } from "./media-link.js";
|
|
2
|
-
import { CallLeftReason, CallTrackSource } from "@protobase/schema/internal";
|
|
2
|
+
import { CallLeftReason, CallRosterMember, CallTrackSource } from "@protobase/schema/internal";
|
|
3
3
|
//#region src/calls/join-call.d.ts
|
|
4
4
|
/** Where a call stands: `resuming` while the link to Protobase is restored, with media still flowing. */
|
|
5
5
|
export type CallState = {
|
|
@@ -21,6 +21,8 @@ export type JoinCallOptions = {
|
|
|
21
21
|
url: string;
|
|
22
22
|
/** Every track of other members the member hears, whenever one comes, goes or its media arrives. */
|
|
23
23
|
onTracks?: (tracks: CallTrack[]) => void;
|
|
24
|
+
/** Everyone in the call, the member too, in the order they came, whenever that changes. */
|
|
25
|
+
onMembers?: (members: CallRosterMember[]) => void;
|
|
24
26
|
/** Who speaks how loudly, from 0 to 1, for the members whose level changed. */
|
|
25
27
|
onSpeakers?: (speakers: {
|
|
26
28
|
member: string;
|
|
@@ -31,14 +33,18 @@ export type JoinCallOptions = {
|
|
|
31
33
|
onUnpublished?: (source: CallTrackSource) => void;
|
|
32
34
|
/** How long to wait before the `attempt`th try to restore a lost link. */
|
|
33
35
|
retryDelayMs?: (attempt: number) => number;
|
|
34
|
-
/** How many tries in a row to restore a lost link before the call is left. Default 8. */
|
|
36
|
+
/** How many tries in a row to restore a lost link, or to get in while the server fails, before the call is left. Default 8. */
|
|
35
37
|
maxRetries?: number;
|
|
36
38
|
WebSocket?: typeof WebSocket;
|
|
37
39
|
createPeerConnection?: (configuration: RTCConfiguration) => RTCPeerConnection;
|
|
38
40
|
};
|
|
39
41
|
export type Call = {
|
|
42
|
+
/** The call, as the record's calls list it and `calls.remove` takes it. */
|
|
43
|
+
readonly id: string;
|
|
40
44
|
/** The member's participant in the media server. */
|
|
41
45
|
readonly participant: string;
|
|
46
|
+
/** Everyone in the call now, the member too. */
|
|
47
|
+
members: () => CallRosterMember[];
|
|
42
48
|
/** Sends tracks under the sources they carry, such as `{ camera, microphone }`; rejects when the member may not. */
|
|
43
49
|
publish: (tracks: Partial<Record<CallTrackSource, MediaStreamTrack>>, options?: PublishOptions) => Promise<void>;
|
|
44
50
|
unpublish: (source: CallTrackSource) => Promise<void>;
|
package/dist/calls/join-call.js
CHANGED
|
@@ -12,6 +12,8 @@ const leftCodes = {
|
|
|
12
12
|
1e3: "ended",
|
|
13
13
|
1008: "removed"
|
|
14
14
|
};
|
|
15
|
+
/** Close codes of a server that failed, or was too busy, for now: getting in is tried again. */
|
|
16
|
+
const failedCodes = /* @__PURE__ */ new Set([1011, 1013]);
|
|
15
17
|
/**
|
|
16
18
|
* Joins a call through the app's function socket and resolves once the member is in it. Media flows over plain
|
|
17
19
|
* `RTCPeerConnection`s, to the media server or straight to the other members, as the app's calls are configured;
|
|
@@ -26,6 +28,8 @@ const joinCall = (options) => {
|
|
|
26
28
|
let socket;
|
|
27
29
|
let link;
|
|
28
30
|
let participant = "";
|
|
31
|
+
let callId = "";
|
|
32
|
+
let members = [];
|
|
29
33
|
let remote = [];
|
|
30
34
|
let ended = false;
|
|
31
35
|
let rejoin = false;
|
|
@@ -74,6 +78,7 @@ const joinCall = (options) => {
|
|
|
74
78
|
const before = link?.sending();
|
|
75
79
|
link?.close();
|
|
76
80
|
participant = message.participant;
|
|
81
|
+
callId = message.call;
|
|
77
82
|
link = newLink(message);
|
|
78
83
|
failures = 0;
|
|
79
84
|
setState({ state: "joined" });
|
|
@@ -90,6 +95,10 @@ const joinCall = (options) => {
|
|
|
90
95
|
case "speakers":
|
|
91
96
|
options.onSpeakers?.(message.speakers);
|
|
92
97
|
return;
|
|
98
|
+
case "members":
|
|
99
|
+
members = message.members;
|
|
100
|
+
options.onMembers?.(members);
|
|
101
|
+
return;
|
|
93
102
|
case "left":
|
|
94
103
|
if (message.reason === "rejoin") rejoin = true;
|
|
95
104
|
else end(message.reason);
|
|
@@ -107,6 +116,7 @@ const joinCall = (options) => {
|
|
|
107
116
|
const resume = mode === "resume" && link ? await link.resumeState(participant) : void 0;
|
|
108
117
|
send(resume ? {
|
|
109
118
|
type: "resume",
|
|
119
|
+
call: callId,
|
|
110
120
|
...resume
|
|
111
121
|
} : { type: "join" });
|
|
112
122
|
});
|
|
@@ -127,19 +137,23 @@ const joinCall = (options) => {
|
|
|
127
137
|
}
|
|
128
138
|
const left = leftCodes[code];
|
|
129
139
|
if (left) return end(left);
|
|
130
|
-
if (!participant) return end("refused");
|
|
140
|
+
if (!participant && !failedCodes.has(code)) return end("refused");
|
|
131
141
|
failures++;
|
|
132
|
-
if (failures > maxRetries) return end("ended");
|
|
133
|
-
setState({ state: "resuming" });
|
|
142
|
+
if (failures > maxRetries) return end(participant ? "ended" : "unavailable");
|
|
143
|
+
setState(participant ? { state: "resuming" } : { state: "connecting" });
|
|
134
144
|
const next = mode === "resume" && opened && !resumedSinceOpen ? "join" : "resume";
|
|
135
145
|
setTimeout(() => {
|
|
136
146
|
if (!ended) connect(next);
|
|
137
147
|
}, retryDelay(failures - 1));
|
|
138
148
|
};
|
|
139
149
|
const call = {
|
|
150
|
+
get id() {
|
|
151
|
+
return callId;
|
|
152
|
+
},
|
|
140
153
|
get participant() {
|
|
141
154
|
return participant;
|
|
142
155
|
},
|
|
156
|
+
members: () => members,
|
|
143
157
|
publish: async (tracks, publishOptions) => {
|
|
144
158
|
if (!link) throw new Error("The call was left");
|
|
145
159
|
await link.publish(tracks, publishOptions);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
//#region src/calls/msid-tracks.ts
|
|
2
2
|
/**
|
|
3
|
-
* The `mid` of each media section of a description by the
|
|
4
|
-
*
|
|
3
|
+
* The `mid` of each media section of a description by the id of the stream its `a=msid` line names. Unlike the track
|
|
4
|
+
* id there, which Firefox makes up for each sender, both browsers write the sending page's stream id.
|
|
5
5
|
*/
|
|
6
|
-
const
|
|
6
|
+
const midsByStreamId = (sdp) => {
|
|
7
7
|
const mids = /* @__PURE__ */ new Map();
|
|
8
8
|
for (const section of sdp.split(/\r?\nm=/).slice(1)) {
|
|
9
9
|
const mid = /^a=mid:(\S+)/m.exec(section)?.[1];
|
|
10
|
-
const
|
|
11
|
-
if (mid !== void 0 &&
|
|
10
|
+
const stream = /^a=msid:(\S+) \S+/m.exec(section)?.[1];
|
|
11
|
+
if (mid !== void 0 && stream !== void 0 && stream !== "-") mids.set(stream, mid);
|
|
12
12
|
}
|
|
13
13
|
return mids;
|
|
14
14
|
};
|
|
15
15
|
//#endregion
|
|
16
|
-
export {
|
|
16
|
+
export { midsByStreamId };
|
package/dist/calls/peer-link.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createPeerConnection } from "./media-link.js";
|
|
2
|
-
import {
|
|
2
|
+
import { midsByStreamId } from "./msid-tracks.js";
|
|
3
3
|
//#region src/calls/peer-link.ts
|
|
4
4
|
/**
|
|
5
5
|
* The media link without a media server: one `RTCPeerConnection` to each other member, whose browser answers, so either
|
|
6
6
|
* side may offer. Protobase passes the descriptions and candidates between the two ("perfect negotiation": the member
|
|
7
7
|
* whose id sorts first gives way when both offer at once) and says who is in the call and which tracks each member
|
|
8
|
-
* hears. A track goes by the id the sending browser
|
|
8
|
+
* hears. A track goes by the id of the stream the sending browser sends it on, which the receiving browser sees too.
|
|
9
9
|
*/
|
|
10
10
|
const peerLink = (self, { iceServers, send, onTracks, createPeerConnection: createPeerConnection$1 = createPeerConnection }) => {
|
|
11
11
|
const peers = /* @__PURE__ */ new Map();
|
|
@@ -13,10 +13,13 @@ const peerLink = (self, { iceServers, send, onTracks, createPeerConnection: crea
|
|
|
13
13
|
let remote = [];
|
|
14
14
|
let publishing;
|
|
15
15
|
let publishes = Promise.resolve();
|
|
16
|
-
const sendOn = (pc, track) => pc.addTransceiver(track, {
|
|
16
|
+
const sendOn = (pc, { track, stream }) => pc.addTransceiver(track, {
|
|
17
|
+
direction: "sendonly",
|
|
18
|
+
streams: [stream]
|
|
19
|
+
}).sender;
|
|
17
20
|
const receivedTrack = ({ member, sid }) => {
|
|
18
21
|
const pc = peers.get(member)?.pc;
|
|
19
|
-
const mid = pc?.remoteDescription &&
|
|
22
|
+
const mid = pc?.remoteDescription && midsByStreamId(pc.remoteDescription.sdp).get(sid);
|
|
20
23
|
return mid === void 0 ? void 0 : pc.getTransceivers().find((transceiver) => transceiver.mid === mid)?.receiver.track;
|
|
21
24
|
};
|
|
22
25
|
const emitTracks = () => onTracks(remote.map((track) => ({
|
|
@@ -57,7 +60,7 @@ const peerLink = (self, { iceServers, send, onTracks, createPeerConnection: crea
|
|
|
57
60
|
if (pc.iceConnectionState === "failed") pc.restartIce();
|
|
58
61
|
};
|
|
59
62
|
pc.ontrack = () => emitTracks();
|
|
60
|
-
for (const [source,
|
|
63
|
+
for (const [source, sending] of local) peer.senders.set(source, sendOn(pc, sending));
|
|
61
64
|
return peer;
|
|
62
65
|
};
|
|
63
66
|
const disconnect = (member) => {
|
|
@@ -111,14 +114,22 @@ const peerLink = (self, { iceServers, send, onTracks, createPeerConnection: crea
|
|
|
111
114
|
const added = Object.entries(tracks);
|
|
112
115
|
for (const [source] of added) if (local.has(source)) throw new Error(`The call already sends a ${source} track: unpublish it first`);
|
|
113
116
|
if (added.length === 0) return;
|
|
117
|
+
const sending = added.map(([source, track]) => {
|
|
118
|
+
const stream = new MediaStream([track]);
|
|
119
|
+
return [source, {
|
|
120
|
+
track,
|
|
121
|
+
sid: stream.id,
|
|
122
|
+
stream
|
|
123
|
+
}];
|
|
124
|
+
});
|
|
114
125
|
const answered = new Promise((resolve, reject) => publishing = {
|
|
115
126
|
resolve,
|
|
116
127
|
reject
|
|
117
128
|
});
|
|
118
129
|
send({
|
|
119
130
|
type: "publish",
|
|
120
|
-
tracks:
|
|
121
|
-
cid:
|
|
131
|
+
tracks: sending.map(([source, { track, sid }]) => ({
|
|
132
|
+
cid: sid,
|
|
122
133
|
kind: track.kind,
|
|
123
134
|
source
|
|
124
135
|
}))
|
|
@@ -128,11 +139,8 @@ const peerLink = (self, { iceServers, send, onTracks, createPeerConnection: crea
|
|
|
128
139
|
} finally {
|
|
129
140
|
publishing = void 0;
|
|
130
141
|
}
|
|
131
|
-
for (const [source, track] of
|
|
132
|
-
local.set(source,
|
|
133
|
-
track,
|
|
134
|
-
sid: track.id
|
|
135
|
-
});
|
|
142
|
+
for (const [source, track] of sending) {
|
|
143
|
+
local.set(source, track);
|
|
136
144
|
for (const peer of peers.values()) peer.senders.set(source, sendOn(peer.pc, track));
|
|
137
145
|
}
|
|
138
146
|
};
|
|
@@ -173,7 +181,7 @@ const peerLink = (self, { iceServers, send, onTracks, createPeerConnection: crea
|
|
|
173
181
|
resumeState: async (participant) => ({
|
|
174
182
|
participant,
|
|
175
183
|
tracks: [...local].map(([source, { track, sid }]) => ({
|
|
176
|
-
cid:
|
|
184
|
+
cid: sid,
|
|
177
185
|
sid,
|
|
178
186
|
kind: track.kind,
|
|
179
187
|
source
|
package/dist/client.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ import { ClientOptions } from "./transport.js";
|
|
|
3
3
|
import { RegisteredDevice, WebDeviceRegistration } from "./devices.js";
|
|
4
4
|
import { InboxPage } from "./notification-history.js";
|
|
5
5
|
import { FilterInput, ListParams, MapParams } from "./query-string.js";
|
|
6
|
+
import { Aggregate, AggregateParams, BatchOp, BatchResult, CommentPage, Facet, FormChallenge, FormProof, Histogram, HistoryPage, ListPage, MapResult, MentionableUser, MetaResult, PublicForm, RecordComment, RecordOf, ResourceRef, Series, SeriesParams, Stored } from "./types.js";
|
|
7
|
+
import { RecordCall, RecordCallsHandlers } from "./record-calls.js";
|
|
6
8
|
import { UploadProgress } from "./upload-request.js";
|
|
7
9
|
import { LiveHandlers } from "./live.js";
|
|
8
|
-
import { Aggregate, AggregateParams, BatchOp, BatchResult, CommentPage, Facet, FormChallenge, FormProof, Histogram, HistoryPage, ListPage, MapResult, MentionableUser, MetaResult, PublicForm, RecordComment, RecordOf, ResourceRef, Series, SeriesParams, Stored } from "./types.js";
|
|
9
10
|
import { KeyValue } from "@protobase/schema";
|
|
10
11
|
//#region src/client.d.ts
|
|
11
12
|
/** How `invoke` calls a function: GET without a body, POST with one, unless `method` says otherwise. */
|
|
@@ -112,6 +113,14 @@ export declare const createClient: (options?: ClientOptions) => {
|
|
|
112
113
|
markRead: (id: string) => Promise<void>;
|
|
113
114
|
markAllRead: (upTo: string) => Promise<void>;
|
|
114
115
|
};
|
|
116
|
+
status: {
|
|
117
|
+
own: () => Promise<import("@protobase/schema/internal").PersonStatus | null>;
|
|
118
|
+
set: (status: import("@protobase/schema/internal").StatusInput) => Promise<import("@protobase/schema/internal").PersonStatus | null>;
|
|
119
|
+
clear: () => Promise<void>;
|
|
120
|
+
people: () => Promise<import("@protobase/schema/internal").PersonStatus[]>;
|
|
121
|
+
};
|
|
122
|
+
calls: (resource: ResourceRef, key: KeyValue | readonly KeyValue[]) => Promise<RecordCall[]>;
|
|
123
|
+
watchCalls: (resource: ResourceRef, key: KeyValue | readonly KeyValue[], handlers: RecordCallsHandlers) => () => void;
|
|
115
124
|
};
|
|
116
125
|
export type Client = ReturnType<typeof createClient>;
|
|
117
126
|
//#endregion
|
package/dist/client.js
CHANGED
|
@@ -6,7 +6,9 @@ import { followLive } from "./live.js";
|
|
|
6
6
|
import { sharedLive } from "./shared-live.js";
|
|
7
7
|
import { channelsClient } from "./channels.js";
|
|
8
8
|
import { devicesClient } from "./devices.js";
|
|
9
|
+
import { statusClient } from "./status.js";
|
|
9
10
|
import { notificationHistoryClient } from "./notification-history.js";
|
|
11
|
+
import { recordCallsClient } from "./record-calls.js";
|
|
10
12
|
import { encodeKey } from "@protobase/schema/internal";
|
|
11
13
|
//#region src/client.ts
|
|
12
14
|
const nameOf = (resource) => typeof resource === "string" ? resource : resource.toModel().name;
|
|
@@ -16,6 +18,8 @@ const channelsUrl = (baseUrl = "/api/v1") => {
|
|
|
16
18
|
return url.toString();
|
|
17
19
|
};
|
|
18
20
|
const keyPath = (resource, key) => `/${nameOf(resource)}/${encodeKey(key)}`;
|
|
21
|
+
/** Where a record's calls are read and followed. */
|
|
22
|
+
const callsPath = (resource, key) => `${keyPath(resource, key)}:calls`;
|
|
19
23
|
const toPage = (body, response, origin) => ({
|
|
20
24
|
items: body.items.map((item) => withFileOrigin(item, origin)),
|
|
21
25
|
nextPageToken: body.next_page_token,
|
|
@@ -430,6 +434,12 @@ const createClient = (options = {}) => {
|
|
|
430
434
|
socket: options.webSocket ?? ((url, protocols) => new WebSocket(url, protocols)),
|
|
431
435
|
retryDelay: (attempt) => Math.min(3e4, 1e3 * 2 ** attempt)
|
|
432
436
|
});
|
|
437
|
+
/** The signed-in user's own devices, where they receive push (see `devicesClient`). */
|
|
438
|
+
const devices = devicesClient(send);
|
|
439
|
+
/** The signed-in user's own notification history, their inbox (see `notificationHistoryClient`). */
|
|
440
|
+
const notifications = notificationHistoryClient(send);
|
|
441
|
+
/** A record's calls, live with `watchCalls`, for whoever may read the record (see `recordCallsClient`). */
|
|
442
|
+
const { calls, watchCalls } = recordCallsClient(send, callsPath);
|
|
433
443
|
return {
|
|
434
444
|
list,
|
|
435
445
|
search,
|
|
@@ -459,9 +469,12 @@ const createClient = (options = {}) => {
|
|
|
459
469
|
submitForm,
|
|
460
470
|
live,
|
|
461
471
|
channel,
|
|
462
|
-
devices
|
|
463
|
-
notifications
|
|
472
|
+
devices,
|
|
473
|
+
notifications,
|
|
474
|
+
status: statusClient(send),
|
|
475
|
+
calls,
|
|
476
|
+
watchCalls
|
|
464
477
|
};
|
|
465
478
|
};
|
|
466
479
|
//#endregion
|
|
467
|
-
export { createClient };
|
|
480
|
+
export { callsPath, createClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,12 @@ import { ClientOptions } from "./transport.js";
|
|
|
5
5
|
import { DevicesClient, RegisteredDevice, WebDeviceRegistration } from "./devices.js";
|
|
6
6
|
import { InboxNotification, InboxPage, NotificationHistoryClient } from "./notification-history.js";
|
|
7
7
|
import { FilterInput, ListParams, MapParams, filterText, listWire } from "./query-string.js";
|
|
8
|
+
import { Aggregate, AggregateGroup, AggregateParams, AggregateSplitTotal, BatchOp, BatchRef, BatchResult, CommentPage, CommentPerson, Facet, FormChallenge, FormProof, Histogram, HistogramBucket, HistoryChange, HistoryPage, ListPage, MapBounds, MapBucket, MapLayerModel, MapResult, MentionableUser, Meta, MetaResult, PluginRef, PublicForm, RecordComment, RecordOf, RecordPermissions, RecordPhotos, RelatedLabels, ResourcePermissions, ResourceRef, Series, SeriesParams, SeriesPoint, SourceRef, Stored, StoredFile, StoredFileVariant } from "./types.js";
|
|
9
|
+
import { RecordCall, RecordCallsHandlers } from "./record-calls.js";
|
|
8
10
|
import { UploadProgress } from "./upload-request.js";
|
|
9
11
|
import { LiveChange, LiveHandlers } from "./live.js";
|
|
10
|
-
import { Aggregate, AggregateGroup, AggregateParams, AggregateSplitTotal, BatchOp, BatchRef, BatchResult, CommentPage, CommentPerson, Facet, FormChallenge, FormProof, Histogram, HistogramBucket, HistoryChange, HistoryPage, ListPage, MapBounds, MapBucket, MapLayerModel, MapResult, MentionableUser, Meta, MetaResult, PluginRef, PublicForm, RecordComment, RecordOf, RecordPermissions, RecordPhotos, RelatedLabels, ResourcePermissions, ResourceRef, Series, SeriesParams, SeriesPoint, SourceRef, Stored, StoredFile, StoredFileVariant } from "./types.js";
|
|
11
12
|
import { Client, InvokeOptions, UploadOptions, UploadResult, createClient } from "./client.js";
|
|
13
|
+
import { Availability, PersonStatus, StatusClient, StatusInput } from "./status.js";
|
|
12
14
|
import { ApiError, PreconditionFailedError, ProblemDetails, ProblemError, isApiError } from "./problem.js";
|
|
13
15
|
import { fileUrl } from "./file-urls.js";
|
|
14
16
|
import { routedClient } from "./routed-client.js";
|
|
@@ -27,4 +29,5 @@ import { AuthSession, AuthSessionOptions, createAuthSession, tokenExpiry } from
|
|
|
27
29
|
import { createStaticSession } from "./static-session.js";
|
|
28
30
|
import { AssistantClient, AssistantClientOptions, AssistantConnection, createAssistantClient } from "./assistant.js";
|
|
29
31
|
import { RuntimeClient, RuntimeClientOptions, RuntimeLastUpdate, RuntimePolicy, RuntimeStatus, RuntimeUpdateStage, createRuntimeClient } from "./runtime.js";
|
|
30
|
-
|
|
32
|
+
import { availabilities, heldStatus, maxStatusText, statusTextProblem } from "@protobase/schema/internal";
|
|
33
|
+
export { type AccountSecurity, type AccountSignIn, type ActiveRole, type Aggregate, type AggregateGroup, type AggregateParams, type AggregateSplitTotal, ApiError, type AppUser, type AssistantClient, type AssistantClientOptions, type AssistantConnection, AuthError, type AuthSession, type AuthSessionOptions, type AuthUser, type AuthenticatorSetup, type Availability, type BatchOp, type BatchRef, type BatchResult, type Channel, type ChannelError, type ChannelMessage, ChannelRefusal, type ChannelStatus, type ChannelUser, type Client, type ClientOptions, type CommentPage, type CommentPerson, type ConnectClient, type ConnectedApp, type ConnectingClient, type CurrentOrganization, type DeviceSession, type DevicesClient, type Facet, type FilterInput, type FormChallenge, type FormProof, type Histogram, type HistogramBucket, type HistoryChange, type HistoryPage, type InboxNotification, type InboxPage, type InvitationPreview, type InvitationSent, type InvokeOptions, type ListPage, type ListParams, type LiveChange, type LiveHandlers, type MapBounds, type MapBucket, type MapLayerModel, type MapParams, type MapResult, type MentionableUser, type Meta, type MetaResult, type NotificationHistoryClient, type Organization, type OrganizationInvitation, type OrganizationMember, type OrganizationPerson, type OrganizationRole, type OrganizationsClient, type OrganizationsFound, type Passkey, type PersonStatus, type PlatformSignIn, type PluginRef, PreconditionFailedError, type PresenceChange, type PresenceEntry, type ProblemDetails, type ProblemError, type PublicForm, type RecordCall, type RecordCallsHandlers, type RecordComment, type RecordOf, type RecordPermissions, type RecordPhotos, type RegisteredDevice, type RelatedLabels, type RequiredSetup, type ResourcePermissions, type ResourceRef, type RoleLabel, type RoleOption, type RolesClient, type RuntimeClient, type RuntimeClientOptions, type RuntimeLastUpdate, type RuntimePolicy, type RuntimeStatus, type RuntimeUpdateStage, type SamlSignIn, type Series, type SeriesParams, type SeriesPoint, type SetupStatus, type SignInMethod, type SignInPolicy, type SignInPolicyClient, type SignInPolicyState, type SignInResult, type SignInRule, type SocketLike, type SourceMeta, type SourceRef, type StaffAccessRule, type StaffSignIn, type StaffSignInClient, type StatusClient, type StatusInput, type Stored, type StoredFile, type StoredFileVariant, type TwoFactorMethod, type UploadOptions, type UploadProgress, type UploadResult, type UserInvitation, type UserInvitationSent, type UsersClient, type UsersFound, type WebDeviceRegistration, availabilities, createAssistantClient, createAuthSession, createClient, createRuntimeClient, createStaticSession, fileUrl, filterText, heldStatus, isApiError, listWire, maxStatusText, routedClient, sourceMeta, statusTextProblem, tokenExpiry };
|
package/dist/index.js
CHANGED
|
@@ -10,4 +10,5 @@ import { createAuthSession, tokenExpiry } from "./auth.js";
|
|
|
10
10
|
import { createStaticSession } from "./static-session.js";
|
|
11
11
|
import { createAssistantClient } from "./assistant.js";
|
|
12
12
|
import { createRuntimeClient } from "./runtime.js";
|
|
13
|
-
|
|
13
|
+
import { availabilities, heldStatus, maxStatusText, statusTextProblem } from "@protobase/schema/internal";
|
|
14
|
+
export { ApiError, AuthError, ChannelRefusal, PreconditionFailedError, availabilities, createAssistantClient, createAuthSession, createClient, createRuntimeClient, createStaticSession, fileUrl, filterText, heldStatus, isApiError, listWire, maxStatusText, routedClient, sourceMeta, statusTextProblem, tokenExpiry };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "./types.js";
|
|
2
|
+
import { KeyValue } from "@protobase/schema";
|
|
3
|
+
//#region src/record-calls.d.ts
|
|
4
|
+
/** A call on a record, as its readers see it: who started it, when, when it ended, who is in it and who was removed. */
|
|
5
|
+
export type RecordCall = {
|
|
6
|
+
id: string;
|
|
7
|
+
started_by: string;
|
|
8
|
+
started_at: string;
|
|
9
|
+
/** `null` while it goes on. */
|
|
10
|
+
ended_at: string | null;
|
|
11
|
+
members: {
|
|
12
|
+
member: string;
|
|
13
|
+
joined_at: string;
|
|
14
|
+
}[];
|
|
15
|
+
removed: {
|
|
16
|
+
member: string;
|
|
17
|
+
removed_at: string;
|
|
18
|
+
}[];
|
|
19
|
+
};
|
|
20
|
+
export type RecordCallsHandlers = {
|
|
21
|
+
/** The record's latest calls, newest first, the one going on first: at once, then whenever they change. */
|
|
22
|
+
onCalls: (calls: RecordCall[]) => void;
|
|
23
|
+
/** The stream failed, with why, or ended (`undefined`); it is followed again after a pause. */
|
|
24
|
+
onRetry?: (error: unknown) => void;
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readEventStream } from "@protobase/schema/internal";
|
|
2
|
+
//#region src/record-calls.ts
|
|
3
|
+
const backoff = (attempt) => Math.min(3e4, 1e3 * 2 ** attempt);
|
|
4
|
+
const pause = (ms, signal) => new Promise((resolve) => {
|
|
5
|
+
const timer = setTimeout(resolve, ms);
|
|
6
|
+
signal.addEventListener("abort", () => {
|
|
7
|
+
clearTimeout(timer);
|
|
8
|
+
resolve();
|
|
9
|
+
}, { once: true });
|
|
10
|
+
});
|
|
11
|
+
/** A record's calls, for whoever may read the record (`GET /api/{resource}/{key}:calls`). `path` names the record's calls. */
|
|
12
|
+
const recordCallsClient = (send, path, retryDelay = backoff) => ({
|
|
13
|
+
/** The record's latest calls, newest first, the one going on first. */
|
|
14
|
+
calls: async (resource, key) => (await (await send({
|
|
15
|
+
method: "GET",
|
|
16
|
+
path: path(resource, key)
|
|
17
|
+
})).json()).items,
|
|
18
|
+
/** Follows the record's calls until the returned function is called; a stream that ends or fails is followed again. */
|
|
19
|
+
watchCalls: (resource, key, handlers) => {
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const { signal } = controller;
|
|
22
|
+
let failures = 0;
|
|
23
|
+
const follow = async () => {
|
|
24
|
+
const response = await send({
|
|
25
|
+
method: "GET",
|
|
26
|
+
path: path(resource, key),
|
|
27
|
+
query: "?watch=true",
|
|
28
|
+
headers: { accept: "text/event-stream" },
|
|
29
|
+
signal
|
|
30
|
+
});
|
|
31
|
+
if (!response.body) throw new Error("The server answered the calls stream without a body");
|
|
32
|
+
await readEventStream(response.body, (data) => {
|
|
33
|
+
failures = 0;
|
|
34
|
+
handlers.onCalls(JSON.parse(data).items);
|
|
35
|
+
}, signal);
|
|
36
|
+
};
|
|
37
|
+
(async () => {
|
|
38
|
+
while (!signal.aborted) {
|
|
39
|
+
const error = await follow().then(() => void 0, (reason) => reason);
|
|
40
|
+
if (signal.aborted) return;
|
|
41
|
+
handlers.onRetry?.(error);
|
|
42
|
+
await pause(retryDelay(failures++), signal);
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
return () => controller.abort();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
export { recordCallsClient };
|
package/dist/routed-client.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Client } from "./client.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* One client over the app's own API and the sources it draws resources from: a call about a resource goes to the API
|
|
5
5
|
* that serves it (`sourceOf` gives a source's client, or `undefined` for the app's own), and `meta`, `invoke`, the
|
|
6
|
-
* public forms, `live` and the user's devices and
|
|
6
|
+
* public forms, `live` and the user's devices, notifications and status go to the app's own. A batch goes to the one API all its writes are for; one that writes to two cannot be atomic, so it is
|
|
7
7
|
* refused before anything is sent.
|
|
8
8
|
*/
|
|
9
9
|
export declare const routedClient: (own: Client, sourceOf: (resource: string) => Client | undefined) => Client;
|
package/dist/routed-client.js
CHANGED
|
@@ -3,7 +3,7 @@ const nameOf = (resource) => typeof resource === "string" ? resource : resource.
|
|
|
3
3
|
/**
|
|
4
4
|
* One client over the app's own API and the sources it draws resources from: a call about a resource goes to the API
|
|
5
5
|
* that serves it (`sourceOf` gives a source's client, or `undefined` for the app's own), and `meta`, `invoke`, the
|
|
6
|
-
* public forms, `live` and the user's devices and
|
|
6
|
+
* public forms, `live` and the user's devices, notifications and status go to the app's own. A batch goes to the one API all its writes are for; one that writes to two cannot be atomic, so it is
|
|
7
7
|
* refused before anything is sent.
|
|
8
8
|
*/
|
|
9
9
|
const routedClient = (own, sourceOf) => {
|
|
@@ -42,7 +42,10 @@ const routedClient = (own, sourceOf) => {
|
|
|
42
42
|
live: (handlers) => own.live(handlers),
|
|
43
43
|
channel: (resource, key) => own.channel(resource, key),
|
|
44
44
|
devices: own.devices,
|
|
45
|
-
notifications: own.notifications
|
|
45
|
+
notifications: own.notifications,
|
|
46
|
+
status: own.status,
|
|
47
|
+
calls: (resource, key) => to(resource).calls(resource, key),
|
|
48
|
+
watchCalls: (resource, key, handlers) => to(resource).watchCalls(resource, key, handlers)
|
|
46
49
|
};
|
|
47
50
|
};
|
|
48
51
|
//#endregion
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Transport } from "./transport.js";
|
|
2
|
+
import { Availability, PersonStatus, PersonStatus as PersonStatus$1, StatusInput, StatusInput as StatusInput$1 } from "@protobase/schema/internal";
|
|
3
|
+
//#region src/status.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* People's own status (`/api/status`), in an app with `status: {}`: `own` the signed-in person's, `set` replaces it
|
|
6
|
+
* and answers it, `clear` removes it, and `people` the statuses of everyone who shares an organization with them, those
|
|
7
|
+
* without one left out. A status past its clear time is none. `set` rejects with an `ApiError` of slug
|
|
8
|
+
* `invalid-status-text` for a text with control characters or over `maxStatusText` characters, and
|
|
9
|
+
* `own-sign-in-only` for staff signed in as the person.
|
|
10
|
+
*/
|
|
11
|
+
export declare const statusClient: (send: Transport) => {
|
|
12
|
+
own: () => Promise<PersonStatus | null>;
|
|
13
|
+
set: (status: StatusInput) => Promise<PersonStatus | null>;
|
|
14
|
+
clear: () => Promise<void>;
|
|
15
|
+
people: () => Promise<PersonStatus[]>;
|
|
16
|
+
};
|
|
17
|
+
export type StatusClient = ReturnType<typeof statusClient>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export type { Availability, PersonStatus$1 as PersonStatus, StatusInput$1 as StatusInput };
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { statusPath } from "@protobase/schema/internal";
|
|
2
|
+
//#region src/status.ts
|
|
3
|
+
/**
|
|
4
|
+
* People's own status (`/api/status`), in an app with `status: {}`: `own` the signed-in person's, `set` replaces it
|
|
5
|
+
* and answers it, `clear` removes it, and `people` the statuses of everyone who shares an organization with them, those
|
|
6
|
+
* without one left out. A status past its clear time is none. `set` rejects with an `ApiError` of slug
|
|
7
|
+
* `invalid-status-text` for a text with control characters or over `maxStatusText` characters, and
|
|
8
|
+
* `own-sign-in-only` for staff signed in as the person.
|
|
9
|
+
*/
|
|
10
|
+
const statusClient = (send) => ({
|
|
11
|
+
own: async () => (await (await send({
|
|
12
|
+
method: "GET",
|
|
13
|
+
path: statusPath(""),
|
|
14
|
+
system: true
|
|
15
|
+
})).json()).status,
|
|
16
|
+
set: async (status) => (await (await send({
|
|
17
|
+
method: "PUT",
|
|
18
|
+
path: statusPath(""),
|
|
19
|
+
system: true,
|
|
20
|
+
body: status
|
|
21
|
+
})).json()).status,
|
|
22
|
+
clear: async () => {
|
|
23
|
+
await send({
|
|
24
|
+
method: "DELETE",
|
|
25
|
+
path: statusPath(""),
|
|
26
|
+
system: true
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
people: async () => (await (await send({
|
|
30
|
+
method: "GET",
|
|
31
|
+
path: statusPath("", "people"),
|
|
32
|
+
system: true
|
|
33
|
+
})).json()).statuses
|
|
34
|
+
});
|
|
35
|
+
//#endregion
|
|
36
|
+
export { statusClient };
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"description": "Typed fetch client for the Protobase REST API.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@better-auth/passkey": "^1.7.12",
|
|
34
|
-
"@protobase/layout": "0.
|
|
35
|
-
"@protobase/schema": "0.
|
|
34
|
+
"@protobase/layout": "0.60.0",
|
|
35
|
+
"@protobase/schema": "0.60.0",
|
|
36
36
|
"@simplewebauthn/browser": "^13.3.0",
|
|
37
37
|
"better-auth": "^1.7.12"
|
|
38
38
|
}
|