@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/dist/feed.js ADDED
@@ -0,0 +1,125 @@
1
+ export class FastrelayFeed {
2
+ group;
3
+ feedId;
4
+ client;
5
+ constructor(client, group, id) {
6
+ this.client = client;
7
+ this.group = group.trim();
8
+ this.feedId = id.trim();
9
+ if (this.group === '' || this.feedId === '') {
10
+ throw new TypeError('feed(group, id) requires both group and id.');
11
+ }
12
+ }
13
+ get id() {
14
+ return `${this.group}:${this.feedId}`;
15
+ }
16
+ /** Subscribe to realtime events for this feed. Returns an unsubscribe fn. */
17
+ on(type, callback) {
18
+ const realtime = this.client.realtime;
19
+ if (!realtime) {
20
+ throw new Error('Realtime is not initialized. Call connectUser(..., {realtime: true}).');
21
+ }
22
+ const normalizedType = type.trim();
23
+ if (normalizedType === '') {
24
+ throw new TypeError('Event type must not be empty.');
25
+ }
26
+ return realtime.subscribeToFeed(this.id, callback, { type: normalizedType });
27
+ }
28
+ /** Subscribe to all realtime events for this feed. */
29
+ onAny(callback) {
30
+ const realtime = this.client.realtime;
31
+ if (!realtime) {
32
+ throw new Error('Realtime is not initialized. Call connectUser(..., {realtime: true}).');
33
+ }
34
+ return realtime.subscribeToFeed(this.id, callback);
35
+ }
36
+ getOrCreate(request = {}, options) {
37
+ return this.client.getOrCreateFeed(this.group, this.feedId, request, options);
38
+ }
39
+ getActivities(query, options) {
40
+ return this.client.getFeedActivities(this.group, this.feedId, query, options);
41
+ }
42
+ getNotificationActivities(query, options) {
43
+ return this.client.getNotificationFeedActivities(this.group, this.feedId, query, options);
44
+ }
45
+ getCapabilities(query, options) {
46
+ return this.client.getCapabilities({ feed: this.id, ...query }, options);
47
+ }
48
+ addActivity(activity, options) {
49
+ const feeds = Array.isArray(activity.feeds)
50
+ ? activity.feeds.map((entry) => String(entry))
51
+ : [];
52
+ if (!feeds.includes(this.id))
53
+ feeds.push(this.id);
54
+ return this.client.addActivity({ ...activity, feeds }, options);
55
+ }
56
+ delete(options) {
57
+ return this.client.deleteFeed(this.group, this.feedId, options);
58
+ }
59
+ setVisibility(level, options) {
60
+ return this.client.setFeedVisibility(this.group, this.feedId, level, options);
61
+ }
62
+ updateSettings(settings, options) {
63
+ return this.client.updateFeedSettings(this.group, this.feedId, settings, options);
64
+ }
65
+ addMember(userId, { role = 'member' } = {}, options) {
66
+ return this.client.addFeedMember(this.group, this.feedId, { userId, role }, options);
67
+ }
68
+ removeMember(userId, options) {
69
+ return this.client.removeFeedMember(this.group, this.feedId, userId, options);
70
+ }
71
+ listMembers(query, options) {
72
+ return this.client.listFeedMembers(this.group, this.feedId, query, options);
73
+ }
74
+ follow(target, { activityCopyLimit } = {}, options) {
75
+ return this.client.followFeed(this.group, this.feedId, {
76
+ target,
77
+ ...(activityCopyLimit !== undefined ? { activityCopyLimit } : {}),
78
+ }, options);
79
+ }
80
+ batchFollow(targets, { activityCopyLimit } = {}, options) {
81
+ return this.client.batchFollowFeed(this.group, this.feedId, {
82
+ targets,
83
+ ...(activityCopyLimit !== undefined ? { activityCopyLimit } : {}),
84
+ }, options);
85
+ }
86
+ unfollow(target, { keepHistory } = {}, options) {
87
+ return this.client.unfollowFeed(this.group, this.feedId, target, { keepHistory }, options);
88
+ }
89
+ listFollowers(query, options) {
90
+ return this.client.listFollowers(this.group, this.feedId, query, options);
91
+ }
92
+ listFollowing(query, options) {
93
+ return this.client.listFollowing(this.group, this.feedId, query, options);
94
+ }
95
+ listFollowRequests(query, options) {
96
+ return this.client.listFollowRequests(this.group, this.feedId, query, options);
97
+ }
98
+ approveFollowRequest(requestId, options) {
99
+ return this.client.approveFollowRequest(this.group, this.feedId, requestId, options);
100
+ }
101
+ rejectFollowRequest(requestId, options) {
102
+ return this.client.rejectFollowRequest(this.group, this.feedId, requestId, options);
103
+ }
104
+ addReaction(activityId, type, options) {
105
+ return this.client.addReaction(activityId, type, options);
106
+ }
107
+ removeReaction(activityId, reactionId, options) {
108
+ return this.client.removeReaction(activityId, reactionId, options);
109
+ }
110
+ addComment(activityId, request, options) {
111
+ return this.client.addComment(activityId, request, options);
112
+ }
113
+ addBookmark(activityId, options) {
114
+ return this.client.addBookmark(activityId, options);
115
+ }
116
+ removeBookmark(activityId, options) {
117
+ return this.client.removeBookmark(activityId, options);
118
+ }
119
+ pinActivity(activityId, options) {
120
+ return this.client.pinActivity(this.group, this.feedId, activityId, options);
121
+ }
122
+ unpinActivity(activityId, options) {
123
+ return this.client.unpinActivity(this.group, this.feedId, activityId, options);
124
+ }
125
+ }
@@ -0,0 +1,8 @@
1
+ export { FastrelayClient, type ConnectUserOptions, type FastrelayAuthMode, type FastrelayClientOptions, type FastrelayRequestOptions, } from './client.ts';
2
+ export { FastrelayApiError, type FastrelayRateLimit } from './error.ts';
3
+ export { FastrelayFeed } from './feed.ts';
4
+ export { FeedPollingService } from './polling.ts';
5
+ export { FastrelayRealtime, type FastrelayRealtimeOptions, type FastrelayRealtimeSocket, type FastrelayRealtimeSocketFactory, type FastrelayTokenProvider, } from './realtime.ts';
6
+ export * from './types.ts';
7
+ export { buildFeedActivityQuery, resolveFeedTarget, splitFeedId, type FeedTarget, } from './utils.ts';
8
+ export { FastrelayVideoUploadError, tusUploadBytes, uploadVideoBytes, type FastrelayVideoUploadProgress, type FastrelayVideoUploadResult, } from './video-upload.ts';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { FastrelayClient, } from "./client.js";
2
+ export { FastrelayApiError } from "./error.js";
3
+ export { FastrelayFeed } from "./feed.js";
4
+ export { FeedPollingService } from "./polling.js";
5
+ export { FastrelayRealtime, } from "./realtime.js";
6
+ export * from "./types.js";
7
+ export { buildFeedActivityQuery, resolveFeedTarget, splitFeedId, } from "./utils.js";
8
+ export { FastrelayVideoUploadError, tusUploadBytes, uploadVideoBytes, } from "./video-upload.js";
@@ -0,0 +1,30 @@
1
+ import type { FastrelayClient } from './client.ts';
2
+ import type { CursorPage, FastrelayActivity, NotificationPage } from './types.ts';
3
+ interface PollerHandlers<T> {
4
+ limit?: number;
5
+ onPage: (page: T) => void;
6
+ onError?: (error: unknown) => void;
7
+ }
8
+ /**
9
+ * Polling fallback for environments without realtime. Each pollFeed /
10
+ * pollNotifications call starts an independent poller; call the returned
11
+ * function (or dispose()) to stop it.
12
+ */
13
+ export declare class FeedPollingService {
14
+ private readonly client;
15
+ private readonly activeIntervalMs;
16
+ private readonly backgroundIntervalMs;
17
+ private readonly pollers;
18
+ private paused;
19
+ constructor(client: FastrelayClient, { activeIntervalMs, backgroundIntervalMs, }?: {
20
+ activeIntervalMs?: number;
21
+ backgroundIntervalMs?: number;
22
+ });
23
+ pollFeed(group: string, id: string, { limit, onPage, onError }: PollerHandlers<CursorPage<FastrelayActivity>>): () => void;
24
+ pollNotifications(group: string, id: string, { limit, onPage, onError, }: PollerHandlers<NotificationPage<FastrelayActivity>>): () => void;
25
+ pause(): void;
26
+ resume(): void;
27
+ dispose(): void;
28
+ private startPoller;
29
+ }
30
+ export {};
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Polling fallback for environments without realtime. Each pollFeed /
3
+ * pollNotifications call starts an independent poller; call the returned
4
+ * function (or dispose()) to stop it.
5
+ */
6
+ export class FeedPollingService {
7
+ client;
8
+ activeIntervalMs;
9
+ backgroundIntervalMs;
10
+ pollers = new Set();
11
+ paused = false;
12
+ constructor(client, { activeIntervalMs = 15_000, backgroundIntervalMs = 60_000, } = {}) {
13
+ this.client = client;
14
+ this.activeIntervalMs = activeIntervalMs;
15
+ this.backgroundIntervalMs = backgroundIntervalMs;
16
+ }
17
+ pollFeed(group, id, { limit = 25, onPage, onError }) {
18
+ return this.startPoller(() => this.client.getFeedActivities(group, id, { limit }), onPage, onError);
19
+ }
20
+ pollNotifications(group, id, { limit = 25, onPage, onError, }) {
21
+ return this.startPoller(() => this.client.getNotificationFeedActivities(group, id, { limit }), onPage, onError);
22
+ }
23
+ pause() {
24
+ this.paused = true;
25
+ for (const poller of this.pollers) {
26
+ poller.setInterval(this.backgroundIntervalMs);
27
+ }
28
+ }
29
+ resume() {
30
+ this.paused = false;
31
+ for (const poller of this.pollers) {
32
+ poller.setInterval(this.activeIntervalMs);
33
+ }
34
+ }
35
+ dispose() {
36
+ for (const poller of this.pollers)
37
+ poller.stop();
38
+ this.pollers.clear();
39
+ }
40
+ startPoller(fetchPage, onPage, onError) {
41
+ let timer = null;
42
+ let stopped = false;
43
+ let inFlight = false;
44
+ const poll = async () => {
45
+ if (stopped || inFlight)
46
+ return;
47
+ inFlight = true;
48
+ try {
49
+ const page = await fetchPage();
50
+ if (!stopped)
51
+ onPage(page);
52
+ }
53
+ catch (error) {
54
+ if (!stopped)
55
+ onError?.(error);
56
+ }
57
+ finally {
58
+ inFlight = false;
59
+ }
60
+ };
61
+ const poller = {
62
+ setInterval: (ms) => {
63
+ if (timer !== null)
64
+ clearInterval(timer);
65
+ timer = setInterval(() => void poll(), ms);
66
+ },
67
+ stop: () => {
68
+ stopped = true;
69
+ if (timer !== null)
70
+ clearInterval(timer);
71
+ timer = null;
72
+ },
73
+ };
74
+ poller.setInterval(this.paused ? this.backgroundIntervalMs : this.activeIntervalMs);
75
+ void poll();
76
+ this.pollers.add(poller);
77
+ return () => {
78
+ poller.stop();
79
+ this.pollers.delete(poller);
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,101 @@
1
+ import type { FastrelayClient } from './client.ts';
2
+ import type { FastrelayConnectionState, FastrelayRealtimeError, FastrelayRealtimeEvent, FastrelayVideoStatusEvent } from './types.ts';
3
+ export type FastrelayTokenProvider = () => Promise<string> | string;
4
+ /** Minimal browser-WebSocket-shaped surface, injectable for tests / Node < 22. */
5
+ export interface FastrelayRealtimeSocket {
6
+ send(data: string): void;
7
+ close(): void;
8
+ onopen: ((event?: unknown) => void) | null;
9
+ onmessage: ((event: {
10
+ data: unknown;
11
+ }) => void) | null;
12
+ onclose: ((event: {
13
+ code?: number;
14
+ reason?: string;
15
+ }) => void) | null;
16
+ onerror: ((event?: unknown) => void) | null;
17
+ }
18
+ export type FastrelayRealtimeSocketFactory = (url: string) => FastrelayRealtimeSocket;
19
+ export interface FastrelayRealtimeOptions {
20
+ client: FastrelayClient;
21
+ token: string;
22
+ tokenProvider?: FastrelayTokenProvider;
23
+ socketFactory?: FastrelayRealtimeSocketFactory;
24
+ subscribeDebounceMs?: number;
25
+ deadConnectionTimeoutMs?: number;
26
+ reconnectInitialDelayMs?: number;
27
+ reconnectMaxDelayMs?: number;
28
+ }
29
+ type Unsubscribe = () => void;
30
+ export declare class FastrelayRealtime {
31
+ private readonly client;
32
+ private token;
33
+ private readonly tokenProvider?;
34
+ private readonly socketFactory;
35
+ private readonly subscribeDebounceMs;
36
+ private readonly deadConnectionTimeoutMs;
37
+ private readonly reconnectInitialDelayMs;
38
+ private readonly reconnectMaxDelayMs;
39
+ private readonly feedListeners;
40
+ private readonly acknowledgedFeeds;
41
+ private readonly pendingSubscribes;
42
+ private readonly pendingUnsubscribes;
43
+ private readonly recentEventIds;
44
+ private readonly eventListeners;
45
+ private readonly stateListeners;
46
+ private readonly errorListeners;
47
+ private readonly videoListeners;
48
+ private socket;
49
+ private subscribeBatchTimer;
50
+ private reconnectTimer;
51
+ private deadConnectionTimer;
52
+ private lastServerMessageAt;
53
+ private state;
54
+ private shouldBeConnected;
55
+ private disposed;
56
+ private awaitingTokenRefresh;
57
+ private hasConnectedAtLeastOnce;
58
+ private generation;
59
+ private reconnectAttempt;
60
+ constructor(options: FastrelayRealtimeOptions);
61
+ get connectionState(): FastrelayConnectionState;
62
+ onEvent(callback: (event: FastrelayRealtimeEvent) => void): Unsubscribe;
63
+ onStateChange(callback: (state: FastrelayConnectionState) => void): Unsubscribe;
64
+ onError(callback: (error: FastrelayRealtimeError) => void): Unsubscribe;
65
+ onVideoStatus(callback: (event: FastrelayVideoStatusEvent) => void): Unsubscribe;
66
+ /**
67
+ * Listen to events for one feed. Subscribes over the socket on the first
68
+ * listener and unsubscribes when the last one is removed.
69
+ */
70
+ subscribeToFeed(feedId: string, callback: (event: FastrelayRealtimeEvent) => void, { type }?: {
71
+ type?: string;
72
+ }): Unsubscribe;
73
+ updateToken(token: string): void;
74
+ onBaseUrlChanged(): void;
75
+ connect(): void;
76
+ disconnect({ clearSubscriptions }?: {
77
+ clearSubscriptions?: boolean | undefined;
78
+ }): void;
79
+ dispose(): void;
80
+ private openSocket;
81
+ private onConnected;
82
+ private onDisconnected;
83
+ private handleRawMessage;
84
+ private dispatchEvent;
85
+ private emitControlError;
86
+ private trackEventId;
87
+ private scheduleSubscribeBatch;
88
+ private flushSubscriptionBatch;
89
+ private send;
90
+ private setState;
91
+ private emitError;
92
+ private startDeadConnectionMonitor;
93
+ private stopDeadConnectionMonitor;
94
+ private scheduleReconnect;
95
+ private nextReconnectDelay;
96
+ private cancelReconnectTimer;
97
+ private cancelSubscribeBatch;
98
+ private closeSocketResources;
99
+ private refreshToken;
100
+ }
101
+ export {};