@fastrelay/js-sdk 0.1.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/README.md +134 -0
- package/dist/client.d.ts +159 -0
- package/dist/client.js +515 -0
- package/dist/error.d.ts +28 -0
- package/dist/error.js +24 -0
- package/dist/feed.d.ts +63 -0
- package/dist/feed.js +125 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/polling.d.ts +30 -0
- package/dist/polling.js +82 -0
- package/dist/realtime.d.ts +101 -0
- package/dist/realtime.js +521 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.js +3 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +77 -0
- package/dist/video-upload.d.ts +44 -0
- package/dist/video-upload.js +97 -0
- package/package.json +29 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { FastrelayApiError } from "./error.js";
|
|
2
|
+
import { FastrelayFeed } from "./feed.js";
|
|
3
|
+
import { FastrelayRealtime, } from "./realtime.js";
|
|
4
|
+
import { buildFeedActivityQuery, parseJsonSafely, resolveFeedTarget, splitFeedId, toAbsoluteUrl, } from "./utils.js";
|
|
5
|
+
const enc = encodeURIComponent;
|
|
6
|
+
export class FastrelayClient {
|
|
7
|
+
apiKey;
|
|
8
|
+
baseUrl;
|
|
9
|
+
token;
|
|
10
|
+
user;
|
|
11
|
+
realtime;
|
|
12
|
+
fetchImpl;
|
|
13
|
+
socketFactory;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.apiKey = options.apiKey;
|
|
16
|
+
this.baseUrl = options.baseUrl ?? 'http://localhost:8080';
|
|
17
|
+
this.token = options.token;
|
|
18
|
+
this.user = options.user ? { ...options.user } : undefined;
|
|
19
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
20
|
+
this.socketFactory = options.socketFactory;
|
|
21
|
+
}
|
|
22
|
+
feed(group, id) {
|
|
23
|
+
return new FastrelayFeed(this, group, id);
|
|
24
|
+
}
|
|
25
|
+
async connectUser(user, token, options = {}) {
|
|
26
|
+
const userId = user.id;
|
|
27
|
+
if (userId === null || userId === undefined || String(userId) === '') {
|
|
28
|
+
throw new TypeError('connectUser requires a user object with an id.');
|
|
29
|
+
}
|
|
30
|
+
if (token.trim() === '') {
|
|
31
|
+
throw new TypeError('connectUser requires a JWT token string.');
|
|
32
|
+
}
|
|
33
|
+
// Drop the previous user's socket before any await so a failed upsert
|
|
34
|
+
// can never leave the old session receiving events under the new token.
|
|
35
|
+
if (this.realtime) {
|
|
36
|
+
this.realtime.dispose();
|
|
37
|
+
this.realtime = undefined;
|
|
38
|
+
}
|
|
39
|
+
const previousUser = this.user;
|
|
40
|
+
const previousToken = this.token;
|
|
41
|
+
this.user = { ...user };
|
|
42
|
+
this.token = token;
|
|
43
|
+
if (options.upsertUser) {
|
|
44
|
+
try {
|
|
45
|
+
await this.request('POST', '/v1/users', {
|
|
46
|
+
body: {
|
|
47
|
+
id: userId,
|
|
48
|
+
displayName: user.displayName ?? user.name,
|
|
49
|
+
profileData: user.profileData ?? user.data,
|
|
50
|
+
...(user.role !== null && user.role !== undefined
|
|
51
|
+
? { role: user.role }
|
|
52
|
+
: {}),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.user = previousUser;
|
|
58
|
+
this.token = previousToken;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (options.realtime) {
|
|
63
|
+
this.realtime = new FastrelayRealtime({
|
|
64
|
+
client: this,
|
|
65
|
+
token,
|
|
66
|
+
tokenProvider: options.tokenProvider,
|
|
67
|
+
socketFactory: this.socketFactory,
|
|
68
|
+
});
|
|
69
|
+
this.realtime.connect();
|
|
70
|
+
}
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
disconnectUser() {
|
|
74
|
+
this.realtime?.dispose();
|
|
75
|
+
this.realtime = undefined;
|
|
76
|
+
this.user = undefined;
|
|
77
|
+
this.token = undefined;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
setToken(token) {
|
|
81
|
+
this.token = token;
|
|
82
|
+
this.realtime?.updateToken(token);
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
setBaseUrl(baseUrl) {
|
|
86
|
+
this.baseUrl = baseUrl;
|
|
87
|
+
this.realtime?.onBaseUrlChanged();
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
close() {
|
|
91
|
+
this.realtime?.dispose();
|
|
92
|
+
this.realtime = undefined;
|
|
93
|
+
}
|
|
94
|
+
// ---- capabilities & users ------------------------------------------------
|
|
95
|
+
getCapabilities(query = {}, options) {
|
|
96
|
+
return this.request('GET', '/v1/me/capabilities', {
|
|
97
|
+
query: { feed: query.feed },
|
|
98
|
+
options,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
getUser(id, options) {
|
|
102
|
+
return this.request('GET', `/v1/users/${enc(id)}`, { options });
|
|
103
|
+
}
|
|
104
|
+
updateUser(id, request, options) {
|
|
105
|
+
return this.request('PATCH', `/v1/users/${enc(id)}`, {
|
|
106
|
+
body: request,
|
|
107
|
+
options,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// ---- feeds ---------------------------------------------------------------
|
|
111
|
+
getOrCreateFeed(group, id, request = {}, options) {
|
|
112
|
+
return this.request('POST', `/v1/feeds/${enc(group)}/${enc(id)}`, {
|
|
113
|
+
body: request,
|
|
114
|
+
options,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
getFeedActivities(group, id, query, options) {
|
|
118
|
+
return this.request('GET', `/v1/feeds/${enc(group)}/${enc(id)}/activities`, {
|
|
119
|
+
query: buildFeedActivityQuery(query),
|
|
120
|
+
options,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
getNotificationFeedActivities(group, id, query, options) {
|
|
124
|
+
return this.getFeedActivities(group, id, query, options);
|
|
125
|
+
}
|
|
126
|
+
deleteFeed(group, id, options) {
|
|
127
|
+
return this.request('DELETE', `/v1/feeds/${enc(group)}/${enc(id)}`, {
|
|
128
|
+
options,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
setFeedVisibility(group, id, level, options) {
|
|
132
|
+
return this.request('PUT', `/v1/feeds/${enc(group)}/${enc(id)}/visibility`, {
|
|
133
|
+
body: { level },
|
|
134
|
+
options,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
updateFeedSettings(group, id, request, options) {
|
|
138
|
+
return this.request('PUT', `/v1/feeds/${enc(group)}/${enc(id)}/settings`, {
|
|
139
|
+
body: request,
|
|
140
|
+
options,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
addFeedMember(group, id, request, options) {
|
|
144
|
+
return this.request('POST', `/v1/feeds/${enc(group)}/${enc(id)}/members`, {
|
|
145
|
+
body: request,
|
|
146
|
+
options,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
removeFeedMember(group, id, userId, options) {
|
|
150
|
+
return this.request('DELETE', `/v1/feeds/${enc(group)}/${enc(id)}/members/${enc(userId)}`, { options });
|
|
151
|
+
}
|
|
152
|
+
listFeedMembers(group, id, query, options) {
|
|
153
|
+
return this.request('GET', `/v1/feeds/${enc(group)}/${enc(id)}/members`, {
|
|
154
|
+
query: { limit: query?.limit, cursor: query?.cursor },
|
|
155
|
+
options,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
followFeed(group, id, request, options) {
|
|
159
|
+
return this.request('POST', `/v1/feeds/${enc(group)}/${enc(id)}/follows`, {
|
|
160
|
+
body: { ...request, target: resolveFeedTarget(request.target) },
|
|
161
|
+
options,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
batchFollowFeed(group, id, request, options) {
|
|
165
|
+
return this.request('POST', `/v1/feeds/${enc(group)}/${enc(id)}/follows/batch`, {
|
|
166
|
+
body: {
|
|
167
|
+
...(request.activityCopyLimit !== undefined
|
|
168
|
+
? { activityCopyLimit: request.activityCopyLimit }
|
|
169
|
+
: {}),
|
|
170
|
+
targets: (request.targets ?? []).map(resolveFeedTarget),
|
|
171
|
+
},
|
|
172
|
+
options,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
unfollowFeed(group, id, target, { keepHistory } = {}, options) {
|
|
176
|
+
const [targetGroup, targetId] = splitFeedId(resolveFeedTarget(target));
|
|
177
|
+
return this.request('DELETE', `/v1/feeds/${enc(group)}/${enc(id)}/follows/${enc(targetGroup)}:${enc(targetId)}`, { query: { keepHistory }, options });
|
|
178
|
+
}
|
|
179
|
+
listFollowers(group, id, query, options) {
|
|
180
|
+
return this.request('GET', `/v1/feeds/${enc(group)}/${enc(id)}/followers`, {
|
|
181
|
+
query: { limit: query?.limit, cursor: query?.cursor },
|
|
182
|
+
options,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
listFollowing(group, id, query, options) {
|
|
186
|
+
return this.request('GET', `/v1/feeds/${enc(group)}/${enc(id)}/following`, {
|
|
187
|
+
query: { limit: query?.limit, cursor: query?.cursor },
|
|
188
|
+
options,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
listFollowRequests(group, id, query, options) {
|
|
192
|
+
return this.request('GET', `/v1/feeds/${enc(group)}/${enc(id)}/follow-requests`, { query: { status: query?.status }, options });
|
|
193
|
+
}
|
|
194
|
+
approveFollowRequest(group, id, requestId, options) {
|
|
195
|
+
return this.request('POST', `/v1/feeds/${enc(group)}/${enc(id)}/follow-requests/${enc(requestId)}/approve`, { options });
|
|
196
|
+
}
|
|
197
|
+
rejectFollowRequest(group, id, requestId, options) {
|
|
198
|
+
return this.request('POST', `/v1/feeds/${enc(group)}/${enc(id)}/follow-requests/${enc(requestId)}/reject`, { options });
|
|
199
|
+
}
|
|
200
|
+
// ---- activities ------------------------------------------------------
|
|
201
|
+
addActivity(request, options) {
|
|
202
|
+
return this.request('POST', '/v1/activities', { body: request, options });
|
|
203
|
+
}
|
|
204
|
+
getActivity(id, options) {
|
|
205
|
+
return this.request('GET', `/v1/activities/${enc(id)}`, { options });
|
|
206
|
+
}
|
|
207
|
+
updateActivity(id, request, options) {
|
|
208
|
+
return this.request('PATCH', `/v1/activities/${enc(id)}`, {
|
|
209
|
+
body: request,
|
|
210
|
+
options,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
deleteActivity(id, options) {
|
|
214
|
+
return this.request('DELETE', `/v1/activities/${enc(id)}`, { options });
|
|
215
|
+
}
|
|
216
|
+
batchGetActivities(requestOrIds, options) {
|
|
217
|
+
const body = Array.isArray(requestOrIds)
|
|
218
|
+
? { ids: requestOrIds }
|
|
219
|
+
: requestOrIds;
|
|
220
|
+
return this.request('POST', '/v1/activities/batch', { body, options });
|
|
221
|
+
}
|
|
222
|
+
// ---- reactions -------------------------------------------------------
|
|
223
|
+
addReaction(activityId, type, options) {
|
|
224
|
+
return this.request('POST', `/v1/activities/${enc(activityId)}/reactions`, {
|
|
225
|
+
body: { type },
|
|
226
|
+
options,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
removeReaction(activityId, reactionId, options) {
|
|
230
|
+
return this.request('DELETE', `/v1/activities/${enc(activityId)}/reactions/${enc(reactionId)}`, { options });
|
|
231
|
+
}
|
|
232
|
+
listReactions(activityId, query, options) {
|
|
233
|
+
return this.request('GET', `/v1/activities/${enc(activityId)}/reactions`, {
|
|
234
|
+
query: { type: query?.type, limit: query?.limit, cursor: query?.cursor },
|
|
235
|
+
options,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// ---- comments --------------------------------------------------------
|
|
239
|
+
addComment(activityId, request, options) {
|
|
240
|
+
return this.request('POST', `/v1/activities/${enc(activityId)}/comments`, {
|
|
241
|
+
body: {
|
|
242
|
+
text: request.text,
|
|
243
|
+
...(request.parentId !== undefined ? { parentId: request.parentId } : {}),
|
|
244
|
+
...(request.mentionedUsers !== undefined
|
|
245
|
+
? { mentionedUsers: request.mentionedUsers }
|
|
246
|
+
: {}),
|
|
247
|
+
},
|
|
248
|
+
options,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
updateComment(commentId, request, options) {
|
|
252
|
+
return this.request('PATCH', `/v1/comments/${enc(commentId)}`, {
|
|
253
|
+
body: { text: request.text },
|
|
254
|
+
options,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
deleteComment(commentId, options) {
|
|
258
|
+
return this.request('DELETE', `/v1/comments/${enc(commentId)}`, { options });
|
|
259
|
+
}
|
|
260
|
+
listComments(activityId, query, options) {
|
|
261
|
+
return this.request('GET', `/v1/activities/${enc(activityId)}/comments`, {
|
|
262
|
+
query: { sort: query?.sort, limit: query?.limit, cursor: query?.cursor },
|
|
263
|
+
options,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
listReplies(commentId, query, options) {
|
|
267
|
+
return this.request('GET', `/v1/comments/${enc(commentId)}/replies`, {
|
|
268
|
+
query: { limit: query?.limit, cursor: query?.cursor },
|
|
269
|
+
options,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
addCommentReaction(commentId, type, options) {
|
|
273
|
+
return this.request('POST', `/v1/comments/${enc(commentId)}/reactions`, {
|
|
274
|
+
body: { type },
|
|
275
|
+
options,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
removeCommentReaction(commentId, reactionId, options) {
|
|
279
|
+
return this.request('DELETE', `/v1/comments/${enc(commentId)}/reactions/${enc(reactionId)}`, { options });
|
|
280
|
+
}
|
|
281
|
+
// ---- bookmarks & pins --------------------------------------------------
|
|
282
|
+
addBookmark(activityId, options) {
|
|
283
|
+
return this.request('POST', `/v1/activities/${enc(activityId)}/bookmarks`, {
|
|
284
|
+
options,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
removeBookmark(activityId, options) {
|
|
288
|
+
return this.request('DELETE', `/v1/activities/${enc(activityId)}/bookmarks`, { options });
|
|
289
|
+
}
|
|
290
|
+
listBookmarks(query, options) {
|
|
291
|
+
return this.request('GET', '/v1/me/bookmarks', {
|
|
292
|
+
query: { limit: query?.limit, cursor: query?.cursor },
|
|
293
|
+
options,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
pinActivity(group, id, activityId, options) {
|
|
297
|
+
return this.request('POST', `/v1/feeds/${enc(group)}/${enc(id)}/activities/${enc(activityId)}/pin`, { options });
|
|
298
|
+
}
|
|
299
|
+
unpinActivity(group, id, activityId, options) {
|
|
300
|
+
return this.request('DELETE', `/v1/feeds/${enc(group)}/${enc(id)}/activities/${enc(activityId)}/pin`, { options });
|
|
301
|
+
}
|
|
302
|
+
// ---- polls -------------------------------------------------------------
|
|
303
|
+
createPoll(activityId, request, options) {
|
|
304
|
+
return this.request('POST', `/v1/activities/${enc(activityId)}/polls`, {
|
|
305
|
+
body: {
|
|
306
|
+
question: request.question,
|
|
307
|
+
options: request.options,
|
|
308
|
+
maxVotesPerUser: request.maxVotesPerUser ?? 1,
|
|
309
|
+
anonymous: request.anonymous ?? false,
|
|
310
|
+
...(request.expiresAt !== undefined
|
|
311
|
+
? {
|
|
312
|
+
expiresAt: request.expiresAt instanceof Date
|
|
313
|
+
? request.expiresAt.toISOString()
|
|
314
|
+
: request.expiresAt,
|
|
315
|
+
}
|
|
316
|
+
: {}),
|
|
317
|
+
},
|
|
318
|
+
options,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
getPollForActivity(activityId, options) {
|
|
322
|
+
return this.request('GET', `/v1/activities/${enc(activityId)}/polls`, {
|
|
323
|
+
options,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
getPoll(pollId, options) {
|
|
327
|
+
return this.request('GET', `/v1/polls/${enc(pollId)}`, { options });
|
|
328
|
+
}
|
|
329
|
+
vote(pollId, optionId, options) {
|
|
330
|
+
return this.request('POST', `/v1/polls/${enc(pollId)}/votes`, {
|
|
331
|
+
body: { optionId },
|
|
332
|
+
options,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
removeVote(pollId, options) {
|
|
336
|
+
return this.request('DELETE', `/v1/polls/${enc(pollId)}/votes`, { options });
|
|
337
|
+
}
|
|
338
|
+
// ---- files & videos ------------------------------------------------------
|
|
339
|
+
async uploadFile(data, filename, { type } = {}, options) {
|
|
340
|
+
const blob = data instanceof Blob ? data : new Blob([data]);
|
|
341
|
+
if (blob.size === 0) {
|
|
342
|
+
throw new TypeError('uploadFile requires non-empty data.');
|
|
343
|
+
}
|
|
344
|
+
if (filename.trim() === '') {
|
|
345
|
+
throw new TypeError('uploadFile requires a non-empty filename.');
|
|
346
|
+
}
|
|
347
|
+
const form = new FormData();
|
|
348
|
+
form.append('file', blob, filename);
|
|
349
|
+
if (type && type.trim() !== '') {
|
|
350
|
+
form.append('type', type.trim());
|
|
351
|
+
}
|
|
352
|
+
return this.request('POST', '/v1/files', { form, options });
|
|
353
|
+
}
|
|
354
|
+
deleteFile(fileId, options) {
|
|
355
|
+
return this.request('DELETE', `/v1/files/${enc(fileId)}`, { options });
|
|
356
|
+
}
|
|
357
|
+
createVideoUploadUrl(request, options) {
|
|
358
|
+
return this.request('POST', '/v1/videos/upload-url', {
|
|
359
|
+
body: request,
|
|
360
|
+
options,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
getVideo(videoId, options) {
|
|
364
|
+
return this.request('GET', `/v1/videos/${enc(videoId)}`, { options });
|
|
365
|
+
}
|
|
366
|
+
deleteVideo(videoId, options) {
|
|
367
|
+
return this.request('DELETE', `/v1/videos/${enc(videoId)}`, { options });
|
|
368
|
+
}
|
|
369
|
+
// ---- feedback & moderation -------------------------------------------
|
|
370
|
+
submitFeedback(activityId, type, options) {
|
|
371
|
+
if (type !== 'show_more' && type !== 'show_less') {
|
|
372
|
+
throw new TypeError("Feedback type must be 'show_more' or 'show_less'.");
|
|
373
|
+
}
|
|
374
|
+
return this.request('POST', `/v1/activities/${enc(activityId)}/feedback`, {
|
|
375
|
+
body: { type },
|
|
376
|
+
options,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
createFlag(targetType, targetId, request, options) {
|
|
380
|
+
return this.request('POST', '/v1/moderation/flags', {
|
|
381
|
+
body: {
|
|
382
|
+
targetType,
|
|
383
|
+
targetId,
|
|
384
|
+
reason: request.reason,
|
|
385
|
+
...(request.description !== undefined
|
|
386
|
+
? { description: request.description }
|
|
387
|
+
: {}),
|
|
388
|
+
},
|
|
389
|
+
options,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
deleteFlag(flagId, options) {
|
|
393
|
+
return this.request('DELETE', `/v1/moderation/flags/${enc(flagId)}`, {
|
|
394
|
+
options,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
createMute(userId, { type = 'personal', expiresAt } = {}, options) {
|
|
398
|
+
return this.request('POST', '/v1/moderation/mutes', {
|
|
399
|
+
body: {
|
|
400
|
+
userId,
|
|
401
|
+
type,
|
|
402
|
+
...(expiresAt !== undefined
|
|
403
|
+
? {
|
|
404
|
+
expiresAt: expiresAt instanceof Date ? expiresAt.toISOString() : expiresAt,
|
|
405
|
+
}
|
|
406
|
+
: {}),
|
|
407
|
+
},
|
|
408
|
+
options,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
removeMute(userId, { type = 'personal' } = {}, options) {
|
|
412
|
+
return this.request('DELETE', `/v1/moderation/mutes/${enc(userId)}`, {
|
|
413
|
+
query: { type },
|
|
414
|
+
options,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
listMutes(query = {}, options) {
|
|
418
|
+
return this.request('GET', '/v1/moderation/mutes', {
|
|
419
|
+
query: {
|
|
420
|
+
type: query.type ?? 'personal',
|
|
421
|
+
limit: query.limit,
|
|
422
|
+
cursor: query.cursor,
|
|
423
|
+
},
|
|
424
|
+
options,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
getMutedUsers(query = {}, options) {
|
|
428
|
+
return this.listMutes(query, options);
|
|
429
|
+
}
|
|
430
|
+
// ---- transport -----------------------------------------------------------
|
|
431
|
+
async request(method, path, { query, body, form, options = {}, } = {}) {
|
|
432
|
+
const authorization = this.buildAuthorization(options.auth ?? 'auto');
|
|
433
|
+
const url = toAbsoluteUrl(this.baseUrl, path, query);
|
|
434
|
+
const headers = { accept: 'application/json' };
|
|
435
|
+
if (authorization)
|
|
436
|
+
headers.authorization = authorization;
|
|
437
|
+
if (options.idempotencyKey) {
|
|
438
|
+
headers['idempotency-key'] = options.idempotencyKey;
|
|
439
|
+
}
|
|
440
|
+
Object.assign(headers, options.headers);
|
|
441
|
+
let requestBody;
|
|
442
|
+
if (form) {
|
|
443
|
+
requestBody = form; // fetch sets the multipart content-type + boundary
|
|
444
|
+
}
|
|
445
|
+
else if (body !== undefined) {
|
|
446
|
+
headers['content-type'] = 'application/json';
|
|
447
|
+
requestBody = JSON.stringify(body);
|
|
448
|
+
}
|
|
449
|
+
const response = await this.fetchImpl(url, {
|
|
450
|
+
method,
|
|
451
|
+
headers,
|
|
452
|
+
body: requestBody,
|
|
453
|
+
});
|
|
454
|
+
const text = await response.text();
|
|
455
|
+
const parsed = parseJsonSafely(text);
|
|
456
|
+
if (response.status < 200 || response.status >= 300) {
|
|
457
|
+
const parsedMap = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
458
|
+
? parsed
|
|
459
|
+
: undefined;
|
|
460
|
+
const errorPayload = parsedMap?.error;
|
|
461
|
+
const errorMap = errorPayload && typeof errorPayload === 'object'
|
|
462
|
+
? errorPayload
|
|
463
|
+
: undefined;
|
|
464
|
+
throw new FastrelayApiError({
|
|
465
|
+
message: String(errorMap?.message ??
|
|
466
|
+
parsedMap?.message ??
|
|
467
|
+
`fastrelay API request failed (${response.status} ${response.statusText}).`.trim()),
|
|
468
|
+
status: response.status,
|
|
469
|
+
code: errorMap?.code !== undefined ? String(errorMap.code) : undefined,
|
|
470
|
+
details: errorMap?.details,
|
|
471
|
+
hint: errorMap?.hint !== undefined ? String(errorMap.hint) : undefined,
|
|
472
|
+
docUrl: errorMap?.docUrl !== undefined ? String(errorMap.docUrl) : undefined,
|
|
473
|
+
requestId: parsedMap?.requestId !== undefined
|
|
474
|
+
? String(parsedMap.requestId)
|
|
475
|
+
: undefined,
|
|
476
|
+
path,
|
|
477
|
+
method,
|
|
478
|
+
rateLimit: readRateLimit(response.headers),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (parsed === null || parsed === '')
|
|
482
|
+
return null;
|
|
483
|
+
return parsed;
|
|
484
|
+
}
|
|
485
|
+
buildAuthorization(auth) {
|
|
486
|
+
switch (auth) {
|
|
487
|
+
case 'none':
|
|
488
|
+
return undefined;
|
|
489
|
+
case 'server':
|
|
490
|
+
throw new Error('Server auth is not supported: this SDK is client-only. ' +
|
|
491
|
+
'Call server endpoints from your backend.');
|
|
492
|
+
case 'user':
|
|
493
|
+
if (!this.token) {
|
|
494
|
+
throw new Error('This request requires a user token. Call connectUser() or setToken().');
|
|
495
|
+
}
|
|
496
|
+
return `Bearer ${this.token}`;
|
|
497
|
+
case 'auto':
|
|
498
|
+
return this.token ? `Bearer ${this.token}` : undefined;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function readRateLimit(headers) {
|
|
503
|
+
const limit = headers.get('x-ratelimit-limit');
|
|
504
|
+
const remaining = headers.get('x-ratelimit-remaining');
|
|
505
|
+
const reset = headers.get('x-ratelimit-reset');
|
|
506
|
+
if (limit === null && remaining === null && reset === null)
|
|
507
|
+
return undefined;
|
|
508
|
+
const parse = (value) => {
|
|
509
|
+
if (value === null)
|
|
510
|
+
return undefined;
|
|
511
|
+
const parsed = Number.parseInt(value, 10);
|
|
512
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
513
|
+
};
|
|
514
|
+
return { limit: parse(limit), remaining: parse(remaining), reset: parse(reset) };
|
|
515
|
+
}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface FastrelayRateLimit {
|
|
2
|
+
limit?: number;
|
|
3
|
+
remaining?: number;
|
|
4
|
+
reset?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class FastrelayApiError extends Error {
|
|
7
|
+
readonly status: number;
|
|
8
|
+
readonly code?: string;
|
|
9
|
+
readonly details?: unknown;
|
|
10
|
+
readonly hint?: string;
|
|
11
|
+
readonly docUrl?: string;
|
|
12
|
+
readonly requestId?: string;
|
|
13
|
+
readonly path?: string;
|
|
14
|
+
readonly method?: string;
|
|
15
|
+
readonly rateLimit?: FastrelayRateLimit;
|
|
16
|
+
constructor(args: {
|
|
17
|
+
message: string;
|
|
18
|
+
status: number;
|
|
19
|
+
code?: string;
|
|
20
|
+
details?: unknown;
|
|
21
|
+
hint?: string;
|
|
22
|
+
docUrl?: string;
|
|
23
|
+
requestId?: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
method?: string;
|
|
26
|
+
rateLimit?: FastrelayRateLimit;
|
|
27
|
+
});
|
|
28
|
+
}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class FastrelayApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
hint;
|
|
6
|
+
docUrl;
|
|
7
|
+
requestId;
|
|
8
|
+
path;
|
|
9
|
+
method;
|
|
10
|
+
rateLimit;
|
|
11
|
+
constructor(args) {
|
|
12
|
+
super(args.message);
|
|
13
|
+
this.name = 'FastrelayApiError';
|
|
14
|
+
this.status = args.status;
|
|
15
|
+
this.code = args.code;
|
|
16
|
+
this.details = args.details;
|
|
17
|
+
this.hint = args.hint;
|
|
18
|
+
this.docUrl = args.docUrl;
|
|
19
|
+
this.requestId = args.requestId;
|
|
20
|
+
this.path = args.path;
|
|
21
|
+
this.method = args.method;
|
|
22
|
+
this.rateLimit = args.rateLimit;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/feed.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { FastrelayClient, FastrelayRequestOptions } from './client.ts';
|
|
2
|
+
import type { CursorPage, FastrelayActivity, FastrelayBookmark, FastrelayComment, FastrelayFeedActivityPin, FastrelayReaction, FastrelayRealtimeEvent, FeedActivityQuery, NotificationPage } from './types.ts';
|
|
3
|
+
import type { FeedTarget } from './utils.ts';
|
|
4
|
+
export declare class FastrelayFeed {
|
|
5
|
+
readonly group: string;
|
|
6
|
+
readonly feedId: string;
|
|
7
|
+
private readonly client;
|
|
8
|
+
constructor(client: FastrelayClient, group: string, id: string);
|
|
9
|
+
get id(): string;
|
|
10
|
+
/** Subscribe to realtime events for this feed. Returns an unsubscribe fn. */
|
|
11
|
+
on(type: string, callback: (event: FastrelayRealtimeEvent) => void): () => void;
|
|
12
|
+
/** Subscribe to all realtime events for this feed. */
|
|
13
|
+
onAny(callback: (event: FastrelayRealtimeEvent) => void): () => void;
|
|
14
|
+
getOrCreate(request?: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<any>;
|
|
15
|
+
getActivities(query?: FeedActivityQuery, options?: FastrelayRequestOptions): Promise<CursorPage<FastrelayActivity>>;
|
|
16
|
+
getNotificationActivities(query?: FeedActivityQuery, options?: FastrelayRequestOptions): Promise<NotificationPage<FastrelayActivity>>;
|
|
17
|
+
getCapabilities(query?: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<any>;
|
|
18
|
+
addActivity(activity: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<FastrelayActivity>;
|
|
19
|
+
delete(options?: FastrelayRequestOptions): Promise<any>;
|
|
20
|
+
setVisibility(level: string, options?: FastrelayRequestOptions): Promise<any>;
|
|
21
|
+
updateSettings(settings: Record<string, unknown>, options?: FastrelayRequestOptions): Promise<any>;
|
|
22
|
+
addMember(userId: string, { role }?: {
|
|
23
|
+
role?: string;
|
|
24
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
25
|
+
removeMember(userId: string, options?: FastrelayRequestOptions): Promise<any>;
|
|
26
|
+
listMembers(query?: {
|
|
27
|
+
limit?: number;
|
|
28
|
+
cursor?: string;
|
|
29
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
30
|
+
follow(target: FeedTarget, { activityCopyLimit }?: {
|
|
31
|
+
activityCopyLimit?: number;
|
|
32
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
33
|
+
batchFollow(targets: FeedTarget[], { activityCopyLimit }?: {
|
|
34
|
+
activityCopyLimit?: number;
|
|
35
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
36
|
+
unfollow(target: FeedTarget, { keepHistory }?: {
|
|
37
|
+
keepHistory?: boolean;
|
|
38
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
39
|
+
listFollowers(query?: {
|
|
40
|
+
limit?: number;
|
|
41
|
+
cursor?: string;
|
|
42
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
43
|
+
listFollowing(query?: {
|
|
44
|
+
limit?: number;
|
|
45
|
+
cursor?: string;
|
|
46
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
47
|
+
listFollowRequests(query?: {
|
|
48
|
+
status?: string;
|
|
49
|
+
}, options?: FastrelayRequestOptions): Promise<any>;
|
|
50
|
+
approveFollowRequest(requestId: string, options?: FastrelayRequestOptions): Promise<any>;
|
|
51
|
+
rejectFollowRequest(requestId: string, options?: FastrelayRequestOptions): Promise<any>;
|
|
52
|
+
addReaction(activityId: string, type: string, options?: FastrelayRequestOptions): Promise<FastrelayReaction>;
|
|
53
|
+
removeReaction(activityId: string, reactionId: string, options?: FastrelayRequestOptions): Promise<void>;
|
|
54
|
+
addComment(activityId: string, request: {
|
|
55
|
+
text: string;
|
|
56
|
+
parentId?: string;
|
|
57
|
+
mentionedUsers?: string[];
|
|
58
|
+
}, options?: FastrelayRequestOptions): Promise<FastrelayComment>;
|
|
59
|
+
addBookmark(activityId: string, options?: FastrelayRequestOptions): Promise<FastrelayBookmark>;
|
|
60
|
+
removeBookmark(activityId: string, options?: FastrelayRequestOptions): Promise<void>;
|
|
61
|
+
pinActivity(activityId: string, options?: FastrelayRequestOptions): Promise<FastrelayFeedActivityPin>;
|
|
62
|
+
unpinActivity(activityId: string, options?: FastrelayRequestOptions): Promise<void>;
|
|
63
|
+
}
|