omikit-plugin 4.1.5 → 4.1.7

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/src/omikit.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { NativeModules, Platform, NativeEventEmitter, DeviceEventEmitter, TurboModuleRegistry } from 'react-native';
1
+ import { NativeModules, Platform, DeviceEventEmitter, TurboModuleRegistry } from 'react-native';
2
2
  import type { Spec } from './NativeOmikitPlugin';
3
3
 
4
4
  const LINKING_ERROR =
@@ -36,23 +36,30 @@ const OmikitPlugin: Spec = resolvedModule || new Proxy(
36
36
  }
37
37
  );
38
38
 
39
- // Setup omiEmitter — safe for Old Arch, New Arch, and bridgeless mode
40
- const omiEmitter = (() => {
41
- if (Platform.OS !== 'ios') {
42
- return DeviceEventEmitter;
43
- }
44
- try {
45
- // Best case: NativeEventEmitter with resolved native module
46
- if (resolvedModule) {
47
- return new NativeEventEmitter(resolvedModule as any);
48
- }
49
- // New Arch without interop: NativeEventEmitter without module arg (RN 0.74+)
50
- return new NativeEventEmitter();
51
- } catch (_) {
52
- // Last resort fallback
53
- return DeviceEventEmitter;
54
- }
55
- })();
39
+ // Setup omiEmitter — works across Old Arch, New Arch (Fabric), and bridgeless.
40
+ //
41
+ // Why DeviceEventEmitter on BOTH platforms (instead of NativeEventEmitter on iOS)?
42
+ //
43
+ // `new NativeEventEmitter(module)` was designed for the legacy bridge where
44
+ // the JS-side emitter explicitly calls `module.addListener(eventName)` and
45
+ // `module.removeListeners(count)` to track listener count on the same Obj-C
46
+ // instance that calls `sendEvent`. In NewArch + Fabric, the JS side resolves
47
+ // the module through the codegen TurboModule wrapper, but listener tracking
48
+ // often does NOT round-trip back to the underlying `RCTEventEmitter`
49
+ // instance, so `sendEvent` short-circuits with "no listeners registered" and
50
+ // drops the event.
51
+ //
52
+ // `DeviceEventEmitter` is the global JS-side counterpart of the native
53
+ // `RCTDeviceEventEmitter` JS module. The native side reaches it via either:
54
+ // - `super.sendEvent(...)` (RCTEventEmitter routes through this module
55
+ // internally — works once RN has wired the dispatch path), OR
56
+ // - `bridge.enqueueJSCall("RCTDeviceEventEmitter", "emit", ...)`, OR
57
+ // - `callableJSModules.invokeModule("RCTDeviceEventEmitter", "emit", ...)`.
58
+ //
59
+ // All three eventually feed `DeviceEventEmitter` on the JS side, so
60
+ // subscribing here always sees the event regardless of architecture.
61
+ // This matches what the Android side has been doing successfully all along.
62
+ const omiEmitter = DeviceEventEmitter;
56
63
 
57
64
  /**
58
65
  * Starts the Omikit services.
@@ -246,12 +253,42 @@ export function toggleOmiVideo(): Promise<boolean> {
246
253
 
247
254
  /**
248
255
  * Logs the user out of the Omikit services.
256
+ *
257
+ * Resolves as soon as the native SDK call returns — does NOT wait for the
258
+ * HTTP `devices/remove` request or the local SIP state reset. Safe to use
259
+ * when you don't need to log in again immediately.
260
+ *
261
+ * If you plan to call {@link initCallWithUserPassword} or
262
+ * {@link initCallWithApiKey} right after, use {@link logoutAndWait} instead
263
+ * to avoid a race between devices/remove and devices/add.
264
+ *
249
265
  * @returns {Promise<boolean>} A promise that resolves to `true` if logout is successful.
250
266
  */
251
267
  export function logout(): Promise<boolean> {
252
268
  return OmikitPlugin.logout();
253
269
  }
254
270
 
