@shamilovtim/react-native-nitro-sse 2.3.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/NitroSse.podspec +37 -0
  3. package/README.md +144 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +124 -0
  6. package/android/src/main/AndroidManifest.xml +4 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +10 -0
  8. package/android/src/main/java/com/margelo/nitro/nitrosse/NetworkInspector.kt +94 -0
  9. package/android/src/main/java/com/margelo/nitro/nitrosse/NitroSse.kt +889 -0
  10. package/android/src/main/java/com/margelo/nitro/nitrosse/NitroSsePackage.kt +22 -0
  11. package/ios/NitroSse.swift +760 -0
  12. package/ios/NitroSseNetworkInspector.h +24 -0
  13. package/ios/NitroSseNetworkInspector.mm +68 -0
  14. package/lib/module/NitroSse.nitro.js +4 -0
  15. package/lib/module/NitroSse.nitro.js.map +1 -0
  16. package/lib/module/NitroSseClient.js +244 -0
  17. package/lib/module/NitroSseClient.js.map +1 -0
  18. package/lib/module/SseInterface.js +4 -0
  19. package/lib/module/SseInterface.js.map +1 -0
  20. package/lib/module/index.js +26 -0
  21. package/lib/module/index.js.map +1 -0
  22. package/lib/module/package.json +1 -0
  23. package/lib/typescript/package.json +1 -0
  24. package/lib/typescript/src/NitroSse.nitro.d.ts +48 -0
  25. package/lib/typescript/src/NitroSse.nitro.d.ts.map +1 -0
  26. package/lib/typescript/src/NitroSseClient.d.ts +26 -0
  27. package/lib/typescript/src/NitroSseClient.d.ts.map +1 -0
  28. package/lib/typescript/src/SseInterface.d.ts +237 -0
  29. package/lib/typescript/src/SseInterface.d.ts.map +1 -0
  30. package/lib/typescript/src/index.d.ts +11 -0
  31. package/lib/typescript/src/index.d.ts.map +1 -0
  32. package/nitro.json +23 -0
  33. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__unordered_map_std__string__std__string_____.hpp +138 -0
  34. package/nitrogen/generated/android/c++/JFunc_void_std__vector_SseEvent_.hpp +102 -0
  35. package/nitrogen/generated/android/c++/JHttpMethod.hpp +58 -0
  36. package/nitrogen/generated/android/c++/JHybridNitroSseSpec.cpp +134 -0
  37. package/nitrogen/generated/android/c++/JHybridNitroSseSpec.hpp +71 -0
  38. package/nitrogen/generated/android/c++/JSseConfig.hpp +162 -0
  39. package/nitrogen/generated/android/c++/JSseEvent.hpp +90 -0
  40. package/nitrogen/generated/android/c++/JSseEventType.hpp +67 -0
  41. package/nitrogen/generated/android/c++/JSseMockConfig.hpp +101 -0
  42. package/nitrogen/generated/android/c++/JSseMockEvent.hpp +94 -0
  43. package/nitrogen/generated/android/c++/JSseMockMode.hpp +58 -0
  44. package/nitrogen/generated/android/c++/JSseStats.hpp +70 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__unordered_map_std__string__std__string_____.kt +80 -0
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/Func_void_std__vector_SseEvent_.kt +80 -0
  47. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/HttpMethod.kt +23 -0
  48. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/HybridNitroSseSpec.kt +91 -0
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/SseConfig.kt +135 -0
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/SseEvent.kt +86 -0
  51. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/SseEventType.kt +26 -0
  52. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/SseMockConfig.kt +71 -0
  53. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/SseMockEvent.kt +91 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/SseMockMode.kt +23 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/SseStats.kt +66 -0
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrosse/nitrosseOnLoad.kt +35 -0
  57. package/nitrogen/generated/android/nitrosse+autolinking.cmake +81 -0
  58. package/nitrogen/generated/android/nitrosse+autolinking.gradle +27 -0
  59. package/nitrogen/generated/android/nitrosseOnLoad.cpp +58 -0
  60. package/nitrogen/generated/android/nitrosseOnLoad.hpp +34 -0
  61. package/nitrogen/generated/ios/NitroSse+autolinking.rb +62 -0
  62. package/nitrogen/generated/ios/NitroSse-Swift-Cxx-Bridge.cpp +74 -0
  63. package/nitrogen/generated/ios/NitroSse-Swift-Cxx-Bridge.hpp +415 -0
  64. package/nitrogen/generated/ios/NitroSse-Swift-Cxx-Umbrella.hpp +74 -0
  65. package/nitrogen/generated/ios/NitroSseAutolinking.mm +33 -0
  66. package/nitrogen/generated/ios/NitroSseAutolinking.swift +26 -0
  67. package/nitrogen/generated/ios/c++/HybridNitroSseSpecSwift.cpp +11 -0
  68. package/nitrogen/generated/ios/c++/HybridNitroSseSpecSwift.hpp +161 -0
  69. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__unordered_map_std__string__std__string_____.swift +67 -0
  70. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  71. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_std__unordered_map_std__string__std__string___.swift +66 -0
  72. package/nitrogen/generated/ios/swift/Func_void_std__unordered_map_std__string__std__string_.swift +54 -0
  73. package/nitrogen/generated/ios/swift/Func_void_std__vector_SseEvent_.swift +46 -0
  74. package/nitrogen/generated/ios/swift/HttpMethod.swift +40 -0
  75. package/nitrogen/generated/ios/swift/HybridNitroSseSpec.swift +63 -0
  76. package/nitrogen/generated/ios/swift/HybridNitroSseSpec_cxx.swift +246 -0
  77. package/nitrogen/generated/ios/swift/SseConfig.swift +346 -0
  78. package/nitrogen/generated/ios/swift/SseEvent.swift +155 -0
  79. package/nitrogen/generated/ios/swift/SseEventType.swift +52 -0
  80. package/nitrogen/generated/ios/swift/SseMockConfig.swift +94 -0
  81. package/nitrogen/generated/ios/swift/SseMockEvent.swift +179 -0
  82. package/nitrogen/generated/ios/swift/SseMockMode.swift +40 -0
  83. package/nitrogen/generated/ios/swift/SseStats.swift +70 -0
  84. package/nitrogen/generated/shared/c++/HttpMethod.hpp +76 -0
  85. package/nitrogen/generated/shared/c++/HybridNitroSseSpec.cpp +29 -0
  86. package/nitrogen/generated/shared/c++/HybridNitroSseSpec.hpp +81 -0
  87. package/nitrogen/generated/shared/c++/SseConfig.hpp +156 -0
  88. package/nitrogen/generated/shared/c++/SseEvent.hpp +115 -0
  89. package/nitrogen/generated/shared/c++/SseEventType.hpp +88 -0
  90. package/nitrogen/generated/shared/c++/SseMockConfig.hpp +105 -0
  91. package/nitrogen/generated/shared/c++/SseMockEvent.hpp +119 -0
  92. package/nitrogen/generated/shared/c++/SseMockMode.hpp +76 -0
  93. package/nitrogen/generated/shared/c++/SseStats.hpp +96 -0
  94. package/package.json +186 -0
  95. package/src/NitroSse.nitro.ts +53 -0
  96. package/src/NitroSseClient.ts +297 -0
  97. package/src/SseInterface.ts +248 -0
  98. package/src/index.ts +31 -0
