# Architecture This document describes how `stringee-react-native-v2` is organised across its JavaScript and native layers, and how a request flows from JS through the bridge into the underlying Stringee SDK and back. It is intended for SDK maintainers — application developers do not need to read it. ## High-level layers ``` ┌─────────────────────────────────────────────────────────────┐ │ Application code (React Native, JS / TS) │ └─────────────────────────────────────────────────────────────┘ │ imports ▼ ┌─────────────────────────────────────────────────────────────┐ │ Public TS API (src/*.ts / src/*.tsx) │ │ StringeeClient · StringeeCall · StringeeCall2 · │ │ StringeeVideoView · listeners · helpers · enums │ └─────────────────────────────────────────────────────────────┘ │ NativeModules / NativeEventEmitter ▼ ┌─────────────────────────────────────────────────────────────┐ │ React Native bridge modules │ │ Android: RNStringeeClientModule / RNStringeeCallModule / │ │ RNStringeeCall2Module / RNStringeeVideoView* │ │ iOS: equivalent ObjC modules │ └─────────────────────────────────────────────────────────────┘ │ delegate to wrappers via uuid ▼ ┌─────────────────────────────────────────────────────────────┐ │ Wrappers (per-instance state holders) │ │ StringeeClientWrapper · StringeeCallWrapper · │ │ StringeeCall2Wrapper │ └─────────────────────────────────────────────────────────────┘ │ forward to SDK ▼ ┌─────────────────────────────────────────────────────────────┐ │ Stringee native SDK (com.stringee.sdk.android, iOS pod) │ └─────────────────────────────────────────────────────────────┘ ``` The same shape exists on iOS — only the file names and language differ. ## TypeScript layer The JS layer is a thin façade over the bridge. It is responsible for: 1. **Generating a `uuid`** when a JS object is constructed (see `StringeeClient.ts`, `StringeeCall.ts`, `StringeeCall2.ts`). The uuid is the handle that ties a JS instance to its native counterpart. 2. **Creating the native wrapper** by invoking `RNStringeeXxx.createWrapper(uuid, ...)` immediately in the constructor. This pre-registers the wrapper inside the native `StringeeManager` map so that subsequent method calls can find it. 3. **Forwarding method calls** to the native module, always passing `this.uuid` as the first argument. 4. **Bridging events back** to the user's listener via `NativeEventEmitter` subscriptions, filtered by `uuid` so that one global event stream can serve many concurrent instances. 5. **Wrapping callbacks** into Promises with `normalCallbackHandle(resolve, reject, fnName)` — the helper rejects with a `StringeeError` whenever the native side returns `(false, code, message)`. ### Event routing `src/helpers/StringeeHelper.ts` holds two important maps: - `clientEvents`, `callEvents` — per-platform names of the events emitted by the native side (e.g. `didChangeSignalingState` on iOS, `onSignalingStateChange` on Android). - `stringeeClientEvents`, `stringeeCallEvents`, `stringeeCall2Events` — the canonical JS-side names exposed on the listener interfaces. When a user calls `setListener(...)`, the JS class iterates the canonical list, looks up the platform-specific native name, subscribes via `NativeEventEmitter`, and dispatches the event into the matching listener method only when the event payload's `uuid` matches the instance's `uuid`. This keeps event delivery instance-scoped. ### Public surface [`index.ts`](../index.ts) re-exports every class, listener, helper, enum, and public type the SDK exposes. The package's `main` and `react-native` fields in [`package.json`](../package.json) point to generated CommonJS under `lib/commonjs`; `types` points to the root declaration proxy, which re-exports the generated declarations. Any new addition that should be visible to applications must be exported from `index.ts`. ## Native layer (Android) All Android sources live under [`android/src/main/java/com/stringeereactnative/`](../android/src/main/java/com/stringeereactnative). Logical grouping: | Package | Responsibility | |---------|---------------| | `module/` | React Native bridge modules — `@ReactMethod` entry points called by JS | | `wrapper/` | Per-instance state objects that hold a real Stringee SDK object plus its listener | | `view/` | `RNStringeeVideoView` and its `ViewManager` — the native side of `StringeeVideoView` | | `common/` | `Constant` (event / key strings), `Utils` (event emit, parsing, audio manager), `StringeeManager` (singleton holding the wrapper maps), `AudioManager` | | `RNStringeeReactPackage` | Registers all modules and view managers with React Native | ### Modules There is one module per JS class that needs a bridge: - [`RNStringeeClientModule`](../android/src/main/java/com/stringeereactnative/module/RNStringeeClientModule.java) — backs `StringeeClient`. - [`RNStringeeCallModule`](../android/src/main/java/com/stringeereactnative/module/RNStringeeCallModule.java) — backs `StringeeCall`. - [`RNStringeeCall2Module`](../android/src/main/java/com/stringeereactnative/module/RNStringeeCall2Module.java) — backs `StringeeCall2`. Each module is stateless; its methods take a `uuid`, look the wrapper up in `StringeeManager.getInstance().getXxxMap()`, and delegate. Example shape from `RNStringeeCallModule`: ```java @ReactMethod public void answer(String uuid, Callback callback) { StringeeCallWrapper callWrapper = StringeeManager.getInstance().getCallMap().get(uuid); if (callWrapper == null) { callback.invoke(false, -1, Constant.MESSAGE_STRINGEE_CALL_NOT_INITIALIZED, ""); return; } callWrapper.answer(callback); } ``` ### Wrappers A wrapper owns one Stringee SDK object and implements its listener interface. They hold per-instance state and translate between the SDK's listener callbacks and React Native events: - [`StringeeClientWrapper`](../android/src/main/java/com/stringeereactnative/wrapper/StringeeClientWrapper.java) — wraps `com.stringee.StringeeClient`. Largest of the three; covers connection, push, custom messaging, conversations, messages, live chat. - [`StringeeCallWrapper`](../android/src/main/java/com/stringeereactnative/wrapper/call/StringeeCallWrapper.java) — wraps `com.stringee.call.StringeeCall`. - [`StringeeCall2Wrapper`](../android/src/main/java/com/stringeereactnative/wrapper/call/StringeeCall2Wrapper.java) — wraps `com.stringee.call.StringeeCall2`. Two patterns to be aware of when editing wrappers: 1. **Per-method `Callback`** — most operations accept the JS callback as a parameter and use a local `StatusListener` whose `onSuccess` / `onError` invoke the callback in scope. Safe and self-contained. 2. **Field-stored `makeCallCallback`** — `makeCall` saves the JS callback into the field `makeCallCallback` so that the asynchronous `StringeeCallListener.onSignalingStateChange(CALLING)` / `onError` can complete it. Only the call wrappers use this pattern; it must always be guarded with a null check before invoking, and cleared after invoking, because: - For incoming calls (`answer` flow) the field is never set. - The RN bridge throws `IllegalStateException` if the same `Callback` is invoked twice. ### Manager and view - [`StringeeManager`](../android/src/main/java/com/stringeereactnative/common/StringeeManager.java) is a singleton holding three maps keyed by uuid: `clientMap`, `callMap`, `call2Map`. The JS constructor's `createWrapper` call ensures an entry exists; cleanup is performed by JS-side `clean(uuid)` methods. - [`RNStringeeVideoView`](../android/src/main/java/com/stringeereactnative/view/RNStringeeVideoView.java) and [`RNStringeeVideoViewManager`](../android/src/main/java/com/stringeereactnative/view/RNStringeeVideoViewManager.java) implement the native view backing `StringeeVideoView`. They look up the wrapper from `StringeeManager` (via the `uuid` prop) and attach a `TextureViewRenderer` to display either the local stream / track or a remote one, honouring `scalingType`. ### Utilities - [`Constant`](../android/src/main/java/com/stringeereactnative/common/Constant.java) — every event name, JSON key, and error message string lives here. New events go here first. - [`Utils`](../android/src/main/java/com/stringeereactnative/common/Utils.java) — `sendEvent(reactContext, name, data)` is the single entry point for emitting events to JS. Also hosts audio-manager helpers and JSON parsing for messages and conversations. ## End-to-end flow This walks through what happens when an application makes an outgoing call. The same shape applies to all other flows. ### Outgoing call (`StringeeCall.makeCall`) 1. **JS construction** - App: `const call = new StringeeCall({stringeeClient, from, to})`. - `StringeeCall` constructor generates `this.uuid` and calls `RNStringeeCall.createWrapper(uuid, stringeeClient.uuid)`. - On Android, the bridge invokes `RNStringeeCallModule.createWrapper`, which inserts a fresh `StringeeCallWrapper` (linked to the `StringeeClientWrapper`) into `StringeeManager.callMap`. 2. **Listener attachment** - App: `call.setListener(listener)`. - JS subscribes to platform-specific events (e.g. `onSignalingStateChange`) on `NativeEventEmitter`, filtering by `uuid`. 3. **Make call** - App: `await call.makeCall()`. - JS calls `RNStringeeCall.makeCall(uuid, params, callback)`. - `RNStringeeCallModule.makeCall` looks up the wrapper and forwards to `StringeeCallWrapper.makeCall(...)`. - Wrapper stores the JS callback in `makeCallCallback`, instantiates a real `StringeeCall`, sets itself as the SDK listener, configures audio manager, and invokes `stringeeCall.makeCall(StatusListener)`. 4. **Server acknowledges** - SDK fires `StringeeCallListener.onSignalingStateChange(stringeeCall, CALLING, ...)` on the wrapper. - Wrapper invokes `makeCallCallback(true, 0, "Success", callId, customData)` and clears the field. - The Promise on the JS side resolves; `onChangeSignalingState` is also dispatched to the application listener. 5. **State updates** - Subsequent state changes (`RINGING`, `ANSWERED`, `ENDED`) are emitted as events through `Utils.sendEvent` and routed by JS to the listener. 6. **Failure path** - If the SDK invokes `onError(code, desc)` instead, the wrapper invokes `makeCallCallback(false, code, desc, ...)` and clears it. - The JS Promise rejects with a `StringeeError`. ### Incoming call 1. SDK delivers an incoming `StringeeCall` to `StringeeClientWrapper`'s client listener. 2. Wrapper allocates a new uuid, creates a `StringeeCallWrapper` for it, registers in `StringeeManager.callMap`, sets the wrapper as the call's listener, and emits an `onIncomingCall` event whose payload includes the uuid plus call metadata. 3. JS receives the event, constructs a `StringeeCall` instance from the payload (passing the existing uuid so no new wrapper is created), and invokes `StringeeClientListener.onIncomingCall(client, call)`. 4. The application calls `initAnswer` / `answer` / `reject` as it would for an outgoing call. Note that `makeCallCallback` is never set on the incoming path — fields used by the listener must always be null-guarded. ## Adding a new SDK method When exposing a new native capability: 1. Decide which JS class owns it (`StringeeClient`, `StringeeCall`, `StringeeCall2`, etc.). 2. Add a JSDoc-annotated method on the JS class that calls the corresponding `RNStringeeXxx.(uuid, ...args, normalCallbackHandle(resolve, reject, ''))`. 3. Add a `@ReactMethod` to the matching Android module that resolves the wrapper from `StringeeManager` and delegates. 4. Implement the operation on the wrapper, using the per-method `Callback` pattern (no field-stored callbacks unless absolutely necessary). 5. Add an iOS counterpart with the same method name. The JS layer is platform-agnostic. 6. If the operation produces async events (not just a single-shot result), define an event name in `Constant.java`, add it to `callEvents` / `clientEvents` in `StringeeHelper.ts` for both platforms, and surface it on the matching listener interface. 7. Re-export anything new from [`index.ts`](../index.ts). ## Adding a new event 1. Define the JS-side canonical name (e.g. `onSomethingHappened`) and add it to the appropriate `stringeeXxxEvents` array in [`StringeeHelper.ts`](../src/helpers/StringeeHelper.ts). 2. Map it to the platform-specific native event names under `clientEvents` / `callEvents`. 3. On Android, declare the event string in [`Constant.java`](../android/src/main/java/com/stringeereactnative/common/Constant.java) and emit it from the wrapper using `Utils.sendEvent(reactContext, eventName, data)`. Always include `uuid` in the payload. 4. Add a typed callback property on the matching `*Listener` JS class so applications get IDE autocompletion.