@orbitconnect/react 0.1.1 → 0.1.4
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/OrbitContext.d.ts +3 -1
- package/dist/components/chat/MessageBubble.d.ts +3 -1
- package/dist/hooks/useMessages.d.ts +3 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1705 -935
- package/dist/offline/BroadcastChannelCoordinator.d.ts +45 -0
- package/dist/offline/BrowserNetworkListener.d.ts +16 -0
- package/dist/offline/IndexedDBStorageAdapter.d.ts +27 -0
- package/dist/offline/OfflineContext.d.ts +33 -0
- package/dist/offline/OfflineQueueProvider.d.ts +15 -0
- package/dist/offline/QueueManager.d.ts +68 -0
- package/dist/offline/index.d.ts +10 -0
- package/dist/offline/types.d.ts +116 -0
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BroadcastChannelCoordinator — OrbitConnect Web SDK
|
|
3
|
+
*
|
|
4
|
+
* Coordinates offline queue operations across multiple browser tabs:
|
|
5
|
+
* - Leader election: only one tab owns the flush loop
|
|
6
|
+
* - Message state sync: when one tab sends, all tabs update
|
|
7
|
+
* - Prevents duplicate queue flushes
|
|
8
|
+
*/
|
|
9
|
+
export interface BroadcastMessage {
|
|
10
|
+
type: 'leader_claim' | 'leader_heartbeat' | 'message_sent' | 'message_failed' | 'queue_flushed';
|
|
11
|
+
tabId: string;
|
|
12
|
+
payload?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export declare class BroadcastChannelCoordinator {
|
|
15
|
+
private channel;
|
|
16
|
+
private tabId;
|
|
17
|
+
private isLeader;
|
|
18
|
+
private leaderTabId;
|
|
19
|
+
private heartbeatTimer;
|
|
20
|
+
private leaderTimeoutTimer;
|
|
21
|
+
private onBecomeLeader;
|
|
22
|
+
private onMessageSent;
|
|
23
|
+
private static readonly CHANNEL_NAME;
|
|
24
|
+
private static readonly HEARTBEAT_INTERVAL;
|
|
25
|
+
private static readonly LEADER_TIMEOUT;
|
|
26
|
+
constructor();
|
|
27
|
+
/** Start coordination — attempts leader election */
|
|
28
|
+
start(opts: {
|
|
29
|
+
onBecomeLeader: () => void;
|
|
30
|
+
onMessageSent?: (localId: string, serverId: string) => void;
|
|
31
|
+
}): void;
|
|
32
|
+
/** Stop coordination */
|
|
33
|
+
stop(): void;
|
|
34
|
+
/** Whether this tab is the current leader (owns flush loop) */
|
|
35
|
+
get isCurrentLeader(): boolean;
|
|
36
|
+
/** Broadcast that a message was sent (so other tabs can update their UI) */
|
|
37
|
+
broadcastMessageSent(localId: string, serverId: string): void;
|
|
38
|
+
/** Broadcast that a message failed */
|
|
39
|
+
broadcastMessageFailed(localId: string, error: string): void;
|
|
40
|
+
private claimLeadership;
|
|
41
|
+
private becomeLeader;
|
|
42
|
+
private handleMessage;
|
|
43
|
+
private resetLeaderTimeout;
|
|
44
|
+
private broadcast;
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrowserNetworkListener — OrbitConnect Web SDK
|
|
3
|
+
*
|
|
4
|
+
* Monitors browser online/offline state using the Navigator API
|
|
5
|
+
* and window events. Triggers queue flush on reconnect.
|
|
6
|
+
*/
|
|
7
|
+
import type { NetworkListener } from './types';
|
|
8
|
+
export declare class BrowserNetworkListener implements NetworkListener {
|
|
9
|
+
private onOnlineCb;
|
|
10
|
+
private onOfflineCb;
|
|
11
|
+
private boundOnline;
|
|
12
|
+
private boundOffline;
|
|
13
|
+
start(onOnline: () => void, onOffline: () => void): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
isOnline(): boolean;
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB Storage Adapter — OrbitConnect Web SDK
|
|
3
|
+
*
|
|
4
|
+
* Persists the offline message queue to IndexedDB for:
|
|
5
|
+
* - Page refresh survival
|
|
6
|
+
* - Browser restart recovery
|
|
7
|
+
* - Tab crash recovery
|
|
8
|
+
*/
|
|
9
|
+
import type { OrbitOfflineMessage, OfflineStorageAdapter } from './types';
|
|
10
|
+
export declare class IndexedDBStorageAdapter implements OfflineStorageAdapter {
|
|
11
|
+
private db;
|
|
12
|
+
open(): Promise<void>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
enqueue(message: OrbitOfflineMessage): Promise<void>;
|
|
15
|
+
update(localId: string, updates: Partial<OrbitOfflineMessage>): Promise<void>;
|
|
16
|
+
remove(localId: string): Promise<void>;
|
|
17
|
+
getPending(): Promise<OrbitOfflineMessage[]>;
|
|
18
|
+
getByConversation(conversationId: string): Promise<OrbitOfflineMessage[]>;
|
|
19
|
+
findByIdempotencyKey(key: string): Promise<OrbitOfflineMessage | undefined>;
|
|
20
|
+
clearFailed(conversationId?: string): Promise<void>;
|
|
21
|
+
getCounts(): Promise<{
|
|
22
|
+
pending: number;
|
|
23
|
+
failed: number;
|
|
24
|
+
sending: number;
|
|
25
|
+
}>;
|
|
26
|
+
private getDb;
|
|
27
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OfflineContext — React context for the offline queue system.
|
|
3
|
+
*/
|
|
4
|
+
import type { QueueManager } from './QueueManager';
|
|
5
|
+
import type { OrbitOfflineMessage, QueueSendOptions } from './types';
|
|
6
|
+
export interface OfflineQueueContextValue {
|
|
7
|
+
/** The queue manager instance */
|
|
8
|
+
queueManager: QueueManager;
|
|
9
|
+
/** Whether the device/browser is currently online */
|
|
10
|
+
isOnline: boolean;
|
|
11
|
+
/** Enqueue a message (persist + optimistic + attempt send) */
|
|
12
|
+
enqueue: (opts: QueueSendOptions) => Promise<OrbitOfflineMessage>;
|
|
13
|
+
/** Retry a specific failed message */
|
|
14
|
+
retryMessage: (localId: string) => Promise<void>;
|
|
15
|
+
/** Retry all failed messages */
|
|
16
|
+
retryAll: () => Promise<void>;
|
|
17
|
+
/** Pause the queue */
|
|
18
|
+
pauseQueue: () => void;
|
|
19
|
+
/** Resume the queue */
|
|
20
|
+
resumeQueue: () => void;
|
|
21
|
+
/** Get pending messages for a conversation */
|
|
22
|
+
getPendingMessages: (conversationId?: string) => Promise<OrbitOfflineMessage[]>;
|
|
23
|
+
/** Clear all failed messages */
|
|
24
|
+
clearFailedMessages: (conversationId?: string) => Promise<void>;
|
|
25
|
+
/** Get queue status counts */
|
|
26
|
+
getQueueStatus: () => Promise<{
|
|
27
|
+
pending: number;
|
|
28
|
+
failed: number;
|
|
29
|
+
sending: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare const OfflineQueueContext: import("react").Context<OfflineQueueContextValue | null>;
|
|
33
|
+
export declare function useOfflineQueue(): OfflineQueueContextValue;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OfflineQueueProvider — React Web SDK
|
|
3
|
+
*
|
|
4
|
+
* Manages the offline queue lifecycle:
|
|
5
|
+
* - Initializes IndexedDB storage
|
|
6
|
+
* - Creates QueueManager
|
|
7
|
+
* - Wires network listener (browser online/offline events)
|
|
8
|
+
* - Wires socket status listener (flush on reconnect)
|
|
9
|
+
* - Provides BroadcastChannel multi-tab coordination
|
|
10
|
+
* - Exposes queue operations via context
|
|
11
|
+
*/
|
|
12
|
+
import { type ReactNode } from 'react';
|
|
13
|
+
export declare function OfflineQueueProvider({ children }: {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueueManager — Core offline queue engine for OrbitConnect SDK.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Enqueue messages with localId generation
|
|
6
|
+
* - Maintain FIFO order per conversation
|
|
7
|
+
* - Retry failed sends with exponential backoff
|
|
8
|
+
* - Handle ACK reconciliation (match serverId to localId)
|
|
9
|
+
* - Recover pending/failed items after reconnect
|
|
10
|
+
* - Remove sent items from persistent storage
|
|
11
|
+
* - Emit SDK events for each state transition
|
|
12
|
+
*/
|
|
13
|
+
import type { OrbitOfflineMessage, OfflineStorageAdapter, QueueEventType, QueueEventHandler, QueueSendOptions } from './types';
|
|
14
|
+
export type SendFn = (msg: OrbitOfflineMessage) => Promise<{
|
|
15
|
+
id: string;
|
|
16
|
+
sequence_number: number;
|
|
17
|
+
}>;
|
|
18
|
+
export declare class QueueManager {
|
|
19
|
+
private storage;
|
|
20
|
+
private sendFn;
|
|
21
|
+
private listeners;
|
|
22
|
+
private retryTimers;
|
|
23
|
+
private flushing;
|
|
24
|
+
private paused;
|
|
25
|
+
private online;
|
|
26
|
+
constructor(storage: OfflineStorageAdapter);
|
|
27
|
+
/** Set the send function (provided by the SDK layer with apiClient access) */
|
|
28
|
+
setSendFn(fn: SendFn): void;
|
|
29
|
+
/** Set network status */
|
|
30
|
+
setOnline(online: boolean): void;
|
|
31
|
+
/** Restore queue from storage — call on initialization */
|
|
32
|
+
restore(): Promise<OrbitOfflineMessage[]>;
|
|
33
|
+
/** Enqueue a new message — persists locally and attempts send */
|
|
34
|
+
enqueue(opts: QueueSendOptions): Promise<OrbitOfflineMessage>;
|
|
35
|
+
/** Retry a specific failed message */
|
|
36
|
+
retryMessage(localId: string): Promise<void>;
|
|
37
|
+
/** Retry all failed messages */
|
|
38
|
+
retryAll(): Promise<void>;
|
|
39
|
+
/** Flush all pending messages (queued status) */
|
|
40
|
+
flushPending(): Promise<void>;
|
|
41
|
+
/** Mark a message as sent (called when server ACK arrives via WS) */
|
|
42
|
+
markSentByIdempotencyKey(idempotencyKey: string, serverId: string): Promise<boolean>;
|
|
43
|
+
/** Pause the queue — stops all sends and retries */
|
|
44
|
+
pause(): void;
|
|
45
|
+
/** Resume the queue — triggers flush */
|
|
46
|
+
resume(): void;
|
|
47
|
+
/** Get all pending messages for a conversation */
|
|
48
|
+
getPendingMessages(conversationId?: string): Promise<OrbitOfflineMessage[]>;
|
|
49
|
+
/** Clear all failed messages */
|
|
50
|
+
clearFailed(conversationId?: string): Promise<void>;
|
|
51
|
+
/** Get queue status counts */
|
|
52
|
+
getStatus(): Promise<{
|
|
53
|
+
pending: number;
|
|
54
|
+
failed: number;
|
|
55
|
+
sending: number;
|
|
56
|
+
}>;
|
|
57
|
+
/** Subscribe to queue events */
|
|
58
|
+
on<K extends QueueEventType>(event: K, handler: QueueEventHandler<K>): () => void;
|
|
59
|
+
/** Unsubscribe from queue events */
|
|
60
|
+
off<K extends QueueEventType>(event: K, handler: QueueEventHandler<K>): void;
|
|
61
|
+
/** Destroy — clear all timers */
|
|
62
|
+
destroy(): void;
|
|
63
|
+
private emit;
|
|
64
|
+
/** Fire-and-forget send attempt (used for immediate sends after enqueue) */
|
|
65
|
+
private attemptSend;
|
|
66
|
+
/** Synchronous send attempt — returns true if sent successfully */
|
|
67
|
+
private attemptSendSync;
|
|
68
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline Queue — Public API
|
|
3
|
+
*/
|
|
4
|
+
export { OfflineQueueProvider } from './OfflineQueueProvider';
|
|
5
|
+
export { useOfflineQueue } from './OfflineContext';
|
|
6
|
+
export { QueueManager } from './QueueManager';
|
|
7
|
+
export { IndexedDBStorageAdapter } from './IndexedDBStorageAdapter';
|
|
8
|
+
export { BrowserNetworkListener } from './BrowserNetworkListener';
|
|
9
|
+
export { BroadcastChannelCoordinator } from './BroadcastChannelCoordinator';
|
|
10
|
+
export type { OrbitOfflineMessage, OfflineStorageAdapter, OfflineMessageStatus, QueueEventType, QueueEventPayloads, QueueEventHandler, NetworkListener, QueueSendOptions, } from './types';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline Queue Types — OrbitConnect SDK
|
|
3
|
+
*
|
|
4
|
+
* Shared type definitions for the offline messaging system.
|
|
5
|
+
* Used by both React Web and React Native SDKs.
|
|
6
|
+
*/
|
|
7
|
+
/** Message status in the offline queue lifecycle */
|
|
8
|
+
export type OfflineMessageStatus = 'queued' | 'sending' | 'sent' | 'failed';
|
|
9
|
+
/** Offline message model — persisted locally before sending */
|
|
10
|
+
export interface OrbitOfflineMessage {
|
|
11
|
+
/** Client-generated UUID — used for optimistic rendering and dedup */
|
|
12
|
+
localId: string;
|
|
13
|
+
/** Server-assigned UUID — populated after successful send */
|
|
14
|
+
serverId?: string;
|
|
15
|
+
/** Target conversation */
|
|
16
|
+
conversationId: string;
|
|
17
|
+
/** Sender app_user ID */
|
|
18
|
+
senderId: string;
|
|
19
|
+
/** Message content (text or encrypted) */
|
|
20
|
+
body: string | null;
|
|
21
|
+
/** Current queue status */
|
|
22
|
+
status: OfflineMessageStatus;
|
|
23
|
+
/** Number of send attempts */
|
|
24
|
+
retries: number;
|
|
25
|
+
/** Whether the message has been confirmed by the server */
|
|
26
|
+
synced: boolean;
|
|
27
|
+
/** Client-side creation timestamp (epoch ms) */
|
|
28
|
+
createdAt: number;
|
|
29
|
+
/** Last status change timestamp (epoch ms) */
|
|
30
|
+
updatedAt: number;
|
|
31
|
+
/** Idempotency key sent to backend */
|
|
32
|
+
idempotencyKey: string;
|
|
33
|
+
/** Message type matching backend enum */
|
|
34
|
+
type: 'text' | 'media' | 'system';
|
|
35
|
+
/** Optional metadata (reply_to, attachment_type, e2e flags, etc.) */
|
|
36
|
+
metadata?: Record<string, unknown>;
|
|
37
|
+
/** Media file ID (if attachment was already uploaded) */
|
|
38
|
+
mediaId?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Storage adapter interface — implemented per platform */
|
|
41
|
+
export interface OfflineStorageAdapter {
|
|
42
|
+
/** Open/initialize the storage backend */
|
|
43
|
+
open(): Promise<void>;
|
|
44
|
+
/** Close the storage backend */
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
/** Persist a message to the queue */
|
|
47
|
+
enqueue(message: OrbitOfflineMessage): Promise<void>;
|
|
48
|
+
/** Update an existing queued message */
|
|
49
|
+
update(localId: string, updates: Partial<OrbitOfflineMessage>): Promise<void>;
|
|
50
|
+
/** Remove a message from the queue */
|
|
51
|
+
remove(localId: string): Promise<void>;
|
|
52
|
+
/** Get all pending messages (queued + failed), ordered by createdAt */
|
|
53
|
+
getPending(): Promise<OrbitOfflineMessage[]>;
|
|
54
|
+
/** Get all messages for a specific conversation */
|
|
55
|
+
getByConversation(conversationId: string): Promise<OrbitOfflineMessage[]>;
|
|
56
|
+
/** Find a message by its idempotency key */
|
|
57
|
+
findByIdempotencyKey(key: string): Promise<OrbitOfflineMessage | undefined>;
|
|
58
|
+
/** Clear all failed messages, optionally scoped to a conversation */
|
|
59
|
+
clearFailed(conversationId?: string): Promise<void>;
|
|
60
|
+
/** Get queue counts by status */
|
|
61
|
+
getCounts(): Promise<{
|
|
62
|
+
pending: number;
|
|
63
|
+
failed: number;
|
|
64
|
+
sending: number;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
/** Queue event types emitted by the QueueManager */
|
|
68
|
+
export type QueueEventType = 'messageQueued' | 'messageSending' | 'messageSent' | 'messageFailed' | 'queueFlushed' | 'networkRestored' | 'networkLost';
|
|
69
|
+
/** Payload for queue events */
|
|
70
|
+
export interface QueueEventPayloads {
|
|
71
|
+
messageQueued: {
|
|
72
|
+
localId: string;
|
|
73
|
+
conversationId: string;
|
|
74
|
+
};
|
|
75
|
+
messageSending: {
|
|
76
|
+
localId: string;
|
|
77
|
+
attempt: number;
|
|
78
|
+
};
|
|
79
|
+
messageSent: {
|
|
80
|
+
localId: string;
|
|
81
|
+
serverId: string;
|
|
82
|
+
conversationId: string;
|
|
83
|
+
};
|
|
84
|
+
messageFailed: {
|
|
85
|
+
localId: string;
|
|
86
|
+
error: string;
|
|
87
|
+
retries: number;
|
|
88
|
+
conversationId: string;
|
|
89
|
+
};
|
|
90
|
+
queueFlushed: {
|
|
91
|
+
count: number;
|
|
92
|
+
conversationId?: string;
|
|
93
|
+
};
|
|
94
|
+
networkRestored: undefined;
|
|
95
|
+
networkLost: undefined;
|
|
96
|
+
}
|
|
97
|
+
/** Queue event handler */
|
|
98
|
+
export type QueueEventHandler<K extends QueueEventType> = (payload: QueueEventPayloads[K]) => void;
|
|
99
|
+
/** Network status listener interface */
|
|
100
|
+
export interface NetworkListener {
|
|
101
|
+
/** Start listening for network changes */
|
|
102
|
+
start(onOnline: () => void, onOffline: () => void): void;
|
|
103
|
+
/** Stop listening */
|
|
104
|
+
stop(): void;
|
|
105
|
+
/** Current online status */
|
|
106
|
+
isOnline(): boolean;
|
|
107
|
+
}
|
|
108
|
+
/** Options for sending a message through the offline queue */
|
|
109
|
+
export interface QueueSendOptions {
|
|
110
|
+
conversationId: string;
|
|
111
|
+
senderId: string;
|
|
112
|
+
body: string | null;
|
|
113
|
+
type?: 'text' | 'media' | 'system';
|
|
114
|
+
metadata?: Record<string, unknown>;
|
|
115
|
+
mediaId?: string;
|
|
116
|
+
}
|
package/package.json
CHANGED