271
+ /**
272
+ * Logs out and waits for the SDK to fully finish its cleanup before resolving.
273
+ *
274
+ * Uses the SDK's native completion callback (iOS `logoutWithCompletion:`,
275
+ * Android `OmiClient.logout(onCompleted)`) and only resolves once BOTH the
276
+ * HTTP `devices/remove` round-trip AND the local SIP state reset have fired.
277
+ * Use this whenever you plan to re-login immediately.
278
+ *
279
+ * @returns {Promise<boolean>} Resolves with the backend success flag. Even
280
+ * when the backend call fails (network error, timeout) the local state has
281
+ * still been cleaned up — you can safely proceed to re-login.
282
+ *
283
+ * @example
284
+ * // Switch extensions cleanly
285
+ * await logoutAndWait();
286
+ * await initCallWithUserPassword(newCredentials);
287
+ */
288
+ export function logoutAndWait(): Promise<boolean> {
289
+ return OmikitPlugin.logoutAndWait();
290
+ }
291
+
255
292
  /**
256
293
  * Registers for video call events.
257
294
  * @returns {Promise<boolean>} A promise that resolves to `true` if registration is successful.
@@ -518,6 +555,131 @@ export function getVoipToken(): Promise<string | null> {
518
555
  return OmikitPlugin.getVoipToken();
519
556
  }
520
557
 
558
+ // MARK: - Backend Device Registration Check APIs
559
+ // Mirrors OmiKit iOS 1.11.19 / OmiSDK Android 2.6.20+
560
+
561
+ /**
562
+ * Single device entry returned by {@link getOmiDevices}.
563
+ * Native bridge returns snake_case (matches SDK raw shape on both platforms);
564
+ * this JS layer normalizes to camelCase so consumers don't deal with two cases.
565
+ *
566
+ * `deviceType` is normalized to `"ios"` / `"android"` (server raw int converted).
567
+ * `sipNumber` is injected from the local session, not from the server payload.
568
+ */
569
+ export type OmiDeviceInfo = {
570
+ deviceId?: string;
571
+ token?: string;
572
+ deviceType?: 'ios' | 'android' | string;
573
+ voipToken?: string;
574
+ appId?: string;
575
+ createdTime?: number;
576
+ projectId?: string;
577
+ sipNumber?: string;
578
+ };
579
+
580
+ /**
581
+ * Raw native payload — snake_case as returned by the iOS/Android SDKs.
582
+ * Kept private to this module; consumers receive the camelCase {@link OmiDeviceInfo}.
583
+ */
584
+ type OmiDeviceInfoNative = {
585
+ device_id?: string;
586
+ token?: string;
587
+ device_type?: 'ios' | 'android' | string;
588
+ voip_token?: string;
589
+ app_id?: string;
590
+ created_time?: number;
591
+ project_id?: string;
592
+ sipNumber?: string;
593
+ };
594
+
595
+ function normalizeDevice(d: OmiDeviceInfoNative): OmiDeviceInfo {
596
+ return {
597
+ deviceId: d.device_id,
598
+ token: d.token,
599
+ deviceType: d.device_type,
600
+ voipToken: d.voip_token,
601
+ appId: d.app_id,
602
+ createdTime: d.created_time,
603
+ projectId: d.project_id,
604
+ sipNumber: d.sipNumber,
605
+ };
606
+ }
607
+
608
+ /**
609
+ * Fetch the list of devices currently registered on the OMI backend for the
610
+ * active SIP user. Returns an empty array when not logged in, on network
611
+ * failure, or on parse error — never throws.
612
+ *
613
+ * Recommended usage: call once after login or on app foreground — do NOT
614
+ * call in tight loops (each call performs a fresh HTTP request, no caching).
615
+ *
616
+ * @returns {Promise<OmiDeviceInfo[]>} Devices registered on backend (camelCase).
617
+ */
618
+ export async function getOmiDevices(): Promise<OmiDeviceInfo[]> {
619
+ const raw = (await OmikitPlugin.getOmiDevices()) as OmiDeviceInfoNative[] | null;
620
+ if (!Array.isArray(raw)) return [];
621
+ return raw.map(normalizeDevice);
622
+ }
623
+
624
+ /**
625
+ * Verify whether THIS device (local `device_id` + `app_id`) is registered on
626
+ * the OMI backend for the current SIP user. Returns `false` early when not
627
+ * logged in, avoiding an unnecessary HTTP round trip.
628
+ *
629
+ * Use this to detect divergence between local session and backend state —
630
+ * e.g. after app reinstall, backend cleanup, or device migration.
631
+ */
632
+ export function isCurrentDeviceRegistered(): Promise<boolean> {
633
+ return OmikitPlugin.isCurrentDeviceRegistered();
634
+ }
635
+
636
+ /**
637
+ * Convenience guard built on top of {@link isCurrentDeviceRegistered}.
638
+ * Returns `true` when a SIP user is set locally but no matching device exists
639
+ * on the backend — i.e. the local session is stale and the user must logout
640
+ * + login again to re-register.
641
+ *
642
+ * Returns `false` when not logged in (nothing to recover) or when the
643
+ * registration is intact. Recommended call sites: app foreground, before
644
+ * critical operations.
645
+ */
646
+ export function needsReLogin(): Promise<boolean> {
647
+ return OmikitPlugin.needsReLogin();
648
+ }
649
+
650
+ /**
651
+ * Look up the SIP number associated with a given `deviceId` in a devices list
652
+ * returned by {@link getOmiDevices}.
653
+ *
654
+ * Use case: after fetching the backend device list, verify which SIP extension
655
+ * is currently bound to THIS device — useful when one user can hold multiple
656
+ * extensions, or when troubleshooting a wrong-extension issue.
657
+ *
658
+ * @param devices - Array returned by {@link getOmiDevices}.
659
+ * @param deviceId - Local device identifier (typically from {@link getDeviceId}).
660
+ * @returns The `sipNumber` of the matched entry, or `null` if no device in the
661
+ * list has that `deviceId` (or if inputs are missing/empty).
662
+ *
663
+ * @example
664
+ * const devices = await getOmiDevices();
665
+ * const localDeviceId = await getDeviceId();
666
+ * const sip = findSipNumberByDeviceId(devices, localDeviceId);
667
+ * if (sip == null) {
668
+ * // backend has no record of this device — session stale, ask user to re-login
669
+ * } else if (sip !== expectedExtension) {
670
+ * // device is bound to a different extension than expected
671
+ * }
672
+ */
673
+ export function findSipNumberByDeviceId(
674
+ devices: OmiDeviceInfo[] | null | undefined,
675
+ deviceId: string | null | undefined
676
+ ): string | null {
677
+ if (!Array.isArray(devices) || devices.length === 0) return null;
678
+ if (!deviceId) return null;
679
+ const match = devices.find((d) => d.deviceId === deviceId);
680
+ return match?.sipNumber ?? null;
681
+ }
682
+
521
683
  /**
522
684
  * Gets the current keep-alive connection status.
523
685
  * @returns {Promise<any>} Keep-alive status object.
@@ -68,9 +68,21 @@ declare module 'omikit-plugin' {
68
68
  projectId?: string;
69
69
  }): Promise<boolean>;
70
70
 
71
- /** Logout and unregister SIP */
71
+ /**
72
+ * Logout and unregister SIP.
73
+ * Resolves before HTTP `devices/remove` completes — use {@link logoutAndWait}
74
+ * if you plan to re-login immediately.
75
+ */
72
76
  export function logout(): Promise<boolean>;