@@ -0,0 +1,297 @@
1
+ import type { NitroSse } from './NitroSse.nitro';
2
+ import type {
3
+ SseClient,
4
+ SseConfig,
5
+ SseEvent,
6
+ SseListener,
7
+ SseStats,
8
+ } from './SseInterface';
9
+
10
+ declare const __DEV__: boolean | undefined;
11
+
12
+ export class NitroSseClient implements SseClient {
13
+ private _native: NitroSse;
14
+ private _listeners: Map<string, Set<SseListener>> = new Map();
15
+ private _legacyCallback?: (events: SseEvent[]) => void;
16
+ private _config?: SseConfig;
17
+ private _mockIntervalId?: any;
18
+ private _mockIndex: number = 0;
19
+
20
+ constructor(native: NitroSse) {
21
+ this._native = native;
22
+ }
23
+
24
+ setup(config: SseConfig, onEvent?: (events: SseEvent[]) => void): void {
25
+ this._config = { ...config };
26
+ this._legacyCallback = onEvent;
27
+
28
+ const isDev = typeof __DEV__ !== 'undefined' ? __DEV__ === true : false;
29
+ if (!isDev) {
30
+ delete this._config.mock;
31
+ }
32
+
33
+ if (this._config.mock) {
34
+ console.warn(
35
+ '\n' +
36
+ '=================================================================\n' +
37
+ '⚠️ [react-native-nitro-sse] WARNING: MOCK STREAMING IS ENABLED! ⚠️\n' +
38
+ ` Mode: ${this._config.mock.mode.toUpperCase()} | Speed: ${
39
+ this._config.mock.eventsPerSecond ?? 1
40
+ } events/sec\n` +
41
+ ' Please ensure mocking is disabled before building for production!\n' +
42
+ '=================================================================\n'
43
+ );
44
+ }
45
+
46
+ if (this._mockIntervalId) {
47
+ clearTimeout(this._mockIntervalId);
48
+ this._mockIntervalId = undefined;
49
+ }
50
+
51
+ // We wrap the native setup to dispatch events to typed listeners
52
+ this._native.setup(this._config, (events) => {
53
+ // 1. Call legacy batch callback if provided
54
+ this._legacyCallback?.(events);
55
+
56
+ // 2. Dispatch individual events to registered listeners
57
+ for (const event of events) {
58
+ // Dispatch basic types: 'open', 'message', 'error', 'close', 'heartbeat'
59
+ this._emit(event.type, event);
60
+
61
+ // Dispatch by SSE event name if it's different from the type
62
+ // e.g., if type is 'message' but event name is 'update'
63
+ if (event.event && event.event !== event.type) {
64
+ this._emit(event.event, event);
65
+ }
66
+ }
67
+ });
68
+ }
69
+
70
+ addEventListener(type: string, listener: SseListener): void {
71
+ if (!this._listeners.has(type)) {
72
+ this._listeners.set(type, new Set());
73
+ }
74
+ this._listeners.get(type)!.add(listener);
75
+ }
76
+
77
+ removeEventListener(type: string, listener: SseListener): void {
78
+ this._listeners.get(type)?.delete(listener);
79
+ }
80
+
81
+ private _emit(type: string, event: SseEvent): void {
82
+ const listeners = this._listeners.get(type);
83
+ if (listeners) {
84
+ listeners.forEach((listener) => {
85
+ try {
86
+ listener(event);
87
+ } catch (e) {
88
+ console.error(`[NitroSse] Error in event listener for "${type}":`, e);
89
+ }
90
+ });
91
+ }
92
+ }
93
+
94
+ private _createSseEvent(rawMock: any): SseEvent {
95
+ return {
96
+ type: rawMock.type ?? 'message',
97
+ data: rawMock.data,
98
+ parsedData: rawMock.parsedData,
99
+ id: rawMock.id,
100
+ event: rawMock.event,
101
+ message: rawMock.message,
102
+ statusCode: rawMock.statusCode ?? 200,
103
+ retry: rawMock.retry,
104
+ };
105
+ }
106
+
107
+ start(): void {
108
+ if (this._mockIntervalId) {
109
+ clearTimeout(this._mockIntervalId);
110
+ this._mockIntervalId = undefined;
111
+ }
112
+
113
+ if (this._config?.mock) {
114
+ const {
115
+ mode,
116
+ data,
117
+ eventsPerSecond = 1,
118
+ loop = false,
119
+ errorRate = 0,
120
+ } = this._config.mock;
121
+ this._mockIndex = 0;
122
+
123
+ // Validate and normalize eventsPerSecond (must be a finite positive number, default to 1)
124
+ let validatedEventsPerSecond = Number(eventsPerSecond);
125
+ if (
126
+ Number.isNaN(validatedEventsPerSecond) ||
127
+ !Number.isFinite(validatedEventsPerSecond) ||
128
+ validatedEventsPerSecond <= 0
129
+ ) {
130
+ validatedEventsPerSecond = 1;
131
+ }
132
+
133
+ // Validate and normalize errorRate (must be a finite number within [0,1], default to 0)
134
+ let validatedErrorRate = Number(errorRate);
135
+ if (
136
+ Number.isNaN(validatedErrorRate) ||
137
+ !Number.isFinite(validatedErrorRate)
138
+ ) {
139
+ validatedErrorRate = 0;
140
+ } else {
141
+ validatedErrorRate = Math.max(0, Math.min(1, validatedErrorRate));
142
+ }
143
+
144
+ const delayMs = 1000 / validatedEventsPerSecond;
145
+ // Safeguard: if eventsPerSecond is huge (e.g. 1000), setInterval/setTimeout is too slow.
146
+ // We batch events together in chunks if the calculated interval is under 10ms.
147
+ const batchSize = Math.max(1, Math.round(validatedEventsPerSecond / 100)); // chunk size for 10ms intervals
148
+ const intervalMs = Math.max(10, delayMs * batchSize);
149
+
150
+ const scheduleNext = () => {
151
+ if (this._mockIndex >= data.length) {
152
+ if (loop && data.length > 0) {
153
+ this._mockIndex = 0;
154
+ } else {
155
+ if (this._mockIntervalId) {
156
+ clearTimeout(this._mockIntervalId);
157
+ this._mockIntervalId = undefined;
158
+ }
159
+ const closeEvent: SseEvent = { type: 'close', statusCode: 200 };
160
+ this._legacyCallback?.([closeEvent]);
161
+ this._emit('close', closeEvent);
162
+ return;
163
+ }
164
+ }
165
+
166
+ // Simulate connection drops
167
+ if (validatedErrorRate && Math.random() < validatedErrorRate) {
168
+ const errorEvent: SseEvent = {
169
+ type: 'error',
170
+ message: 'Mock Connection Drop (Simulated Error)',
171
+ statusCode: 500,
172
+ };
173
+ this._legacyCallback?.([errorEvent]);
174
+ this._emit('error', errorEvent);
175
+
176
+ // Retry / reconnect delay simulator (e.g. 2000ms)
177
+ this._mockIntervalId = setTimeout(scheduleNext, 2000);
178
+ return;
179
+ }
180
+
181
+ const currentItem = data[this._mockIndex];
182
+ const hasCustomDelay =
183
+ currentItem && typeof currentItem.delayMs === 'number';
184
+
185
+ const batch: SseEvent[] = [];
186
+ if (hasCustomDelay) {
187
+ const rawMock = data[this._mockIndex++];
188
+ if (rawMock) {
189
+ batch.push(this._createSseEvent(rawMock));
190
+ }
191
+ } else {
192
+ for (let i = 0; i < batchSize && this._mockIndex < data.length; i++) {
193
+ const nextItem = data[this._mockIndex];
194
+ if (nextItem && typeof nextItem.delayMs === 'number') {
195
+ break;
196
+ }
197
+ const rawMock = data[this._mockIndex++];
198
+ if (rawMock) {
199
+ batch.push(this._createSseEvent(rawMock));
200
+ }
201
+ }
202
+ }
203
+
204
+ if (batch.length > 0) {
205
+ // 1. Call legacy batch callback if provided
206
+ this._legacyCallback?.(batch);
207
+
208
+ // 2. Dispatch individual events to registered listeners
209
+ for (const event of batch) {
210
+ this._emit(event.type, event);
211
+ if (event.event && event.event !== event.type) {
212
+ this._emit(event.event, event);
213
+ }
214
+ }
215
+ }
216
+
217
+ const nextDelay = hasCustomDelay ? currentItem.delayMs! : intervalMs;
218
+ this._mockIntervalId = setTimeout(scheduleNext, nextDelay);
219
+ };
220
+
221
+ const firstItem = data[0];
222
+ const firstDelay =
223
+ firstItem && typeof firstItem.delayMs === 'number'
224
+ ? firstItem.delayMs
225
+ : intervalMs;
226
+
227
+ if (mode === 'replace') {
228
+ // Emit simulated open event
229
+ const openEvent: SseEvent = { type: 'open', statusCode: 200 };
230
+ this._legacyCallback?.([openEvent]);
231
+ this._emit('open', openEvent);
232
+
233
+ this._mockIntervalId = setTimeout(scheduleNext, firstDelay);
234
+ return; // Skip native start
235
+ } else if (mode === 'inject') {
236
+ this._mockIntervalId = setTimeout(scheduleNext, firstDelay);
237
+ }
238
+ }
239
+
240
+ this._native.start();
241
+ }
242
+
243
+ stop(): void {
244
+ if (this._mockIntervalId) {
245
+ clearTimeout(this._mockIntervalId);
246
+ this._mockIntervalId = undefined;
247
+ }
248
+ this._native.stop();
249
+ }
250
+
251
+ restart(): void {
252
+ if (this._config?.mock) {
253
+ this.stop();
254
+ this.start();
255
+ } else {
256
+ this._native.restart();
257
+ }
258
+ }
259
+
260
+ flush(): void {
261
+ this._native.flush();
262
+ }
263
+
264
+ isConnected(): boolean {
265
+ if (this._config?.mock?.mode === 'replace' && this._mockIntervalId) {
266
+ return true;
267
+ }
268
+ return this._native.isConnected();
269
+ }
270
+
271
+ getStats(): SseStats {
272
+ if (this._config?.mock?.mode === 'replace') {
273
+ return {
274
+ totalBytesReceived: this._mockIndex * 150,
275
+ reconnectCount: 0,
276
+ };
277
+ }
278
+ return this._native.getStats();
279
+ }
280
+
281
+ updateHeaders(headers: Record<string, string>): void {
282
+ this._native.updateHeaders(headers);
283
+ }
284
+
285
+ setLastProcessedId(id: string): void {
286
+ this._native.setLastProcessedId(id);
287
+ }
288
+
289
+ injectMockEvent(event: Partial<SseEvent>): void {
290
+ const sseEvent = this._createSseEvent(event);
291
+ this._legacyCallback?.([sseEvent]);
292
+ this._emit(sseEvent.type, sseEvent);
293
+ if (sseEvent.event && sseEvent.event !== sseEvent.type) {
294
+ this._emit(sseEvent.event, sseEvent);
295
+ }
296
+ }
297
+ }
@@ -0,0 +1,248 @@
1
+ import type { AnyMap } from 'react-native-nitro-modules';
2
+ /**
3
+ * HTTP method to use for the SSE connection.
4
+ */
5
+ export type HttpMethod = 'get' | 'post';
6
+
7
+ /**
8
+ * Type of event received from the SSE stream.
9
+ * - `open`: Connection established.
10
+ * - `message`: Standard data message.
11
+ * - `error`: Connection error or other failure.
12
+ * - `close`: Connection closed (not typical for SSE, but used for cleanup).
13
+ * - `heartbeat`: Ping/Keep-alive signal.
14
+ */
15
+ export type SseEventType = 'open' | 'message' | 'error' | 'close' | 'heartbeat';
16
+
17
+ /**
18
+ * Configuration for the SSE connection.
19
+ */
20
+ export interface SseConfig {
21
+ /** The URL of the SSE endpoint. */
22
+ url: string;
23
+ /** HTTP method (default: 'GET'). */
24
+ method?: HttpMethod;
25
+ /** Custom HTTP headers to include in the request. */
26
+ headers?: Record<string, string>;
27
+ /** Body for POST requests. */
28
+ body?: string;
29
+ /**
30
+ * Whether to continue processing events when the app is in the background.
31
+ * Note: On iOS, this uses background tasks which are limited in time by the OS.
32
+ * @default false
33
+ */
34
+ backgroundExecution?: boolean;
35
+ /**
36
+ * How long (in ms) to wait and group multiple events before sending them to the JS side.
37
+ * High-frequency streams should use this to reduce JS bridge overhead and improve UI performance.
38
+ * Set to 0 to deliver events immediately one-by-one.
39
+ * @default 0
40
+ */
41
+ batchingIntervalMs?: number;
42
+ /**
43
+ * Maximum number of events to keep in the native buffer before forcing a flush to JS,
44
+ * regardless of the `batchingIntervalMs`. Prevents memory pressure.
45
+ * @default 1000
46
+ */
47
+ maxBufferSize?: number;
48
+ /**
49
+ * Maximum time (in ms) to wait for the initial server connection and handshake to complete.
50
+ * Effectively the \"Connect Timeout\".
51
+ * @default 15000
52
+ */
53
+ connectionTimeoutMs?: number;
54
+ /**
55
+ * Maximum idle time (in ms) allowed between receiving data packets or heartbeats.
56
+ * If the server remains silent longer than this, the connection is considered stalled and will reconnect.
57
+ * Increase this for streams that transmit data infrequently.
58
+ * @default 300000
59
+ */
60
+ readTimeoutMs?: number;
61
+ /**
62
+ * Initial delay (in ms) for reconnection attempts.
63
+ * Subsequent attempts use exponential backoff.
64
+ * @default 1000
65
+ */
66
+ retryIntervalMs?: number;
67
+ /**
68
+ * Maximum delay (in ms) for reconnection attempts.
69
+ * @default 30000
70
+ */
71
+ maxRetryIntervalMs?: number;
72
+ /**
73
+ * Factor to randomize reconnection attempts (0.0 to 1.0).
74
+ * 0 means no jitter, 1.0 means up to 100% randomization.
75
+ * @default 0.5
76
+ */
77
+ jitterFactor?: number;
78
+ /**
79
+ * Maximum number of reconnection attempts before giving up.
80
+ * Use -1 for infinite (default), 0 to disable auto-reconnection.
81
+ * @default -1
82
+ */
83
+ maxReconnectAttempts?: number;
84
+ /**
85
+ * Whether to automatically parse message data as JSON in a background native thread.
86
+ * If true, and parsing succeeds, the result will be available in the 'parsedData' field.
87
+ * @default false
88
+ */
89
+ autoParseJSON?: boolean;
90
+ /**
91
+ * Whether to automatically monitor network connectivity changes.
92
+ * If true, the client will proactively reconnect when switching networks (WiFi <-> Cellular)
93
+ * and pause/resume based on internet availability.
94
+ * @default true
95
+ */
96
+ monitorNetwork?: boolean;
97
+ /**
98
+ * Async interceptor called before every connection attempt (including auto-reconnects).
99
+ * Use this to refresh tokens or calculate dynamic headers.
100
+ * Note: This is protected by a native timeout to prevent the app from hanging.
101
+ */
102
+ onBeforeRequest?: () => Promise<Record<string, string>>;
103
+ /**
104
+ * Configuration for mock streaming data.
105
+ * Used for local testing and debugging.
106
+ */
107
+ mock?: SseMockConfig;
108
+ }
109
+
110
+ export type SseMockMode = 'replace' | 'inject';
111
+
112
+ /**
113
+ * Represents a single mock SSE event configuration.
114
+ */
115
+ export interface SseMockEvent {
116
+ /** The type of the event. */
117
+ type?: SseEventType;
118
+ /** The data payload of the event as a raw string. */
119
+ data?: string;
120
+ /** The parsed JSON data, if autoParseJSON is enabled and parsing succeeds. */
121
+ parsedData?: AnyMap;
122
+ /** The event ID, if provided. */
123
+ id?: string;
124
+ /** The event name, if provided (internal 'event' field in SSE). */
125
+ event?: string;
126
+ /** System message or error description. */
127
+ message?: string;
128
+ /** HTTP status code if applicable. */
129
+ statusCode?: number;
130
+ /** Server-requested retry delay in milliseconds. */
131
+ retry?: number;
132
+ /** Custom delay in milliseconds before this event is dispatched. */
133
+ delayMs?: number;
134
+ }
135
+
136
+ /**
137
+ * Configuration for mock streaming data.
138
+ */
139
+ export interface SseMockConfig {
140
+ /**
141
+ * - 'replace': Simulates the stream completely in JavaScript without any native network connection.
142
+ * - 'inject': Establishes the real server connection and injects mock events alongside it.
143
+ */
144
+ mode: SseMockMode;
145
+ /**
146
+ * The list of mock events to be streamed.
147
+ */
148
+ data: SseMockEvent[];
149
+ /**
150
+ * The frequency at which mock events are dispatched.
151
+ * Represented in events per second.
152
+ * @default 1
153
+ */
154
+ eventsPerSecond?: number;
155
+ /**
156
+ * Whether to loop the mock stream indefinitely.
157
+ * @default false
158
+ */
159
+ loop?: boolean;
160
+ /**
161
+ * Probability of simulated connection drop/error events (0.0 to 1.0).
162
+ * @default 0
163
+ */
164
+ errorRate?: number;
165
+ }
166
+
167
+ /**
168
+ * Represents a single SSE event.
169
+ */
170
+ export interface SseEvent {
171
+ /** The type of the event. */
172
+ type: SseEventType;
173
+ /** The data payload of the event as a raw string. */
174
+ data?: string;
175
+ /** The parsed JSON data, if autoParseJSON is enabled and parsing succeeds. */
176
+ parsedData?: AnyMap;
177
+ /** The event ID, if provided. */
178
+ id?: string;
179
+ /** The event name, if provided (internal 'event' field in SSE). */
180
+ event?: string;
181
+ /** System message or error description. */
182
+ message?: string;
183
+ /** HTTP status code if applicable. */
184
+ statusCode?: number;
185
+ /** Server-requested retry delay in milliseconds. */
186
+ retry?: number;
187
+ }
188
+
189
+ /**
190
+ * Statistics about the SSE connection.
191
+ */
192
+ export interface SseStats {
193
+ /** Total bytes received so far. */
194
+ totalBytesReceived: number;
195
+ /** Number of times the connection has been re-established. */
196
+ reconnectCount: number;
197
+ /** Timestamp of the last error event. */
198
+ lastErrorTime?: number;
199
+ /** Error code of the last error. */
200
+ lastErrorCode?: string;
201
+ }
202
+
203
+ /**
204
+ * Listener for a specific SSE event.
205
+ */
206
+ export type SseListener = (event: SseEvent) => void;
207
+
208
+ /**
209
+ * Public interface for the NitroSse client, supporting typed event listeners.
210
+ */
211
+ export interface SseClient {
212
+ /**
213
+ * Configure SSE and setup event callback.
214
+ * @param config The SSE configuration.
215
+ * @param onEvent Optional legacy batch callback for all events.
216
+ */
217
+ setup(config: SseConfig, onEvent?: (events: SseEvent[]) => void): void;
218
+
219
+ /**
220
+ * Register a listener for a specific event type ('message', 'open', etc.)
221
+ * or a custom SSE event name (from the 'event:' field).
222
+ */
223
+ addEventListener(type: string, listener: SseListener): void;
224
+
225
+ /**
226
+ * Unregister a listener.
227
+ */
228
+ removeEventListener(type: string, listener: SseListener): void;
229
+
230
+ /** Start the connection. */
231
+ start(): void;
232
+ /** Stop the connection. */
233
+ stop(): void;
234
+ /** Restart the connection (stop + start). */
235
+ restart(): void;
236
+ /** Force flush buffered events to JS. */
237
+ flush(): void;
238
+ /** Check if active. */
239
+ isConnected(): boolean;
240
+ /** Get stats. */
241
+ getStats(): SseStats;
242
+ /** Manually update headers. */
243
+ updateHeaders(headers: Record<string, string>): void;
244
+ /** Set last event ID. */
245
+ setLastProcessedId(id: string): void;
246
+ /** Manually inject a mock event into the stream (for testing/debugging). */
247
+ injectMockEvent(event: Partial<SseEvent>): void;
248
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ import type { NitroSse } from './NitroSse.nitro';
3
+ import { NitroSseClient } from './NitroSseClient';
4
+ import type { SseClient } from './SseInterface';
5
+
6
+ export * from './SseInterface';
7
+ export * from './NitroSse.nitro';
8
+
9
+ /**
10
+ * Creates a high-performance SSE client that supports typed event listeners (`addEventListener`) and legacy batching.
11
+ *
12
+ * @returns An SseClient instance wrapping the native NitroSse implementation.
13
+ * @throws If the native NitroSse module cannot be found. Ensure the library is linked and the app is built for iOS/Android.
14
+ */
15
+ export function createNitroSse(): SseClient {
16
+ let nativeSse: NitroSse | undefined;
17
+ try {
18
+ nativeSse = NitroModules.createHybridObject<NitroSse>('NitroSse');
19
+ } catch {
20
+ console.debug(
21
+ 'Native NitroSse not found. This might be a test environment or web.'
22
+ );
23
+ }
24
+
25
+ if (!nativeSse) {
26
+ throw new Error(
27
+ 'NitroSse: Native module not found. Ensure you have linked the library and built the app for iOS/Android.'
28
+ );
29
+ }
30
+ return new NitroSseClient(nativeSse);
31
+ }