Skip to content

Commit 4dc687f

Browse files
Yaswanth Polisettianuragsingh001syuvrajjsingh0
authored andcommitted
feat: support multiple HyperServices instances
Adds a HyperServiceInstance class that creates independent, keyed HyperServices objects alongside the existing module-wide singleton API, each with its own event channel. This PR covers the core integration: create, initiate, process, terminate, back-press and status helpers. Merchant views, HyperFragmentView and processWithActivity/openPaymentPage per instance follow in a separate PR. JS: - HyperServiceInstance: constructor(tenantId?, clientId?), initiate, process, terminate, onBackPressed, isNull, isInitialised and getHyperEventString() as the instance's event channel - lazy linking error; platform guards for iOS-only gaps Android: - keyed create/initiate/process/terminate/back-press/isInitialised with per-key event emission - permission and activity results forwarded to every live instance - invalidate() terminates and clears keyed state on React reloads - every skipped call logged to logcat alongside SdkTracker telemetry - legacy single-instance API behaviour preserved exactly - resolve ReactHost reflectively so the module compiles on RN < 0.74 - fix rnVersion Groovy scoping crash for newArchEnabled=false consumers iOS: - keyed methods mirrored with per-key delegate retention, dynamic supportedEvents, synchronized dict access, and invalidate cleanup - podspec: RN version detection for development layouts; folly compiler flags applied Example app: - toolchain modernized: React Native 0.79.7 (react 19), AGP 8.9 via RNGP, Gradle 8.13, compileSdk 36, Kotlin 2.0.21, RN 0.79 Podfile, Flipper removed; Jetifier re-enabled for the Juspay micro-SDKs with RN AARs on the ignorelist - demo flows for multiple instances: create/select instances, initiate, process and terminate per instance with per-instance event listeners - generateSign accepts PKCS#8/PKCS#1 keys with or without PEM armor on both platforms, with errors surfaced in the UI Docs: - README section covering merchant integration for multiple instances Co-authored-by: Anurag Singh <as6003688@gmail.com> Co-authored-by: yuvrajjsingh0 <yuvraj.singh@juspay.in>
1 parent db324ed commit 4dc687f

33 files changed

Lines changed: 5093 additions & 2383 deletions

File tree

