react-native-adapty
Version:
Adapty React Native SDK
148 lines (121 loc) • 3.95 kB
text/typescript
import {
EmitterSubscription,
NativeEventEmitter,
NativeModules,
} from 'react-native';
import { AdaptyError } from '@/adapty-error';
import { LogContext } from '@/logger';
import { parseMethodResult } from '@/coders';
import {
AdaptyType,
parseCommonEvent,
parsePaywallEvent,
} from '@/coders/parse';
const KEY_HANDLER_NAME = 'HANDLER';
export class NativeRequestHandler<
Method extends string,
Params extends string,
> {
_module: (typeof NativeModules)[string];
_request: (method: Method, params: Record<string, string>) => Promise<string>;
_emitter: NativeEventEmitter;
_listeners: Set<EmitterSubscription>;
constructor(moduleName: string) {
this._module = NativeModules[moduleName];
if (!this._module) {
throw new Error('Adapty NativeModule is not defined');
}
this._emitter = new NativeEventEmitter(this._module);
this._listeners = new Set();
// Handler name is defined in native module
const constants = this._module.getConstants() as Record<string, string>;
const handlerName = constants[KEY_HANDLER_NAME];
if (!handlerName) {
throw new Error(
`Adapty NativeModule does not expose "${KEY_HANDLER_NAME}" constant`,
);
}
this._request = this._module[handlerName] as (
method: Method,
params: Record<string, string>,
) => Promise<string>;
if (!this._request) {
throw new Error('Adapty native handler is not defined');
}
}
async request<T>(
method: Method,
params: Params,
resultType: AdaptyType,
ctx?: LogContext,
) {
const log = ctx?.bridge({ methodName: `fetch/${method}` });
log?.start({ method, params });
try {
const response = await this._request(method, { args: params });
const result = parseMethodResult<T>(response, resultType, ctx);
log?.success({ response });
return result;
} catch (error) {
log?.success({ error });
if (typeof error !== 'object' || error === null) {
throw AdaptyError.failedToDecodeNativeError(
`Unexpected native error type. "Expected object", but got "${typeof error}"`,
error,
);
}
const errorObj = error as Record<string, any>;
if (!errorObj.hasOwnProperty('message') || !errorObj['message']) {
throw AdaptyError.failedToDecodeNativeError(
'Native error does not have expected "message" property',
error,
);
}
throw errorObj;
}
}
addRawEventListener<
Event extends string,
Cb extends (event: any) => void | Promise<void>,
>(event: Event, cb: Cb): EmitterSubscription {
const subscription = this._emitter.addListener(event, cb);
this._listeners.add(subscription);
return subscription;
}
addEventListener<Event extends string, CallbackData>(
event: Event,
cb: (this: { rawValue: any }, data: CallbackData) => void | Promise<void>,
): EmitterSubscription {
const consumeNativeCallback = (...data: string[]) => {
const ctx = new LogContext();
const log = ctx.event({ methodName: event });
log.start(data);
let rawValue = null;
const args = data.map(arg => {
try {
const commonEvent = parseCommonEvent(event, arg, ctx);
if (commonEvent) return commonEvent;
const paywallEvent = parsePaywallEvent(arg, ctx);
try {
rawValue = JSON.parse(arg);
} catch {}
return paywallEvent;
} catch (error) {
log.failed({ error });
throw error;
}
});
cb.apply({ rawValue }, args as any);
};
const subscription = this._emitter.addListener(
event,
consumeNativeCallback,
);
this._listeners.add(subscription);
return subscription;
}
removeAllEventListeners(): void {
this._listeners?.forEach(listener => listener.remove());
this._listeners?.clear();
}
}