@inno-isw/react-native-rtp 0.0.0-stage → 0.1.0

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.
Files changed (55) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +362 -2
  3. package/lib/module/client.js +218 -0
  4. package/lib/module/client.js.map +1 -0
  5. package/lib/module/constants.js +44 -0
  6. package/lib/module/constants.js.map +1 -0
  7. package/lib/module/emitter.js +46 -0
  8. package/lib/module/emitter.js.map +1 -0
  9. package/lib/module/index.js +16 -0
  10. package/lib/module/index.js.map +1 -0
  11. package/lib/module/mock/makeRtpPayload.js +51 -0
  12. package/lib/module/mock/makeRtpPayload.js.map +1 -0
  13. package/lib/module/mock/mockTransport.js +82 -0
  14. package/lib/module/mock/mockTransport.js.map +1 -0
  15. package/lib/module/package.json +1 -0
  16. package/lib/module/push.js +96 -0
  17. package/lib/module/push.js.map +1 -0
  18. package/lib/module/react/RtpProvider.js +142 -0
  19. package/lib/module/react/RtpProvider.js.map +1 -0
  20. package/lib/module/types.js +4 -0
  21. package/lib/module/types.js.map +1 -0
  22. package/lib/module/validate.js +80 -0
  23. package/lib/module/validate.js.map +1 -0
  24. package/lib/typescript/package.json +1 -0
  25. package/lib/typescript/src/client.d.ts +11 -0
  26. package/lib/typescript/src/client.d.ts.map +1 -0
  27. package/lib/typescript/src/constants.d.ts +45 -0
  28. package/lib/typescript/src/constants.d.ts.map +1 -0
  29. package/lib/typescript/src/emitter.d.ts +15 -0
  30. package/lib/typescript/src/emitter.d.ts.map +1 -0
  31. package/lib/typescript/src/index.d.ts +14 -0
  32. package/lib/typescript/src/index.d.ts.map +1 -0
  33. package/lib/typescript/src/mock/makeRtpPayload.d.ts +17 -0
  34. package/lib/typescript/src/mock/makeRtpPayload.d.ts.map +1 -0
  35. package/lib/typescript/src/mock/mockTransport.d.ts +29 -0
  36. package/lib/typescript/src/mock/mockTransport.d.ts.map +1 -0
  37. package/lib/typescript/src/push.d.ts +41 -0
  38. package/lib/typescript/src/push.d.ts.map +1 -0
  39. package/lib/typescript/src/react/RtpProvider.d.ts +56 -0
  40. package/lib/typescript/src/react/RtpProvider.d.ts.map +1 -0
  41. package/lib/typescript/src/types.d.ts +142 -0
  42. package/lib/typescript/src/types.d.ts.map +1 -0
  43. package/lib/typescript/src/validate.d.ts +22 -0
  44. package/lib/typescript/src/validate.d.ts.map +1 -0
  45. package/package.json +141 -4
  46. package/src/client.ts +239 -0
  47. package/src/constants.ts +49 -0
  48. package/src/emitter.ts +53 -0
  49. package/src/index.tsx +43 -0
  50. package/src/mock/makeRtpPayload.ts +66 -0
  51. package/src/mock/mockTransport.ts +103 -0
  52. package/src/push.ts +109 -0
  53. package/src/react/RtpProvider.tsx +200 -0
  54. package/src/types.ts +161 -0
  55. package/src/validate.ts +93 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Quickteller
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,363 @@
1
- # Temporary Holding Version
1
+ # @inno-isw/react-native-rtp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Request to Pay (RTP) SDK for React Native.
4
+
5
+ `@inno-isw/react-native-rtp` owns the **business logic** of a Request to Pay flow — payload
6
+ validation, de-duplication, expiry, recipient matching, normalized status, and
7
+ the accept / decline / status calls. The **host app owns all UI**: it renders the
8
+ bottom sheet, collects the PIN, and decides navigation.
9
+
10
+ It is pure JS/TS with **zero native code** and **zero runtime dependencies**
11
+ (only `react` / `react-native` peer deps). Every external effect is injected, so
12
+ the same contract can be re-implemented on Kotlin or Swift.
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```sh
19
+ yarn add @inno-isw/react-native-rtp
20
+ # or: npm install @inno-isw/react-native-rtp
21
+ ```
22
+
23
+ Peer dependencies: `react`, `react-native`.
24
+
25
+ ### Publishing (maintainers)
26
+
27
+ The package is consumed as a normal npm dependency — do **not** link it locally.
28
+
29
+ ```sh
30
+ yarn install
31
+ yarn prepare # builds lib/ (bob build) via the prepare script
32
+ npm publish # publishes @inno-isw/react-native-rtp to the npm registry
33
+ ```
34
+
35
+ Consumer (e.g. QTM) then installs it like any other package:
36
+
37
+ ```sh
38
+ yarn add @inno-isw/react-native-rtp
39
+ # or: npm install @inno-isw/react-native-rtp
40
+ ```
41
+
42
+ ---
43
+
44
+ ## The portable contract
45
+
46
+ The SDK never sees a push notification. It sees a **decoded RTP payload**. Each
47
+ platform is responsible for mapping *its* push shape (RTCP, raw FCM, APNs,
48
+ in-house PN) into this structure.
49
+
50
+ ```ts
51
+ interface RtpPayload {
52
+ version?: string; // schema version; unknown values are ignored
53
+ type: 'RTP'; // discriminator
54
+ rtpId: string; // stable id — the idempotency key
55
+ amount: string; // decimal string, e.g. "5000.00"
56
+ currency: string; // ISO 4217, e.g. "NGN"
57
+ merchant: { name: string; accountNumber?: string; bank?: string; logoUrl?: string };
58
+ transactionRef?: string;
59
+ expiresAt: string; // ISO 8601
60
+ recipient?: string; // alias the request is addressed to
61
+ [key: string]: unknown; // forward-compatible passthrough
62
+ }
63
+ ```
64
+
65
+ > Unknown fields are **ignored, not rejected**, so older SDKs keep working when
66
+ > the backend adds fields.
67
+
68
+ ### `isRtp(payload)`
69
+
70
+ The type guard operates on the **decoded payload**, not the notification
71
+ envelope:
72
+
73
+ ```ts
74
+ payload.type === 'RTP' && typeof payload.rtpId === 'string';
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Quick start
80
+
81
+ ### 1. Create the client (injected seams)
82
+
83
+ ```ts
84
+ import { createRtpClient } from '@inno-isw/react-native-rtp';
85
+ import { api } from './api'; // your existing HTTP client / ky / axios instance
86
+
87
+ export const rtpClient = createRtpClient({
88
+ // (required) minimal HTTP contract — the SDK never imports a network lib
89
+ transport: {
90
+ post: (path, body, headers) => api.post(path, { json: body, headers }),
91
+ get: (path, headers) => api.get(path, { headers }),
92
+ },
93
+
94
+ // (optional) auth headers, called per request so tokens/MACs stay fresh
95
+ getAuthHeaders: async () => ({ Authorization: `Bearer ${await getToken()}` }),
96
+
97
+ // (optional) drives the auth gate + recipient matching
98
+ getCurrentUser: () => authStore.user?.email ?? null,
99
+
100
+ // (optional) injectable clock for deterministic expiry
101
+ now: () => Date.now(),
102
+
103
+ // (optional) diagnostics sink
104
+ logger: (...args) => console.log('[rtp]', ...args),
105
+
106
+ // (optional) REST base path, default "/rtp"
107
+ basePath: '/rtp',
108
+ });
109
+ ```
110
+
111
+ ### 2. Normalize your push notification → payload
112
+
113
+ This is the **only** platform-specific input seam. Use the reference helper, or
114
+ write your own:
115
+
116
+ ```ts
117
+ import { mapPushToRtp } from '@inno-isw/react-native-rtp';
118
+
119
+ // Works with RTCP/FCM shapes, including app_data JSON unwrapping:
120
+ const payload = mapPushToRtp(notification);
121
+ if (payload) {
122
+ rtpClient.receive(payload);
123
+ }
124
+ ```
125
+
126
+ For a bespoke PN system, pass your own data accessor:
127
+
128
+ ```ts
129
+ const payload = mapPushToRtp(envelope, (e) => e.customKeys);
130
+ ```
131
+
132
+ ### 3. Mount the provider and build your UI
133
+
134
+ ```tsx
135
+ import { RtpProvider, useRtp, RTP_STATUS } from '@inno-isw/react-native-rtp';
136
+ import { rtpClient } from './rtpClient';
137
+
138
+ export default function App() {
139
+ const isAuthenticated = useAuthStore((s) => Boolean(s.user));
140
+
141
+ return (
142
+ <RtpProvider
143
+ client={rtpClient}
144
+ isAuthenticated={isAuthenticated}
145
+ storage={persistence} // optional — see "Cold start" below
146
+ >
147
+ <Dashboard />
148
+ </RtpProvider>
149
+ );
150
+ }
151
+ ```
152
+
153
+ Inside your screen, render your own sheet (e.g. QTM's `QTBottomSheet`):
154
+
155
+ ```tsx
156
+ function Dashboard() {
157
+ const { request, status, isReady, accept, decline, dismiss, lastResult } =
158
+ useRtp();
159
+
160
+ return (
161
+ <>
162
+ <YourDashboard />
163
+
164
+ <QTBottomSheet isOpen={status !== null} onClose={dismiss}>
165
+ {request && (
166
+ <>
167
+ <Text>{request.merchant.name}</Text>
168
+ <Text>{request.currency} {request.amount}</Text>
169
+ <Text>{request.transactionRef}</Text>
170
+ </>
171
+ )}
172
+
173
+ {isReady && (
174
+ <>
175
+ {/* collect PIN yourself, produce an opaque credential, then: */}
176
+ <Button title="Accept" onPress={() => accept(credential)} />
177
+ <Button title="Decline" onPress={() => decline('USER_DECLINED')} />
178
+ </>
179
+ )}
180
+
181
+ {status === RTP_STATUS.ACCEPTED && <Text>Payment accepted</Text>}
182
+ {status === RTP_STATUS.FAILED && <Text>Something went wrong</Text>}
183
+ {lastResult?.error && <Text>{lastResult.error}</Text>}
184
+ </QTBottomSheet>
185
+ </>
186
+ );
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Status catalog
193
+
194
+ The SDK computes status; the app only renders it.
195
+
196
+ | Status | Meaning |
197
+ | --- | --- |
198
+ | `RECEIVED` | Payload arrived and passed validation. |
199
+ | `AWAITING_AUTH` | Valid, but the user is logged out (auth gate). |
200
+ | `READY` | Valid, authenticated, actionable — show Accept / Decline. |
201
+ | `PROCESSING` | Accept/decline in flight. |
202
+ | `ACCEPTED` | Backend confirmed acceptance. |
203
+ | `DECLINED` | Backend confirmed (or user initiated) a decline. |
204
+ | `EXPIRED` | Past `expiresAt`; no longer actionable. |
205
+ | `FAILED` | Terminal failure. See `RtpError` for the reason. |
206
+
207
+ `RtpError` values: `INVALID_PAYLOAD`, `RECIPIENT_MISMATCH`, `NETWORK_ERROR`,
208
+ `BACKEND_REJECTED`, `UNKNOWN`.
209
+
210
+ ---
211
+
212
+ ## Events
213
+
214
+ Subscribe via `client.on(event, handler)`. Every call returns an unsubscribe
215
+ function.
216
+
217
+ | Event | Payload | Fires when |
218
+ | --- | --- | --- |
219
+ | `request` | `RtpRequest` | A request becomes actionable (`READY`). |
220
+ | `state` | `{ id, status }` | Any status transition. |
221
+ | `result` | `RtpResult` | A terminal outcome (`ACCEPTED`/`DECLINED`/`EXPIRED`/`FAILED`). |
222
+
223
+ ```ts
224
+ const off = rtpClient.on('result', (r) => {
225
+ if (r.status === 'ACCEPTED') showToast('Paid');
226
+ });
227
+ // later
228
+ off();
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Client API
234
+
235
+ | Method | Description |
236
+ | --- | --- |
237
+ | `isRtp(payload)` | Type guard for decoded RTP payloads. |
238
+ | `receive(payload)` | Validate, de-dupe, apply the auth gate/expiry, and emit. Returns an outcome. |
239
+ | `accept({ id, credential })` | Authorize a request. `credential` is **opaque** — the SDK forwards it untouched. |
240
+ | `decline({ id, reason })` | Decline a request. |
241
+ | `getStatus(id)` | Current normalized status. |
242
+ | `getRequest(id)` | The normalized request. |
243
+ | `acknowledge(id)` | Forget a request (after you dismiss the UI). |
244
+ | `resumeAuth()` | Release requests waiting on the auth gate. Usually called for you by the provider. |
245
+ | `on(event, cb)` | Subscribe; returns an unsubscribe fn. |
246
+
247
+ ### The opaque credential
248
+
249
+ `accept()` takes a `credential` that the SDK **never inspects** — no PIN, no
250
+ crypto, no card-service output crosses the boundary as structured data:
251
+
252
+ ```ts
253
+ const credential = await cardService.generateSecureData(pin); // stays in the app
254
+ await accept(credential);
255
+ ```
256
+
257
+ This is what makes the portability promise hold: a Kotlin or Swift host produces
258
+ its own credential type and posts it opaquely.
259
+
260
+ ---
261
+
262
+ ## Edge cases handled for you
263
+
264
+ - **Duplicate delivery** — the same `rtpId` arriving via foreground *and*
265
+ background (or a double tap) emits `request` exactly once.
266
+ - **Expiry re-checked on every transition** — a request can expire while the
267
+ sheet is open; `accept` refuses and reports `EXPIRED`.
268
+ - **Recipient mismatch** — if `payload.recipient` doesn't match `getCurrentUser()`,
269
+ the request fails with `RECIPIENT_MISMATCH` instead of being shown.
270
+ - **Logged-out → login resume** — a request received while logged out sits in
271
+ `AWAITING_AUTH`; when `isAuthenticated` flips true the provider calls
272
+ `resumeAuth()` and emits `request`.
273
+
274
+ ### Cold start (killed app)
275
+
276
+ Register your PN handler early (before React mounts) and persist the payload:
277
+
278
+ ```ts
279
+ // index.js — before App renders
280
+ rtcp.onNotificationTapped(async (notification) => {
281
+ const payload = mapPushToRtp(notification);
282
+ if (payload) await persistence.setItem('rtp.pendingPayload', JSON.stringify(payload));
283
+ });
284
+ ```
285
+
286
+ `RtpProvider` drains that key on mount. The storage key is
287
+ `rtp.pendingPayload` (exported as `STORAGE_KEY` in the example).
288
+
289
+ ```ts
290
+ export const persistence = {
291
+ getItem: (k) => AsyncStorage.getItem(k),
292
+ setItem: (k, v) => AsyncStorage.setItem(k, v),
293
+ removeItem: (k) => AsyncStorage.removeItem(k),
294
+ };
295
+ ```
296
+
297
+ ---
298
+
299
+ ## Security notes
300
+
301
+ - **Never trust the push body for money movement.** It carries an opaque
302
+ `rtpId`/token; `accept` is verified server-side.
303
+ - **Recipient matching is enforced by the SDK** via `getCurrentUser()`, which
304
+ matters most in the logged-out flow where you have no session to lean on.
305
+ - **Idempotency is keyed by `rtpId`**, so replays can't double-action a request.
306
+
307
+ ---
308
+
309
+ ## Cross-platform conformance
310
+
311
+ This package is one of several implementations of the same spec. To keep them
312
+ honest, `fixtures/conformance.json` holds platform-neutral test vectors with a
313
+ **fixed clock**: valid, logged-out, expired, malformed, wrong-recipient,
314
+ non-RTP, duplicate, and unknown-field cases.
315
+
316
+ Every implementation (React Native, Kotlin, Swift) must produce the same
317
+ outcome for each case. The vectors are the contract; the code is just one
318
+ realization of it.
319
+
320
+ > If you ever add signed payloads (HMAC), lock the canonical byte string
321
+ > (field order, encoding, key/digest encoding, MAC placement) in these vectors
322
+ > first — otherwise signatures silently diverge between platforms.
323
+
324
+ ---
325
+
326
+ ## Example app
327
+
328
+ A runnable host app lives in [`example/`](./example). It ships a mock transport
329
+ and a control panel to simulate incoming PNs and every edge case:
330
+
331
+ ```sh
332
+ yarn example start # then press i / a / w
333
+ yarn example web # or run it in a browser
334
+ ```
335
+
336
+ Buttons: deliver valid / expired / duplicate requests, simulate a cold start
337
+ (persist + remount provider), toggle authentication, and force accept failures.
338
+ The event log shows the `request` / `state` / `result` stream live.
339
+
340
+ ---
341
+
342
+ ## Development
343
+
344
+ ```sh
345
+ yarn typecheck
346
+ yarn lint
347
+ yarn test
348
+ yarn prepare # build lib/
349
+ ```
350
+
351
+ ## Contributing
352
+
353
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
354
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
355
+ - [Code of conduct](./CODE_OF_CONDUCT.md)
356
+
357
+ ## License
358
+
359
+ MIT
360
+
361
+ ---
362
+
363
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+
3
+ import { RTP_ERROR, RTP_EVENT, RTP_STATUS } from "./constants.js";
4
+ import { createEmitter, defaultNow, noopLogger } from "./emitter.js";
5
+ import { findValidationErrors, isExpired, isRtpPayload, toRtpRequest } from "./validate.js";
6
+ /**
7
+ * Create a portable Request-to-Pay client.
8
+ *
9
+ * The client owns validation, de-duplication, expiry, status computation and
10
+ * the accept/decline/status calls. It owns no UI, no navigation and no crypto.
11
+ * Every external effect is injected so the same contract can be re-implemented
12
+ * on other platforms.
13
+ */
14
+ export function createRtpClient(options) {
15
+ const {
16
+ transport,
17
+ getAuthHeaders = async () => ({}),
18
+ getCurrentUser = () => null,
19
+ now = defaultNow,
20
+ logger = noopLogger,
21
+ basePath = '/rtp'
22
+ } = options;
23
+ if (!transport) {
24
+ throw new Error('[react-native-rtp] `transport` is required');
25
+ }
26
+ const emitter = createEmitter();
27
+ const entries = new Map();
28
+ function setStatus(id, status) {
29
+ const entry = entries.get(id);
30
+ if (entry) {
31
+ entry.status = status;
32
+ }
33
+ emitter.emit(RTP_EVENT.STATE, {
34
+ id,
35
+ status
36
+ });
37
+ }
38
+ function emitResult(result) {
39
+ setStatus(result.id, result.status);
40
+ emitter.emit(RTP_EVENT.RESULT, result);
41
+ return result;
42
+ }
43
+ function fail(id, error) {
44
+ return emitResult({
45
+ id,
46
+ status: RTP_STATUS.FAILED,
47
+ error
48
+ });
49
+ }
50
+ async function receive(payload) {
51
+ if (!isRtpPayload(payload)) {
52
+ return {
53
+ handled: false
54
+ };
55
+ }
56
+ const id = payload.rtpId;
57
+
58
+ // Idempotency: the same request can arrive twice (foreground + background,
59
+ // or a double tap). Never re-emit for an id we have already seen.
60
+ const existing = entries.get(id);
61
+ if (existing) {
62
+ return {
63
+ handled: true,
64
+ request: existing.request,
65
+ status: existing.status
66
+ };
67
+ }
68
+ const errors = findValidationErrors(payload);
69
+ if (errors.length > 0) {
70
+ logger('[react-native-rtp] invalid payload, missing:', errors.join(', '));
71
+ return {
72
+ handled: true,
73
+ error: RTP_ERROR.INVALID_PAYLOAD
74
+ };
75
+ }
76
+ const request = toRtpRequest(payload);
77
+ entries.set(id, {
78
+ request,
79
+ status: RTP_STATUS.RECEIVED
80
+ });
81
+ if (isExpired(request, now())) {
82
+ const result = emitResult({
83
+ id,
84
+ status: RTP_STATUS.EXPIRED
85
+ });
86
+ return {
87
+ handled: true,
88
+ request,
89
+ status: result.status
90
+ };
91
+ }
92
+ const currentUser = getCurrentUser();
93
+ if (!currentUser) {
94
+ setStatus(id, RTP_STATUS.AWAITING_AUTH);
95
+ return {
96
+ handled: true,
97
+ request,
98
+ status: RTP_STATUS.AWAITING_AUTH
99
+ };
100
+ }
101
+ if (request.recipient && currentUser !== request.recipient) {
102
+ const result = fail(id, RTP_ERROR.RECIPIENT_MISMATCH);
103
+ return {
104
+ handled: true,
105
+ request,
106
+ status: result.status,
107
+ error: result.error
108
+ };
109
+ }
110
+ setStatus(id, RTP_STATUS.READY);
111
+ emitter.emit(RTP_EVENT.REQUEST, request);
112
+ return {
113
+ handled: true,
114
+ request,
115
+ status: RTP_STATUS.READY
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Called by the host after a successful login to release any requests that
121
+ * were waiting on the auth gate.
122
+ */
123
+ function resumeAuth() {
124
+ for (const [id, entry] of entries) {
125
+ if (entry.status !== RTP_STATUS.AWAITING_AUTH) {
126
+ continue;
127
+ }
128
+ if (isExpired(entry.request, now())) {
129
+ const result = emitResult({
130
+ id,
131
+ status: RTP_STATUS.EXPIRED
132
+ });
133
+ logger('[react-native-rtp] request expired while awaiting auth:', id, result.status);
134
+ continue;
135
+ }
136
+ const currentUser = getCurrentUser();
137
+ if (entry.request.recipient && currentUser && currentUser !== entry.request.recipient) {
138
+ fail(id, RTP_ERROR.RECIPIENT_MISMATCH);
139
+ continue;
140
+ }
141
+ setStatus(id, RTP_STATUS.READY);
142
+ emitter.emit(RTP_EVENT.REQUEST, entry.request);
143
+ }
144
+ }
145
+ async function accept(params) {
146
+ const {
147
+ id,
148
+ credential
149
+ } = params;
150
+ const entry = entries.get(id);
151
+ if (!entry) {
152
+ return fail(id, RTP_ERROR.UNKNOWN);
153
+ }
154
+ if (isExpired(entry.request, now())) {
155
+ return emitResult({
156
+ id,
157
+ status: RTP_STATUS.EXPIRED
158
+ });
159
+ }
160
+ setStatus(id, RTP_STATUS.PROCESSING);
161
+ try {
162
+ const headers = await getAuthHeaders();
163
+ const res = await transport.post(`${basePath}/${id}/accept`, {
164
+ credential
165
+ }, headers);
166
+ const backendStatus = res?.data?.status;
167
+ const status = backendStatus === 'DECLINED' ? RTP_STATUS.DECLINED : backendStatus === 'FAILED' ? RTP_STATUS.FAILED : RTP_STATUS.ACCEPTED;
168
+ return emitResult({
169
+ id,
170
+ status,
171
+ data: res?.data,
172
+ error: status === RTP_STATUS.FAILED ? RTP_ERROR.BACKEND_REJECTED : undefined
173
+ });
174
+ } catch (err) {
175
+ logger('[react-native-rtp] accept failed:', err);
176
+ return fail(id, RTP_ERROR.NETWORK_ERROR);
177
+ }
178
+ }
179
+ async function decline(params) {
180
+ const {
181
+ id,
182
+ reason
183
+ } = params;
184
+ const entry = entries.get(id);
185
+ if (!entry) {
186
+ return fail(id, RTP_ERROR.UNKNOWN);
187
+ }
188
+ setStatus(id, RTP_STATUS.PROCESSING);
189
+ try {
190
+ const headers = await getAuthHeaders();
191
+ const res = await transport.post(`${basePath}/${id}/decline`, {
192
+ reason
193
+ }, headers);
194
+ return emitResult({
195
+ id,
196
+ status: RTP_STATUS.DECLINED,
197
+ data: res?.data
198
+ });
199
+ } catch (err) {
200
+ logger('[react-native-rtp] decline failed:', err);
201
+ return fail(id, RTP_ERROR.NETWORK_ERROR);
202
+ }
203
+ }
204
+ return {
205
+ isRtp: isRtpPayload,
206
+ receive,
207
+ resumeAuth,
208
+ accept,
209
+ decline,
210
+ getStatus: id => entries.get(id)?.status,
211
+ getRequest: id => entries.get(id)?.request,
212
+ acknowledge: id => {
213
+ entries.delete(id);
214
+ },
215
+ on: emitter.on
216
+ };
217
+ }
218
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["RTP_ERROR","RTP_EVENT","RTP_STATUS","createEmitter","defaultNow","noopLogger","findValidationErrors","isExpired","isRtpPayload","toRtpRequest","createRtpClient","options","transport","getAuthHeaders","getCurrentUser","now","logger","basePath","Error","emitter","entries","Map","setStatus","id","status","entry","get","emit","STATE","emitResult","result","RESULT","fail","error","FAILED","receive","payload","handled","rtpId","existing","request","errors","length","join","INVALID_PAYLOAD","set","RECEIVED","EXPIRED","currentUser","AWAITING_AUTH","recipient","RECIPIENT_MISMATCH","READY","REQUEST","resumeAuth","accept","params","credential","UNKNOWN","PROCESSING","headers","res","post","backendStatus","data","DECLINED","ACCEPTED","BACKEND_REJECTED","undefined","err","NETWORK_ERROR","decline","reason","isRtp","getStatus","getRequest","acknowledge","delete","on"],"sourceRoot":"../../src","sources":["client.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Normalized RTP status.
5
+ *
6
+ * The SDK is the single source of truth for these values so that every host
7
+ * platform (React Native, Kotlin, Swift) renders the same UI states.
8
+ */
9
+ export const RTP_STATUS = {
10
+ /** Payload arrived and passed validation, but is not yet actionable. */
11
+ RECEIVED: 'RECEIVED',
12
+ /** Expired before it could be actioned. */
13
+ EXPIRED: 'EXPIRED',
14
+ /** Valid, but the user is not authenticated yet (login gate). */
15
+ AWAITING_AUTH: 'AWAITING_AUTH',
16
+ /** Valid, authenticated, and ready for the user to accept/decline. */
17
+ READY: 'READY',
18
+ /** Accept/decline in flight. */
19
+ PROCESSING: 'PROCESSING',
20
+ /** Backend confirmed acceptance. */
21
+ ACCEPTED: 'ACCEPTED',
22
+ /** Backend confirmed (or user initiated) a decline. */
23
+ DECLINED: 'DECLINED',
24
+ /** Terminal failure (network, validation, recipient mismatch, etc.). */
25
+ FAILED: 'FAILED'
26
+ };
27
+ /** Events emitted by the client. */
28
+ export const RTP_EVENT = {
29
+ /** A new, actionable request is available. */
30
+ REQUEST: 'request',
31
+ /** Status transition for a request. */
32
+ STATE: 'state',
33
+ /** Terminal outcome for a request. */
34
+ RESULT: 'result'
35
+ };
36
+ /** Reasons a request can end in FAILED, exposed for UI/diagnostics. */
37
+ export const RTP_ERROR = {
38
+ INVALID_PAYLOAD: 'INVALID_PAYLOAD',
39
+ RECIPIENT_MISMATCH: 'RECIPIENT_MISMATCH',
40
+ NETWORK_ERROR: 'NETWORK_ERROR',
41
+ BACKEND_REJECTED: 'BACKEND_REJECTED',
42
+ UNKNOWN: 'UNKNOWN'
43
+ };
44
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["RTP_STATUS","RECEIVED","EXPIRED","AWAITING_AUTH","READY","PROCESSING","ACCEPTED","DECLINED","FAILED","RTP_EVENT","REQUEST","STATE","RESULT","RTP_ERROR","INVALID_PAYLOAD","RECIPIENT_MISMATCH","NETWORK_ERROR","BACKEND_REJECTED","UNKNOWN"],"sourceRoot":"../../src","sources":["constants.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,UAAU,GAAG;EACxB;EACAC,QAAQ,EAAE,UAAU;EACpB;EACAC,OAAO,EAAE,SAAS;EAClB;EACAC,aAAa,EAAE,eAAe;EAC9B;EACAC,KAAK,EAAE,OAAO;EACd;EACAC,UAAU,EAAE,YAAY;EACxB;EACAC,QAAQ,EAAE,UAAU;EACpB;EACAC,QAAQ,EAAE,UAAU;EACpB;EACAC,MAAM,EAAE;AACV,CAAU;AAIV;AACA,OAAO,MAAMC,SAAS,GAAG;EACvB;EACAC,OAAO,EAAE,SAAS;EAClB;EACAC,KAAK,EAAE,OAAO;EACd;EACAC,MAAM,EAAE;AACV,CAAU;AAIV;AACA,OAAO,MAAMC,SAAS,GAAG;EACvBC,eAAe,EAAE,iBAAiB;EAClCC,kBAAkB,EAAE,oBAAoB;EACxCC,aAAa,EAAE,eAAe;EAC9BC,gBAAgB,EAAE,kBAAkB;EACpCC,OAAO,EAAE;AACX,CAAU","ignoreList":[]}