react-native-meshstream 1.0.0 → 1.0.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.2
4
+
5
+ - Upgrade the public iOS dependency to MeshStreamKit 1.0.7 (iOS 15+).
6
+ - Upgrade the public Android dependency to meshstream-android-sdk 1.0.6.
7
+ - Use the public sdk.meshstream.net Maven endpoint in the example and installation guides.
8
+
9
+ ## 1.0.1
10
+
11
+ - Pin the public Android and iOS native dependencies to 1.0.4.
12
+ - Add `MeshStream.getSdkVersion()` to read the loaded native SDK version before creating a client.
13
+ - Add `MeshStream.getVersion()` with the React Native package version, native SDK version, and current platform.
14
+ - Display both versions in the example and resolve its SDK from the installed npm package.
15
+ - Update the example for the local Android NDK and current Xcode/iOS scene lifecycle.
16
+
3
17
  ## 1.0.0
4
18
 
5
19
  - Unified Android and iOS React Native bridges over the respective native MeshStream SDKs.
@@ -1,17 +1,17 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MeshStreamP2P'
3
- s.version = '1.0.0'
3
+ s.version = '1.0.2'
4
4
  s.summary = 'MeshStream P2P playback URL bridge for React Native.'
5
5
  s.homepage = 'https://github.com/Mesh-Stream/meshstream-p2p-react-native-sdk'
6
6
  s.license = { :type => 'Apache-2.0' }
7
7
  s.author = { 'MeshStream' => 'support@meshstream.net' }
8
- s.platforms = { :ios => '13.0' }
8
+ s.platforms = { :ios => '15.0' }
9
9
  s.source = { :git => 'https://github.com/Mesh-Stream/meshstream-p2p-react-native-sdk.git', :tag => s.version.to_s }
10
10
  s.source_files = 'ios/**/*.{h,m}'
11
11
  s.public_header_files = 'ios/MeshStreamP2P.h'
12
12
  s.private_header_files = 'ios/MeshStreamP2PValidation.h'
13
13
  s.dependency 'React-Core'
14
- s.dependency 'MeshStreamKit', '= 1.0.2'
14
+ s.dependency 'MeshStreamKit', '= 1.0.7'
15
15
  s.frameworks = 'Foundation', 'Network', 'Security'
16
16
 
17
17
  s.test_spec 'Validation' do |test_spec|
package/README.md CHANGED
@@ -9,7 +9,7 @@ React Native binding for MeshStream P2P playback acceleration. It creates a CDN-
9
9
  ## Requirements
10
10
 
11
11
  - React Native `0.72+`
12
- - Android API 24+ (Android 7.0+) or iOS 13+
12
+ - Android API 24+ (Android 7.0+) or iOS 15+
13
13
  - MeshStream SDK key enabled for the target platform
14
14
 
15
15
  ## Install
@@ -25,13 +25,13 @@ repositories {
25
25
  google()
26
26
  mavenCentral()
27
27
  maven {
28
- url = uri("https://pub-61830e1369904d19af505ff74fd55ba4.r2.dev/maven")
28
+ url = uri("https://sdk.meshstream.net/maven")
29
29
  content { includeGroup("io.meshstream") }
30
30
  }
31
31
  }
32
32
  ```
33
33
 
34
- For iOS, the bridge resolves the public `MeshStreamKit 1.0.2` CocoaPod. After installation, run `pod install --repo-update` from the app's `ios` directory and rebuild the native app. Do not add a local `MeshStreamKit` pod path or embed its XCFramework yourself.
34
+ For iOS, the bridge resolves the public `MeshStreamKit 1.0.7` CocoaPod. After installation, run `pod install --repo-update` from the app's `ios` directory and rebuild the native app. Do not add a local `MeshStreamKit` pod path or embed its XCFramework yourself.
35
35
 
36
36
  ## Basic usage
37
37
 
@@ -102,6 +102,16 @@ const diagnostics = await session.getDiagnostics();
102
102
  subscription.remove();
103
103
  ```
104
104
 
105
+ ### Version information
106
+
107
+ Read version information before creating a client when attaching diagnostics to a support request. The React Native package and the native engine are versioned independently, so record both fields.
108
+
109
+ ```ts
110
+ const sdkVersion = await MeshStream.getSdkVersion();
111
+ const version = await MeshStream.getVersion();
112
+ // { packageVersion: '1.0.2', nativeSdkVersion: '1.0.6', platform: 'android' }
113
+ ```
114
+
105
115
  Available event types: `peerCountChanged`, `playbackPrepared`, `p2pReady`, `p2pUnavailable`, `statistics`, `cdnFallback`, `trackerError`, and `peerConnectError`.
106
116
 
107
117
  ## Supported media
@@ -114,13 +124,13 @@ The package is a unified JavaScript layer, not an Android SDK repackaged for Rea
114
124
 
115
125
  ### Versioning
116
126
 