README.md

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ const { HyperSdkReact } = NativeModules;
110110
export default HyperSdkReact as HyperSdkReactType;
111111
```
112112
113+
For apps that need more than one tenant / client in the same session, the module also exports a
114+
`HyperServiceInstance` class. See [Multiple HyperServices Instances](#multiple-hyperservices-instances).
115+
116+
```ts
117+
import HyperSdkReact, { HyperServiceInstance } from 'hyper-sdk-react';
118+
```
119+
113120
### Import HyperSDK
114121
115122
```ts
@@ -134,6 +141,9 @@ This method creates an instance of `HyperServices` class in the React Bridge Mod
134141
HyperSdkReact.createHyperServices();
135142
```
136143
144+
This creates a single, module-wide instance. If you need several independent instances (multiple
145+
tenants / clients), use [`HyperServiceInstance`](#multiple-hyperservices-instances) instead.
146+
137147
### Step-2: Initiate
138148
139149
This method should be called on the render of the host screen. This will boot up the SDK and start the Hyper engine. It takes a `stringified JSON` as its argument which will contain the base parameters for the entire session and remains static throughout one SDK instance lifetime.
@@ -340,7 +350,7 @@ If your view dynamically computes height. Height can be obtained by adding the f
340350
useLayoutEffect(() => {
341351
if (ref.current?.measure) {
342352
ref.current.measure((x, y, width, height, pageX, pageY) => {
343-
HyperServices.updateMerchantViewHeight(HyperSdkReact.JuspayHeader, height);
353+
HyperSdkReact.updateMerchantViewHeight(HyperSdkReact.JuspayHeader, height);
344354
});
345355
}
346356
}, []);
@@ -369,6 +379,166 @@ If your `AppDelegate` is in `swift` and you are using react native version great
369379
370380
```
371381
382+
## Multiple HyperServices Instances
383+
384+
By default the module keeps a **single** `HyperServices` object, and every top-level API
385+
(`HyperSdkReact.initiate`, `HyperSdkReact.process`, …) operates on it. If your app needs to talk to
386+
more than one tenant / client in the same session — for example a marketplace that switches between
387+
two Juspay merchants, or a super-app hosting several sub-brands — you can create independent
388+
instances with `HyperServiceInstance`.
389+
390+
Each instance owns its own `HyperServices` object natively and **emits its events on its own channel**.
391+
This is the only real difference in the integration: the event name is no longer the constant
392+
`HyperSdkReact.HyperEvent`, it is the instance's own key, returned by `getHyperEventString()`.
393+
394+
### Step-1: Import
395+
396+
```ts
397+
import HyperSdkReact, { HyperServiceInstance } from 'hyper-sdk-react';
398+
```
399+
400+
### Step-2: Create an instance
401+
402+
Replaces `HyperSdkReact.createHyperServices()` / `HyperSdkReact.createHyperServicesWithTenantId()`.
403+
The constructor allocates the native object immediately and generates the instance key.
404+
405+
```ts
406+
// Default tenant / client
407+
const instance = new HyperServiceInstance();
408+
409+
// Explicit tenant and client
410+
const tenantInstance = new HyperServiceInstance(tenantId, clientId);
411+
```
412+
413+
On Android the native object can only be created while an activity is in the foreground, and creation
414+
happens asynchronously on the native side — so a synchronous `isNull()` right after the constructor
415+
will still report `true`. If you want to confirm creation succeeded, check `isNull()` on a later tick
416+
(for example just before calling `initiate`). Hold on to the object for the whole
417+
lifetime of the flow — an instance can only be addressed through the reference you keep in JS. A common pattern is a `Map` keyed by `getHyperEventString()`:
418+
419+
```ts
420+
const instances = new Map<string, HyperServiceInstance>();
421+
const instance = new HyperServiceInstance(tenantId, clientId);
422+
instances.set(instance.getHyperEventString(), instance);
423+
```
424+
425+
`preFetch` stays global and is still called once as `HyperSdkReact.preFetch(...)` — it is not per-instance.
426+
427+
### Step-3: Listen to events from this instance
428+
429+
Register the listener **before** calling `initiate`, using the instance's key as the event name.
430+
Events for one instance are never delivered on another instance's channel, nor on `HyperSdkReact.HyperEvent`.
431+
432+
```ts
433+
componentDidMount() {
434+
const eventEmitter = new NativeEventEmitter(NativeModules.HyperSdkReact);
435+
436+
this.eventListener = eventEmitter.addListener(
437+
this.instance.getHyperEventString(),
438+
(resp) => {
439+
const data = JSON.parse(resp);
440+
switch (data.event || '') {
441+
case 'show_loader':
442+
break;
443+
case 'hide_loader':
444+
break;
445+
case 'initiate_result':
446+
console.log('initiate_result: ', data.payload || {});
447+
break;
448+
case 'process_result':
449+
console.log('process_result: ', data.payload || {});
450+
break;
451+
default:
452+
console.log('Unknown Event', data);
453+
}
454+
}
455+
);
456+
}
457+
458+
componentWillUnmount() {
459+
this.eventListener.remove();
460+
}
461+
```
462+
463+
**Note**: the key is generated per instance, so it must be threaded through to whichever screen
464+
listens for the response. If you navigate to another screen to run `process`, pass the instance (or
465+
its key) through the navigation params and subscribe there.
466+
467+
### Step-4: Initiate and Process
468+
469+
Same payloads as the single-instance API — only the receiver changes.
470+
471+
```ts
472+
instance.initiate(JSON.stringify(initiatePayload));
473+
instance.process(JSON.stringify(processPayload));
474+
```
475+
476+
### Step-5: Android hardware back-press handling
477+
478+
Back press must be offered to the instance that currently owns the screen. With more than one live
479+
instance, track which one is in the foreground and delegate to that one:
480+
481+
```ts
482+
BackHandler.addEventListener('hardwareBackPress', () => {
483+
const instance = this.activeInstance;
484+
return !!instance && !instance.isNull() && instance.onBackPressed();
485+
});
486+
```
487+
488+
### Step-6: Android permissions and activity results
489+
490+
Unchanged and still done once, at the activity level — the snippets in
491+
[Step-6](#step-6-android-permissions-handling) of the single-instance guide apply as-is. No
492+
per-instance wiring is required in `MainActivity`.
493+
494+
Permission and activity results are offered to every live instance; the SDK routes them internally by
495+
request code, so no per-instance wiring is needed.
496+
497+
### Step-7: Terminate
498+
499+
Terminate each instance you created. Terminating one instance does not affect the others.
500+
501+
```ts
502+
instance.terminate();
503+
```
504+
505+
After `terminate()` the key is released natively; also remove the JS listener registered in
506+
[Step-3](#step-3-listen-to-events-from-this-instance) and drop your reference to the object, otherwise
507+
the instance is retained on the JS side.
508+
509+
### Helpers
510+
511+
```ts
512+
const isNull: boolean = instance.isNull(); // native object missing / already terminated
513+
instance.isInitialised().then((init: boolean) => {}); // initiate has completed
514+
const key: string = instance.getHyperEventString(); // event channel for this instance
515+
```
516+
517+
### Instance API
518+
519+
```ts
520+
class HyperServiceInstance {
521+
constructor(tenantId?: string, clientId?: string);
522+
initiate(data: string): void;
523+
process(data: string): void;
524+
terminate(): void;
525+
onBackPressed(): boolean;
526+
isNull(): boolean;
527+
isInitialised(): Promise<boolean>;
528+
getHyperEventString(): string;
529+
}
530+
```
531+
532+
### Limitations
533+
534+
- **`processWithActivity`, `openPaymentPage`, merchant views** (`JuspayHeader`, `JuspayFooter`, …)
535+
and **`HyperFragmentView`** are not instance-aware yet; they operate on the single-instance
536+
(module-level) API. Use `HyperSdkReact.*` for flows that need them.
537+
- **Permission and activity results** are offered to every live instance and routed internally by
538+
request code. If two instances trigger flows that wait on the same Android request code at the same
539+
moment, results cannot be disambiguated — avoid two simultaneous permission-driven flows.
540+
- The single-instance `HyperSdkReact.*` API and `HyperServiceInstance` can coexist in one app.
541+
372542
## Payload Structure
373543
374544
Please refer [here for Express Checkout SDK](https://developer.juspay.in/v2.0/docs/payload) and [here for Payment Page SDK](https://developer.juspay.in/v4.0/docs/payload), for all request and response payload structure.

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def getRNVersion() {
5959
}
6060

6161

62-
def rnVersion = getRNVersion()
62+
ext.rnVersion = getRNVersion()
6363
println "Found react native version as ${rnVersion}"
6464

6565
def isNewArchitectureEnabled() {

0 commit comments

Comments
 (0)