73
77
 
78
+ /**
79
+ * Logout and wait for the SDK to fully clean up.
80
+ * Resolves only after HTTP `devices/remove` AND the local SIP state reset
81
+ * have finished. Use this before re-login to avoid races between
82
+ * `devices/remove` and `devices/add`.
83
+ */
84
+ export function logoutAndWait(): Promise<boolean>;
85
+
74
86
  // ============================================
75
87
  // CALL CONTROL
76
88
  // ============================================
@@ -275,6 +287,60 @@ declare module 'omikit-plugin' {
275
287
  /** Get VoIP push token (iOS only, returns null on Android) */
276
288
  export function getVoipToken(): Promise<string | null>;
277
289
 
290
+ // ============================================
291
+ // BACKEND DEVICE REGISTRATION CHECK (v4.1.8+)
292
+ // OmiKit iOS 1.11.19 / OmiSDK Android 2.6.20+
293
+ // ============================================
294
+
295
+ /**
296
+ * Single device entry returned by {@link getOmiDevices}.
297
+ * Native bridge returns snake_case; the JS layer normalizes to camelCase.
298
+ * `deviceType` is normalized to `"ios"` / `"android"`.
299
+ * `sipNumber` is injected from the local session, not from the server payload.
300
+ */
301
+ export type OmiDeviceInfo = {
302
+ deviceId?: string;
303
+ token?: string;
304
+ deviceType?: 'ios' | 'android' | string;
305
+ voipToken?: string;
306
+ appId?: string;
307
+ createdTime?: number;
308
+ projectId?: string;
309
+ sipNumber?: string;
310
+ };
311
+
312
+ /**
313
+ * Fetch the list of devices currently registered on the OMI backend for the
314
+ * active SIP user. Returns `[]` when not logged in or on any failure — never throws.
315
+ * No caching — call once after login or on app foreground, not in loops.
316
+ */
317
+ export function getOmiDevices(): Promise<OmiDeviceInfo[]>;
318
+
319
+ /**
320
+ * Verify whether THIS device (local `device_id` + `app_id`) is registered on
321
+ * the OMI backend for the current SIP user. Returns `false` early when not logged in.
322
+ */
323
+ export function isCurrentDeviceRegistered(): Promise<boolean>;
324
+
325
+ /**
326
+ * Returns `true` when a SIP user is set locally but no matching device exists on
327
+ * the backend — i.e. the local session is stale and the user must logout + login
328
+ * again to re-register. Returns `false` when not logged in or registration is intact.
329
+ */
330
+ export function needsReLogin(): Promise<boolean>;
331
+
332
+ /**
333
+ * Look up the SIP number associated with a given `deviceId` in a devices list
334
+ * returned by {@link getOmiDevices}. Returns `null` if no match (or inputs empty).
335
+ *
336
+ * Useful for verifying which extension THIS device is bound to on the backend,
337
+ * especially when one user can hold multiple extensions.
338
+ */
339
+ export function findSipNumberByDeviceId(
340
+ devices: OmiDeviceInfo[] | null | undefined,
341
+ deviceId: string | null | undefined
342
+ ): string | null;
343
+
278
344
  // ============================================
279
345
  // NOTIFICATION CONTROL
280
346
  // ============================================