117
- - `react-native-meshstream` follows its own React Native package version. This release is `1.0.0`.
118
- - The Android bridge keeps its independent native dependency: `io.meshstream:meshstream-android-sdk:1.0.2`.
119
- - The iOS bridge keeps its independent native dependency: `MeshStreamKit >= 1.0.0`.
127
+ - `react-native-meshstream` follows its own React Native package version. This release is `1.0.2`.
128
+ - The Android bridge keeps its independent native dependency: `io.meshstream:meshstream-android-sdk:1.0.6`.
129
+ - The iOS bridge keeps its independent native dependency: `MeshStreamKit 1.0.7`.
120
130
 
121
131
  Updating the React Native package does not implicitly upgrade either native SDK.
122
132
 
123
- - Android resolves `io.meshstream:meshstream-android-sdk:1.0.2` from the MeshStream Maven repository.
124
- - iOS resolves the public `MeshStreamKit 1.0.2` CocoaPod.
133
+ - Android resolves `io.meshstream:meshstream-android-sdk:1.0.6` from the MeshStream Maven repository.
134
+ - iOS resolves the public `MeshStreamKit 1.0.7` CocoaPod.
125
135
 
126
136
  Both bridges maintain client/session isolation. Closing a client only closes its own sessions and never interrupts another React Native player in the same process.
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ## 环境要求
10
10
 
11
11
  - React Native `0.72+`
12
- - Android API 24+(Android 7.0+)或 iOS 13+
12
+ - Android API 24+(Android 7.0+)或 iOS 15+
13
13
  - 已启用对应目标平台的 MeshStream SDK Key
14
14
 
15
15
  ## 安装
@@ -25,13 +25,13 @@ repositories {
25
25
  google()
26
26
  mavenCentral()
27
27
  maven {
28
- url = uri("https://pub-61830e1369904d19af505ff74fd55ba4.r2.dev/maven")
28
+ url = uri("https://sdk.meshstream.net/maven")
29
29
  content { includeGroup("io.meshstream") }
30
30
  }
31
31
  }
32
32
  ```
33
33
 
34
- 接入 iOS 时,bridge 会解析公开的 `MeshStreamKit 1.0.2` CocoaPod。安装 npm 包后,请在应用的 `ios` 目录执行 `pod install --repo-update` 并重新编译原生工程;不要添加本地 `MeshStreamKit` Pod 路径,也不需要手动嵌入 XCFramework。
34
+ 接入 iOS 时,bridge 会解析公开的 `MeshStreamKit 1.0.7` CocoaPod。安装 npm 包后,请在应用的 `ios` 目录执行 `pod install --repo-update` 并重新编译原生工程;不要添加本地 `MeshStreamKit` Pod 路径,也不需要手动嵌入 XCFramework。
35
35
 
36
36
  ## 基础接入
37
37
 
@@ -102,6 +102,16 @@ const diagnostics = await session.getDiagnostics();
102
102
  subscription.remove();
103
103
  ```
104
104
 
105
+ ### 版本信息
106
+
107
+ 创建客户端前即可读取版本信息。提交工单或诊断日志时,应同时记录 React Native 包版本和原生内核版本;两者独立发布。
108
+
109
+ ```ts
110
+ const sdkVersion = await MeshStream.getSdkVersion();
111
+ const version = await MeshStream.getVersion();
112
+ // { packageVersion: '1.0.2', nativeSdkVersion: '1.0.6', platform: 'android' }
113
+ ```
114
+
105
115
  事件类型包括:`peerCountChanged`、`playbackPrepared`、`p2pReady`、`p2pUnavailable`、`statistics`、`cdnFallback`、`trackerError`、`peerConnectError`。
106
116
 
107
117
  ## 支持的媒体格式
@@ -114,13 +124,13 @@ subscription.remove();
114
124
 
115
125
  ### 版本说明
116
126
 
117
- - `react-native-meshstream` 使用独立的 React Native 包版本;当前首发版本为 `1.0.0`。
118
- - Android 桥接使用独立的原生依赖:`io.meshstream:meshstream-android-sdk:1.0.2`。
119
- - iOS 桥接使用独立的原生依赖:`MeshStreamKit >= 1.0.0`。
127
+ - `react-native-meshstream` 使用独立的 React Native 包版本;当前版本为 `1.0.2`。
128
+ - Android 桥接使用独立的原生依赖:`io.meshstream:meshstream-android-sdk:1.0.6`。
129
+ - iOS 桥接使用独立的原生依赖:`MeshStreamKit 1.0.7`。
120
130
 
121
131
  升级 React Native 包不会隐式升级任一原生 SDK。
122
132
 
123
- - Android 从 MeshStream Maven 仓库解析 `io.meshstream:meshstream-android-sdk:1.0.2`。
124
- - iOS 通过 CocoaPods 解析公开的 `MeshStreamKit 1.0.2`。
133
+ - Android 从 MeshStream Maven 仓库解析 `io.meshstream:meshstream-android-sdk:1.0.6`。
134
+ - iOS 通过 CocoaPods 解析公开的 `MeshStreamKit 1.0.7`。
125
135
 
126
136
  两个 bridge 都保证 client/session 隔离。关闭某一个 client 只会关闭其所属会话,不会中断同一进程中的其他 React Native 播放器。
@@ -19,5 +19,5 @@ android {
19
19
  dependencies {
20
20
  implementation 'com.facebook.react:react-android'
21
21
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
22
- implementation 'io.meshstream:meshstream-android-sdk:1.0.2'
22
+ implementation 'io.meshstream:meshstream-android-sdk:1.0.6'
23
23
  }
@@ -33,6 +33,7 @@ class MeshStreamP2PModule(
33
33
  private data class BridgeClient(
34
34
  val client: P2pClient,
35
35
  val sessions: ConcurrentHashMap<String, PlaybackSession> = ConcurrentHashMap(),
36
+ val nativeSessionIds: ConcurrentHashMap<Long, String> = ConcurrentHashMap(),
36
37
  val pendingEvents: ConcurrentHashMap<Long, MutableList<PendingEvent>> = ConcurrentHashMap(),
37
38
  )
38
39
 
@@ -45,6 +46,15 @@ class MeshStreamP2PModule(
45
46
 
46
47
  override fun getName(): String = MODULE_NAME
47
48
 
49
+ @ReactMethod
50
+ fun getSdkVersion(promise: Promise) {
51
+ try {
52
+ promise.resolve(P2pSdk.getSdkVersion())
53
+ } catch (error: Throwable) {
54
+ promise.reject("MESHSTREAM_VERSION_FAILED", error.message, error)
55
+ }
56
+ }
57
+
48
58
  @ReactMethod
49
59
  fun createClient(config: ReadableMap, promise: Promise) {
50
60
  try {
@@ -84,6 +94,7 @@ class MeshStreamP2PModule(
84
94
  )
85
95
  val sessionId = UUID.randomUUID().toString()
86
96
  bridgeClient.sessions[sessionId] = session
97
+ bridgeClient.nativeSessionIds[session.sessionId] = sessionId
87
98
  flushPendingEvents(clientId, sessionId, session, bridgeClient)
88
99
  promise.resolve(session.toWritableMap(clientId, sessionId))
89
100
  } catch (error: Throwable) {
@@ -141,6 +152,7 @@ class MeshStreamP2PModule(
141
152
  try {
142
153
  val bridgeClient = clients.requireClient(clientId)
143
154
  bridgeClient.sessions.remove(sessionId)?.let { session ->
155
+ bridgeClient.nativeSessionIds.remove(session.sessionId, sessionId)
144
156
  bridgeClient.pendingEvents.remove(session.sessionId)
145
157
  session.close()
146
158
  }
@@ -156,6 +168,7 @@ class MeshStreamP2PModule(
156
168
  clients.remove(clientId)?.let { bridgeClient ->
157
169
  bridgeClient.sessions.values.forEach { it.close() }
158
170
  bridgeClient.sessions.clear()
171
+ bridgeClient.nativeSessionIds.clear()
159
172
  bridgeClient.pendingEvents.clear()
160
173
  bridgeClient.client.close()
161
174
  }
@@ -168,6 +181,7 @@ class MeshStreamP2PModule(
168
181
  override fun invalidate() {
169
182
  clients.values.forEach { bridgeClient ->
170
183
  bridgeClient.sessions.values.forEach { it.close() }
184
+ bridgeClient.nativeSessionIds.clear()
171
185
  bridgeClient.pendingEvents.clear()
172
186
  bridgeClient.client.close()
173
187
  }
@@ -184,8 +198,7 @@ class MeshStreamP2PModule(
184
198
  }
185
199
 
186
200
  private fun createEventListener(clientId: String, bridgeClient: BridgeClient) = object : P2pEventListener {
187
- private fun sessionId(nativeSessionId: Long): String? = bridgeClient.sessions.entries
188
- .firstOrNull { it.value.sessionId == nativeSessionId }?.key
201
+ private fun sessionId(nativeSessionId: Long): String? = bridgeClient.nativeSessionIds[nativeSessionId]
189
202
 
190
203
  private fun emit(nativeSessionId: Long, type: String, extras: WritableMap.() -> Unit = {}) {
191
204
  val jsSessionId = sessionId(nativeSessionId)
@@ -99,8 +99,17 @@ static NSDictionary *MeshStreamDiagnosticsMap(P2PDiagnostics *diagnostics) {
99
99
  @implementation MeshStreamBridgeClient
100
100
  @end
101
101
 
102
+ @interface MeshStreamSessionRoute : NSObject
103
+ @property(nonatomic, copy) NSString *clientId;
104
+ @property(nonatomic, copy) NSString *sessionId;
105
+ @end
106
+
107
+ @implementation MeshStreamSessionRoute
108
+ @end
109
+
102
110
  @interface MeshStreamP2P () <P2PEventListener>
103
111
  @property(nonatomic, strong) NSMutableDictionary<NSString *, MeshStreamBridgeClient *> *clients;
112
+ @property(nonatomic, strong) NSMutableDictionary<NSString *, MeshStreamSessionRoute *> *sessionRoutes;
104
113
  @property(nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray<NSDictionary *> *> *pendingEvents;
105
114
  @end
106
115
 
@@ -113,6 +122,7 @@ RCT_EXPORT_MODULE(MeshStreamP2P)
113
122
  - (instancetype)init {
114
123
  if ((self = [super init])) {
115
124
  _clients = [NSMutableDictionary dictionary];
125
+ _sessionRoutes = [NSMutableDictionary dictionary];
116
126
  _pendingEvents = [NSMutableDictionary dictionary];
117
127
  }
118
128
  return self;
@@ -120,6 +130,12 @@ RCT_EXPORT_MODULE(MeshStreamP2P)
120
130
 
121
131
  - (NSArray<NSString *> *)supportedEvents { return @[MeshStreamEventName]; }
122
132
 
133
+ RCT_REMAP_METHOD(getSdkVersion,
134
+ getSdkVersionWithResolver:(RCTPromiseResolveBlock)resolve
135
+ rejecter:(RCTPromiseRejectBlock)reject) {
136
+ resolve([P2PSDK getSdkVersion] ?: @"unknown");
137
+ }
138
+
123
139
  RCT_REMAP_METHOD(createClient,
124
140
  createClientWithConfig:(NSDictionary *)config
125
141
  resolver:(RCTPromiseResolveBlock)resolve
@@ -168,6 +184,10 @@ RCT_EXPORT_METHOD(createPlaybackSession:(NSString *)clientId
168
184
  }
169
185
  NSString *sessionId = NSUUID.UUID.UUIDString;
170
186
  @synchronized (bridgeClient.sessions) { bridgeClient.sessions[sessionId] = session; }
187
+ MeshStreamSessionRoute *route = [MeshStreamSessionRoute new];
188
+ route.clientId = clientId;
189
+ route.sessionId = sessionId;
190
+ @synchronized (self.sessionRoutes) { self.sessionRoutes[session.sessionId] = route; }
171
191
  [self flushPendingEventsForNativeSessionId:session.sessionId clientId:clientId sessionId:sessionId];
172
192
  NSMutableDictionary *result = [@{
173
193
  @"clientId": clientId,
@@ -220,7 +240,10 @@ RCT_EXPORT_METHOD(closeSession:(NSString *)clientId sessionId:(NSString *)sessio
220
240
  if (bridgeClient == nil) { reject(@"MESHSTREAM_CLOSE_SESSION_FAILED", @"Unknown or closed MeshStream client.", nil); return; }
221
241
  PlaybackSession *session;
222
242
  @synchronized (bridgeClient.sessions) { session = bridgeClient.sessions[sessionId]; [bridgeClient.sessions removeObjectForKey:sessionId]; }
223
- if (session != nil) [self removePendingEventsForNativeSessionId:session.sessionId];
243
+ if (session != nil) {
244
+ [self removeSessionRouteForNativeSessionId:session.sessionId];
245
+ [self removePendingEventsForNativeSessionId:session.sessionId];
246
+ }
224
247
  [session close];
225
248
  resolve(nil);
226
249
  }
@@ -233,6 +256,7 @@ RCT_EXPORT_METHOD(closeClient:(NSString *)clientId resolver:(RCTPromiseResolveBl
233
256
  NSArray<PlaybackSession *> *sessions;
234
257
  @synchronized (bridgeClient.sessions) { sessions = bridgeClient.sessions.allValues; [bridgeClient.sessions removeAllObjects]; }
235
258
  for (PlaybackSession *session in sessions) {
259
+ [self removeSessionRouteForNativeSessionId:session.sessionId];
236
260
  [self removePendingEventsForNativeSessionId:session.sessionId];
237
261
  [session close];
238
262
  }
@@ -259,22 +283,13 @@ RCT_EXPORT_METHOD(closeClient:(NSString *)clientId resolver:(RCTPromiseResolveBl
259
283
  }
260
284
 
261
285
  - (void)emitForNativeSessionId:(NSString *)nativeSessionId type:(NSString *)type extras:(NSDictionary *)extras {
262
- __block NSString *clientId;
263
- __block NSString *sessionId;
264
- @synchronized (self.clients) {
265
- [self.clients enumerateKeysAndObjectsUsingBlock:^(NSString *key, MeshStreamBridgeClient *bridgeClient, BOOL *stop) {
266
- @synchronized (bridgeClient.sessions) {
267
- [bridgeClient.sessions enumerateKeysAndObjectsUsingBlock:^(NSString *jsId, PlaybackSession *session, BOOL *sessionStop) {
268
- if ([session.sessionId isEqualToString:nativeSessionId]) { clientId = key; sessionId = jsId; *stop = YES; *sessionStop = YES; }
269
- }];
270
- }
271
- }];
272
- }
273
- if (clientId == nil || sessionId == nil) {
286
+ MeshStreamSessionRoute *route;
287
+ @synchronized (self.sessionRoutes) { route = self.sessionRoutes[nativeSessionId]; }
288
+ if (route == nil) {
274
289
  [self bufferPendingEventForNativeSessionId:nativeSessionId type:type extras:extras];
275
290
  return;
276
291
  }
277
- [self sendSessionEventForClientId:clientId sessionId:sessionId type:type extras:extras];
292
+ [self sendSessionEventForClientId:route.clientId sessionId:route.sessionId type:type extras:extras];
278
293
  }
279
294
 
280
295
  - (void)sendSessionEventForClientId:(NSString *)clientId sessionId:(NSString *)sessionId type:(NSString *)type extras:(NSDictionary *)extras {
@@ -311,6 +326,10 @@ RCT_EXPORT_METHOD(closeClient:(NSString *)clientId resolver:(RCTPromiseResolveBl
311
326
  @synchronized (self.pendingEvents) { [self.pendingEvents removeObjectForKey:nativeSessionId]; }
312
327
  }
313
328
 
329
+ - (void)removeSessionRouteForNativeSessionId:(NSString *)nativeSessionId {
330
+ @synchronized (self.sessionRoutes) { [self.sessionRoutes removeObjectForKey:nativeSessionId]; }
331
+ }
332
+
314
333
  - (void)onPlaybackPreparedWithSessionId:(NSString *)sessionId playbackUrl:(NSURL *)playbackUrl { [self emitForNativeSessionId:sessionId type:@"playbackPrepared" extras:@{ @"playbackUrl": playbackUrl.absoluteString ?: @"" }]; }
315
334
  - (void)onStatisticsWithSessionId:(NSString *)sessionId stats:(P2PStats *)stats { [self emitForNativeSessionId:sessionId type:@"statistics" extras:@{ @"statistics": MeshStreamStatsMap(stats) }]; }
316
335
  - (void)onPeerCountChangedWithSessionId:(NSString *)sessionId peerCount:(NSInteger)peerCount { [self emitForNativeSessionId:sessionId type:@"peerCountChanged" extras:@{ @"peerCount": @(peerCount) }]; }
@@ -326,12 +345,16 @@ RCT_EXPORT_METHOD(closeClient:(NSString *)clientId resolver:(RCTPromiseResolveBl
326
345
  for (NSString *clientId in clientIds) {
327
346
  MeshStreamBridgeClient *bridgeClient = [self bridgeClient:clientId];
328
347
  [bridgeClient.client removeEventListener:self];
329
- for (PlaybackSession *session in bridgeClient.sessions.allValues) [session close];
348
+ for (PlaybackSession *session in bridgeClient.sessions.allValues) {
349
+ [self removeSessionRouteForNativeSessionId:session.sessionId];
350
+ [session close];
351
+ }
330
352
  [bridgeClient.sessions removeAllObjects];
331
353
  [bridgeClient.client close];
332
354
  bridgeClient.client = nil;
333
355
  }
334
356
  @synchronized (self.clients) { [self.clients removeAllObjects]; }
357
+ @synchronized (self.sessionRoutes) { [self.sessionRoutes removeAllObjects]; }
335
358
  @synchronized (self.pendingEvents) { [self.pendingEvents removeAllObjects]; }
336
359
  [super invalidate];
337
360
  }
@@ -1,5 +1,6 @@
1
1
  import type { MeshStreamConfig, MeshStreamDiagnostics, MeshStreamSessionInfo, MeshStreamStats, MeshStreamStatus, PlaybackSessionOptions } from './types';
2
2
  interface NativeMeshStreamP2P {
3
+ getSdkVersion(): Promise<string>;
3
4
  createClient(config: MeshStreamConfig): Promise<{
4
5
  clientId: string;
5
6
  status: MeshStreamStatus;
@@ -1,5 +1,5 @@
1
1
  import { type EmitterSubscription } from 'react-native';
2
- import type { MeshStreamConfig, MeshStreamDiagnostics, MeshStreamEvent, MeshStreamEventName, MeshStreamSessionInfo, MeshStreamStats, MeshStreamStatus, PlaybackSessionOptions } from './types';
2
+ import type { MeshStreamConfig, MeshStreamDiagnostics, MeshStreamEvent, MeshStreamEventName, MeshStreamSessionInfo, MeshStreamStats, MeshStreamStatus, MeshStreamVersionInfo, PlaybackSessionOptions } from './types';
3
3
  export * from './types';
4
4
  export declare class MeshStreamPlaybackSession {
5
5
  readonly clientId: string;
@@ -7,9 +7,10 @@ export declare class MeshStreamPlaybackSession {
7
7
  readonly playbackUrl: string;
8
8
  readonly playbackMode: MeshStreamSessionInfo['playbackMode'];
9
9
  readonly fallbackReason?: MeshStreamSessionInfo['fallbackReason'];
10
+ private readonly isClientClosed;
10
11
  private closed;
11
12
  private closing?;
12
- constructor(info: MeshStreamSessionInfo);
13
+ constructor(info: MeshStreamSessionInfo, isClientClosed?: () => boolean);
13
14
  preload(): Promise<boolean>;
14
15
  setP2pEnabled(enabled: boolean): Promise<void>;
15
16
  updateBufferedDurationMs(durationMs: number): Promise<void>;
@@ -30,6 +31,10 @@ export declare class MeshStreamClient {
30
31
  private withOpen;
31
32
  }
32
33
  export declare const MeshStream: {
34
+ /** Returns the version of the native MeshStream SDK loaded by this application. */
35
+ getSdkVersion(): Promise<string>;
36
+ /** Returns the React Native package version and the loaded native engine version. */
37
+ getVersion(): Promise<MeshStreamVersionInfo>;
33
38
  create(config: MeshStreamConfig): Promise<MeshStreamClient>;
34
39
  addEventListener(listener: (event: MeshStreamEvent) => void): EmitterSubscription;
35
40
  };
@@ -18,6 +18,7 @@ exports.MeshStream = exports.MeshStreamClient = exports.MeshStreamPlaybackSessio
18
18
  exports.addMeshStreamEventListener = addMeshStreamEventListener;
19
19
  const react_native_1 = require("react-native");
20
20
  const NativeMeshStreamP2P_1 = require("./NativeMeshStreamP2P");
21
+ const version_1 = require("./version");
21
22
  __exportStar(require("./types"), exports);
22
23
  const EVENT_NAME = 'MeshStreamP2P:event';
23
24
  function assertNonEmpty(value, field) {
@@ -29,13 +30,14 @@ function assertNonNegative(value, field) {
29
30
  throw new Error(`${field} must be a finite non-negative number.`);
30
31
  }
31
32
  class MeshStreamPlaybackSession {
32
- constructor(info) {
33
+ constructor(info, isClientClosed = () => false) {
33
34
  this.closed = false;
34
35
  this.clientId = info.clientId;
35
36
  this.sessionId = info.sessionId;
36
37
  this.playbackUrl = info.playbackUrl;
37
38
  this.playbackMode = info.playbackMode;
38
39
  this.fallbackReason = info.fallbackReason;
40
+ this.isClientClosed = isClientClosed;
39
41
  }
40
42
  async preload() {
41
43
  return this.withOpen(() => (0, NativeMeshStreamP2P_1.requireNativeModule)().preload(this.clientId, this.sessionId));
@@ -60,6 +62,9 @@ class MeshStreamPlaybackSession {
60
62
  async close() {
61
63
  if (this.closed)
62
64
  return;
65
+ if (this.isClientClosed()) {
66
+ return;
67
+ }
63
68
  if (!this.closing) {
64
69
  this.closing = (0, NativeMeshStreamP2P_1.requireNativeModule)()
65
70
  .closeSession(this.clientId, this.sessionId)
@@ -69,8 +74,9 @@ class MeshStreamPlaybackSession {
69
74
  return this.closing;
70
75
  }
71
76
  async withOpen(operation) {
72
- if (this.closed || this.closing)
77
+ if (this.closed || this.closing || this.isClientClosed()) {
73
78
  throw new Error('Playback session is closed.');
79
+ }
74
80
  return operation();
75
81
  }
76
82
  }
@@ -82,7 +88,12 @@ class MeshStreamClient {
82
88
  }
83
89
  async createPlaybackSession(options) {
84
90
  assertNonEmpty(options.url, 'options.url');
85
- return this.withOpen(async () => new MeshStreamPlaybackSession(await (0, NativeMeshStreamP2P_1.requireNativeModule)().createPlaybackSession(this.clientId, options)));
91
+ return this.withOpen(async () => {
92
+ const info = await (0, NativeMeshStreamP2P_1.requireNativeModule)().createPlaybackSession(this.clientId, options);
93
+ if (this.closed || this.closing)
94
+ throw new Error('MeshStream client is closed.');
95
+ return new MeshStreamPlaybackSession(info, () => this.closed || this.closing !== undefined);
96
+ });
86
97
  }
87
98
  async getStatus() {
88
99
  return this.withOpen(() => (0, NativeMeshStreamP2P_1.requireNativeModule)().getStatus(this.clientId));
@@ -106,6 +117,22 @@ class MeshStreamClient {
106
117
  }
107
118
  exports.MeshStreamClient = MeshStreamClient;
108
119
  exports.MeshStream = {
120
+ /** Returns the version of the native MeshStream SDK loaded by this application. */
121
+ async getSdkVersion() {
122
+ return (0, NativeMeshStreamP2P_1.requireNativeModule)().getSdkVersion();
123
+ },
124
+ /** Returns the React Native package version and the loaded native engine version. */
125
+ async getVersion() {
126
+ const platform = react_native_1.Platform.OS;
127
+ if (platform !== 'android' && platform !== 'ios') {
128
+ throw new Error(`react-native-meshstream does not support ${platform}.`);
129
+ }
130
+ return {
131
+ packageVersion: version_1.MESHSTREAM_REACT_NATIVE_VERSION,
132
+ nativeSdkVersion: await exports.MeshStream.getSdkVersion(),
133
+ platform,
134
+ };
135
+ },
109
136
  async create(config) {
110
137
  assertNonEmpty(config.key, 'config.key');
111
138
  const result = await (0, NativeMeshStreamP2P_1.requireNativeModule)().createClient(config);
@@ -4,6 +4,15 @@ export interface MeshStreamConfig {
4
4
  key: string;
5
5
  p2pEnabled?: boolean;
6
6
  }
7
+ /** Versions of the JavaScript wrapper and native engine used by this application. */
8
+ export interface MeshStreamVersionInfo {
9
+ /** Version of the installed react-native-meshstream package. */
10
+ packageVersion: string;
11
+ /** Version of the Android or Apple MeshStream engine loaded by the bridge. */
12
+ nativeSdkVersion: string;
13
+ /** Native platform that supplied nativeSdkVersion. */
14
+ platform: 'android' | 'ios';
15
+ }
7
16
  export interface PlaybackSessionOptions {
8
17
  url: string;
9
18
  contentId?: string;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Public npm package version. Keep this value aligned with package.json for each release.
3
+ * It is deliberately separate from the Android/iOS engine version.
4
+ */
5
+ export declare const MESHSTREAM_REACT_NATIVE_VERSION = "1.0.2";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MESHSTREAM_REACT_NATIVE_VERSION = void 0;
4
+ /**
5
+ * Public npm package version. Keep this value aligned with package.json for each release.
6
+ * It is deliberately separate from the Android/iOS engine version.
7
+ */
8
+ exports.MESHSTREAM_REACT_NATIVE_VERSION = '1.0.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-meshstream",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MeshStream P2P playback URL SDK for React Native",
5
5
  "main": "lib/typescript/index.js",
6
6
  "module": "lib/typescript/index.js",
@@ -22,10 +22,13 @@
22
22
  "scripts": {
23
23
  "build": "tsc --project tsconfig.json",
24
24
  "typecheck": "tsc --project tsconfig.json --noEmit",
25
+ "verify:version": "node scripts/verify-package-version.mjs",
25
26
  "test:ios": "node --test tests/node/ios-regression-guards.test.mjs",
26
27
  "verify:ios": "npm run test:ios && node scripts/verify-ios-package.mjs && node scripts/verify-ios-xctest.mjs",
27
28
  "verify:ios:public": "npm run test:ios && node scripts/verify-ios-package.mjs && node scripts/verify-ios-xctest.mjs --repo-update",
28
- "prepare": "npm run build"
29
+ "prepare": "npm run build",
30
+ "prepack": "npm run verify:version",
31
+ "test": "npm run build && node --test tests/node/*.test.mjs"
29
32
  },
30
33
  "keywords": [
31
34
  "react-native",
@@ -10,6 +10,7 @@ import type {
10
10
  } from './types';
11
11
 
12
12
  interface NativeMeshStreamP2P {
13
+ getSdkVersion(): Promise<string>;
13
14
  createClient(config: MeshStreamConfig): Promise<{ clientId: string; status: MeshStreamStatus }>;
14
15
  createPlaybackSession(clientId: string, options: PlaybackSessionOptions): Promise<MeshStreamSessionInfo>;
15
16
  preload(clientId: string, sessionId: string): Promise<boolean>;
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { DeviceEventEmitter, type EmitterSubscription } from 'react-native';
1
+ import { DeviceEventEmitter, Platform, type EmitterSubscription } from 'react-native';
2
2
 
3
3
  import { requireNativeModule } from './NativeMeshStreamP2P';
4
+ import { MESHSTREAM_REACT_NATIVE_VERSION } from './version';
4
5
  import type {
5
6
  MeshStreamConfig,
6
7
  MeshStreamDiagnostics,
@@ -9,6 +10,7 @@ import type {
9
10
  MeshStreamSessionInfo,
10
11
  MeshStreamStats,
11
12
  MeshStreamStatus,
13
+ MeshStreamVersionInfo,
12
14
  PlaybackSessionOptions,
13
15
  } from './types';
14
16
 
@@ -30,15 +32,17 @@ export class MeshStreamPlaybackSession {
30
32
  readonly playbackUrl: string;
31
33
  readonly playbackMode: MeshStreamSessionInfo['playbackMode'];
32
34
  readonly fallbackReason?: MeshStreamSessionInfo['fallbackReason'];
35
+ private readonly isClientClosed: () => boolean;
33
36
  private closed = false;
34
37
  private closing?: Promise<void>;
35
38
 
36
- constructor(info: MeshStreamSessionInfo) {
39
+ constructor(info: MeshStreamSessionInfo, isClientClosed: () => boolean = () => false) {
37
40
  this.clientId = info.clientId;
38
41
  this.sessionId = info.sessionId;
39
42
  this.playbackUrl = info.playbackUrl;
40
43
  this.playbackMode = info.playbackMode;
41
44
  this.fallbackReason = info.fallbackReason;
45
+ this.isClientClosed = isClientClosed;
42
46
  }
43
47
 
44
48
  async preload(): Promise<boolean> {
@@ -69,6 +73,9 @@ export class MeshStreamPlaybackSession {
69
73
 
70
74
  async close(): Promise<void> {
71
75
  if (this.closed) return;
76
+ if (this.isClientClosed()) {
77
+ return;
78
+ }
72
79
  if (!this.closing) {
73
80
  this.closing = requireNativeModule()
74
81
  .closeSession(this.clientId, this.sessionId)
@@ -79,7 +86,9 @@ export class MeshStreamPlaybackSession {
79
86
  }
80
87
 
81
88
  private async withOpen<T>(operation: () => Promise<T>): Promise<T> {
82
- if (this.closed || this.closing) throw new Error('Playback session is closed.');
89
+ if (this.closed || this.closing || this.isClientClosed()) {
90
+ throw new Error('Playback session is closed.');
91
+ }
83
92
  return operation();
84
93
  }
85
94
  }
@@ -95,9 +104,11 @@ export class MeshStreamClient {
95
104
 
96
105
  async createPlaybackSession(options: PlaybackSessionOptions): Promise<MeshStreamPlaybackSession> {
97
106
  assertNonEmpty(options.url, 'options.url');
98
- return this.withOpen(async () => new MeshStreamPlaybackSession(
99
- await requireNativeModule().createPlaybackSession(this.clientId, options),
100
- ));
107
+ return this.withOpen(async () => {
108
+ const info = await requireNativeModule().createPlaybackSession(this.clientId, options);
109
+ if (this.closed || this.closing) throw new Error('MeshStream client is closed.');
110
+ return new MeshStreamPlaybackSession(info, () => this.closed || this.closing !== undefined);
111
+ });
101
112
  }
102
113
 
103
114
  async getStatus(): Promise<MeshStreamStatus> {
@@ -122,6 +133,24 @@ export class MeshStreamClient {
122
133
  }
123
134
 
124
135
  export const MeshStream = {
136
+ /** Returns the version of the native MeshStream SDK loaded by this application. */
137
+ async getSdkVersion(): Promise<string> {
138
+ return requireNativeModule().getSdkVersion();
139
+ },
140
+
141
+ /** Returns the React Native package version and the loaded native engine version. */
142
+ async getVersion(): Promise<MeshStreamVersionInfo> {
143
+ const platform = Platform.OS;
144
+ if (platform !== 'android' && platform !== 'ios') {
145
+ throw new Error(`react-native-meshstream does not support ${platform}.`);
146
+ }
147
+ return {
148
+ packageVersion: MESHSTREAM_REACT_NATIVE_VERSION,
149
+ nativeSdkVersion: await MeshStream.getSdkVersion(),
150
+ platform,
151
+ };
152
+ },
153
+
125
154
  async create(config: MeshStreamConfig): Promise<MeshStreamClient> {
126
155
  assertNonEmpty(config.key, 'config.key');
127
156
  const result = await requireNativeModule().createClient(config);
package/src/types.ts CHANGED
@@ -6,6 +6,16 @@ export interface MeshStreamConfig {
6
6
  p2pEnabled?: boolean;
7
7
  }
8
8
 
9
+ /** Versions of the JavaScript wrapper and native engine used by this application. */
10
+ export interface MeshStreamVersionInfo {
11
+ /** Version of the installed react-native-meshstream package. */
12
+ packageVersion: string;
13
+ /** Version of the Android or Apple MeshStream engine loaded by the bridge. */
14
+ nativeSdkVersion: string;
15
+ /** Native platform that supplied nativeSdkVersion. */
16
+ platform: 'android' | 'ios';
17
+ }
18
+
9
19
  export interface PlaybackSessionOptions {
10
20
  url: string;
11
21
  contentId?: string;
package/src/version.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Public npm package version. Keep this value aligned with package.json for each release.
3
+ * It is deliberately separate from the Android/iOS engine version.
4
+ */
5
+ export const MESHSTREAM_REACT_NATIVE_VERSION = '1.0.2';
@@ -1,4 +1,5 @@
1
1
  #import <XCTest/XCTest.h>
2
+ #import <MeshStreamKit/MeshStreamKit.h>
2
3
 
3
4
  #import "../../ios/MeshStreamP2PValidation.h"
4
5
 
@@ -7,6 +8,10 @@
7
8
 
8
9
  @implementation MeshStreamP2PValidationTests
9
10
 
11
+ - (void)testNativeVersionIsAvailableBeforeCreatingClient {
12
+ XCTAssertEqualObjects([P2PSDK getSdkVersion], @"1.0.6");
13
+ }
14
+
10
15
  - (void)testTrimsURLsAndFiltersNonStringHeaders {
11
16
  XCTAssertEqualObjects(
12
17
  MeshStreamTrimmedString(@" content-42\n"),