@inno-isw/react-native-rtp 0.0.0-stage → 0.2.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.
- package/LICENSE +20 -0
- package/README.md +385 -2
- package/lib/module/client.js +391 -0
- package/lib/module/client.js.map +1 -0
- package/lib/module/constants.js +49 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/emitter.js +46 -0
- package/lib/module/emitter.js.map +1 -0
- package/lib/module/index.js +16 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/mock/makeRtpPayload.js +54 -0
- package/lib/module/mock/makeRtpPayload.js.map +1 -0
- package/lib/module/mock/mockTransport.js +89 -0
- package/lib/module/mock/mockTransport.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/push.js +96 -0
- package/lib/module/push.js.map +1 -0
- package/lib/module/react/RtpProvider.js +152 -0
- package/lib/module/react/RtpProvider.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/validate.js +122 -0
- package/lib/module/validate.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/client.d.ts +20 -0
- package/lib/typescript/src/client.d.ts.map +1 -0
- package/lib/typescript/src/constants.d.ts +50 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/emitter.d.ts +15 -0
- package/lib/typescript/src/emitter.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +14 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/mock/makeRtpPayload.d.ts +17 -0
- package/lib/typescript/src/mock/makeRtpPayload.d.ts.map +1 -0
- package/lib/typescript/src/mock/mockTransport.d.ts +35 -0
- package/lib/typescript/src/mock/mockTransport.d.ts.map +1 -0
- package/lib/typescript/src/push.d.ts +41 -0
- package/lib/typescript/src/push.d.ts.map +1 -0
- package/lib/typescript/src/react/RtpProvider.d.ts +60 -0
- package/lib/typescript/src/react/RtpProvider.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +153 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/validate.d.ts +33 -0
- package/lib/typescript/src/validate.d.ts.map +1 -0
- package/package.json +141 -4
- package/src/client.ts +428 -0
- package/src/constants.ts +54 -0
- package/src/emitter.ts +53 -0
- package/src/index.tsx +45 -0
- package/src/mock/makeRtpPayload.ts +69 -0
- package/src/mock/mockTransport.ts +119 -0
- package/src/push.ts +109 -0
- package/src/react/RtpProvider.tsx +235 -0
- package/src/types.ts +172 -0
- package/src/validate.ts +141 -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,386 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @inno-isw/react-native-rtp
|
|
2
2
|
|
|
3
|
-
|
|
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: number; // minor unit (e.g. kobo); integer, no floats
|
|
56
|
+
currency: string; // ISO 4217, e.g. "NGN"
|
|
57
|
+
payer: { name: string; accountNumber?: string; bank?: string; logoUrl?: string };
|
|
58
|
+
payee: { name: string; accountNumber?: string; bank?: string; logoUrl?: string };
|
|
59
|
+
narration?: string;
|
|
60
|
+
transactionRef?: string;
|
|
61
|
+
orderNumber?: string; // merchant/issuer order no. (≠ transactionRef)
|
|
62
|
+
deepLink?: string; // secure transaction / deep-link reference
|
|
63
|
+
actionMessage?: string; // instruction shown to the customer
|
|
64
|
+
expiresAt: string; // ISO 8601
|
|
65
|
+
recipient?: string; // alias the request is addressed to
|
|
66
|
+
[key: string]: unknown; // forward-compatible passthrough
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> `payer` is the party being asked to pay (your customer); `payee` is the party
|
|
71
|
+
> that receives the money (the merchant).
|
|
72
|
+
|
|
73
|
+
> Unknown fields are **ignored, not rejected**, so older SDKs keep working when
|
|
74
|
+
> the backend adds fields.
|
|
75
|
+
|
|
76
|
+
### `isRtp(payload)`
|
|
77
|
+
|
|
78
|
+
The type guard operates on the **decoded payload**, not the notification
|
|
79
|
+
envelope:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
payload.type === 'RTP' && typeof payload.rtpId === 'string';
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Quick start
|
|
88
|
+
|
|
89
|
+
### 1. Create the client (injected seams)
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createRtpClient } from '@inno-isw/react-native-rtp';
|
|
93
|
+
import { api } from './api'; // your existing HTTP client / ky / axios instance
|
|
94
|
+
|
|
95
|
+
export const rtpClient = createRtpClient({
|
|
96
|
+
// (required) minimal HTTP contract — the SDK never imports a network lib
|
|
97
|
+
transport: {
|
|
98
|
+
post: (path, body, headers) => api.post(path, { json: body, headers }),
|
|
99
|
+
get: (path, headers) => api.get(path, { headers }),
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// (optional) auth headers, called per request so tokens/MACs stay fresh
|
|
103
|
+
getAuthHeaders: async () => ({ Authorization: `Bearer ${await getToken()}` }),
|
|
104
|
+
|
|
105
|
+
// (optional) drives the auth gate + recipient matching
|
|
106
|
+
getCurrentUser: () => authStore.user?.email ?? null,
|
|
107
|
+
|
|
108
|
+
// (optional) injectable clock for deterministic expiry
|
|
109
|
+
now: () => Date.now(),
|
|
110
|
+
|
|
111
|
+
// (optional) diagnostics sink
|
|
112
|
+
logger: (...args) => console.log('[rtp]', ...args),
|
|
113
|
+
|
|
114
|
+
// (optional) REST base path, default "/rtp"
|
|
115
|
+
basePath: '/rtp',
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. Normalize your push notification → payload
|
|
120
|
+
|
|
121
|
+
This is the **only** platform-specific input seam. Use the reference helper, or
|
|
122
|
+
write your own:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { mapPushToRtp } from '@inno-isw/react-native-rtp';
|
|
126
|
+
|
|
127
|
+
// Works with RTCP/FCM shapes, including app_data JSON unwrapping:
|
|
128
|
+
const payload = mapPushToRtp(notification);
|
|
129
|
+
if (payload) {
|
|
130
|
+
rtpClient.receive(payload);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For a bespoke PN system, pass your own data accessor:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const payload = mapPushToRtp(envelope, (e) => e.customKeys);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 3. Mount the provider and build your UI
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { RtpProvider, useRtp, RTP_STATUS } from '@inno-isw/react-native-rtp';
|
|
144
|
+
import { rtpClient } from './rtpClient';
|
|
145
|
+
|
|
146
|
+
export default function App() {
|
|
147
|
+
const isAuthenticated = useAuthStore((s) => Boolean(s.user));
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<RtpProvider
|
|
151
|
+
client={rtpClient}
|
|
152
|
+
isAuthenticated={isAuthenticated}
|
|
153
|
+
storage={persistence} // optional — see "Cold start" below
|
|
154
|
+
>
|
|
155
|
+
<Dashboard />
|
|
156
|
+
</RtpProvider>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Inside your screen, render your own sheet (e.g. QTM's `QTBottomSheet`):
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
function Dashboard() {
|
|
165
|
+
const { request, status, isReady, isLoading, accept, decline, dismiss, lastResult } =
|
|
166
|
+
useRtp();
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
<YourDashboard />
|
|
171
|
+
|
|
172
|
+
<QTBottomSheet isOpen={status !== null} onClose={dismiss}>
|
|
173
|
+
{isLoading && <ActivityIndicator />}
|
|
174
|
+
|
|
175
|
+
{request && (
|
|
176
|
+
<>
|
|
177
|
+
<Text>{request.payee.name}</Text>
|
|
178
|
+
<Text>{request.currency} {request.amount}</Text>
|
|
179
|
+
<Text>{request.transactionRef}</Text>
|
|
180
|
+
</>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{isReady && (
|
|
184
|
+
<>
|
|
185
|
+
{/* collect PIN yourself, produce an opaque credential, then: */}
|
|
186
|
+
<Button title="Accept" onPress={() => accept(credential)} />
|
|
187
|
+
<Button title="Decline" onPress={() => decline('USER_DECLINED')} />
|
|
188
|
+
</>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{status === RTP_STATUS.ACCEPTED && <Text>Payment accepted</Text>}
|
|
192
|
+
{status === RTP_STATUS.FAILED && <Text>Something went wrong</Text>}
|
|
193
|
+
{lastResult?.error && <Text>{lastResult.error}</Text>}
|
|
194
|
+
</QTBottomSheet>
|
|
195
|
+
</>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Status catalog
|
|
203
|
+
|
|
204
|
+
The SDK computes status; the app only renders it.
|
|
205
|
+
|
|
206
|
+
| Status | Meaning |
|
|
207
|
+
| --- | --- |
|
|
208
|
+
| `RECEIVED` | Payload arrived and passed validation. |
|
|
209
|
+
| `RETRIEVING` | Details are being fetched from the backend (`retrieve`). |
|
|
210
|
+
| `AWAITING_AUTH` | Valid, but the user is logged out (auth gate). |
|
|
211
|
+
| `READY` | Valid, authenticated, actionable — show Accept / Decline. |
|
|
212
|
+
| `PROCESSING` | Accept/decline in flight. |
|
|
213
|
+
| `PENDING` | Backend accepted the action but a final decision is still pending. |
|
|
214
|
+
| `ACCEPTED` | Backend confirmed acceptance. |
|
|
215
|
+
| `DECLINED` | Backend confirmed (or user initiated) a decline. |
|
|
216
|
+
| `EXPIRED` | Past `expiresAt`; no longer actionable. |
|
|
217
|
+
| `FAILED` | Terminal failure. See `RtpError` for the reason. |
|
|
218
|
+
|
|
219
|
+
`ACCEPTED`, `DECLINED`, `EXPIRED` and `FAILED` are terminal; `PENDING` is not.
|
|
220
|
+
|
|
221
|
+
`RtpError` values: `INVALID_PAYLOAD`, `RECIPIENT_MISMATCH`, `RETRIEVE_FAILED`,
|
|
222
|
+
`NETWORK_ERROR`, `BACKEND_REJECTED`, `UNKNOWN`.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Events
|
|
227
|
+
|
|
228
|
+
Subscribe via `client.on(event, handler)`. Every call returns an unsubscribe
|
|
229
|
+
function.
|
|
230
|
+
|
|
231
|
+
| Event | Payload | Fires when |
|
|
232
|
+
| --- | --- | --- |
|
|
233
|
+
| `request` | `RtpRequest` | A request becomes actionable (`READY`), or its details were refreshed. |
|
|
234
|
+
| `state` | `{ id, status }` | Any status transition, including the in-place `EXPIRED` below. |
|
|
235
|
+
| `result` | `RtpResult` | A terminal outcome (`ACCEPTED`/`DECLINED`/`EXPIRED`/`FAILED`). |
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
const off = rtpClient.on('result', (r) => {
|
|
239
|
+
if (r.status === 'ACCEPTED') showToast('Paid');
|
|
240
|
+
});
|
|
241
|
+
// later
|
|
242
|
+
off();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Client API
|
|
248
|
+
|
|
249
|
+
| Method | Description |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| `isRtp(payload)` | Type guard for decoded RTP payloads. |
|
|
252
|
+
| `receive(payload)` | Validate, de-dupe, apply the auth gate/expiry, and emit. Returns an outcome. |
|
|
253
|
+
| `retrieve(id)` | Fetch/refresh authorization details and reconcile the backend status. |
|
|
254
|
+
| `accept({ id, credential })` | Authorize a request. `credential` is **opaque** — the SDK forwards it untouched. |
|
|
255
|
+
| `decline({ id, reason })` | Decline a request. |
|
|
256
|
+
| `getStatus(id)` | Current normalized status. |
|
|
257
|
+
| `getRequest(id)` | The normalized request. |
|
|
258
|
+
| `acknowledge(id)` | Forget a request (after you dismiss the UI). |
|
|
259
|
+
| `resumeAuth()` | Release requests waiting on the auth gate. Usually called for you by the provider. |
|
|
260
|
+
| `on(event, cb)` | Subscribe; returns an unsubscribe fn. |
|
|
261
|
+
|
|
262
|
+
### The opaque credential
|
|
263
|
+
|
|
264
|
+
`accept()` takes a `credential` that the SDK **never inspects** — no PIN, no
|
|
265
|
+
crypto, no card-service output crosses the boundary as structured data:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const credential = await cardService.generateSecureData(pin); // stays in the app
|
|
269
|
+
await accept(credential);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
This is what makes the portability promise hold: a Kotlin or Swift host produces
|
|
273
|
+
its own credential type and posts it opaquely.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Edge cases handled for you
|
|
278
|
+
|
|
279
|
+
- **Duplicate delivery** — the same `rtpId` arriving via foreground *and*
|
|
280
|
+
background (or a double tap) emits `request` exactly once.
|
|
281
|
+
- **Expiry fires on its own** — a timer is armed at `expiresAt`, so a request
|
|
282
|
+
that lapses while the sheet is open transitions to `EXPIRED` in place (a
|
|
283
|
+
`state` + `result` event) instead of waiting for the next user action.
|
|
284
|
+
- **Expiry re-checked on every transition** — `accept`/`decline`/`retrieve`
|
|
285
|
+
refuse an already-lapsed request and report `EXPIRED`.
|
|
286
|
+
- **Backend status is never assumed** — an unrecognised status from
|
|
287
|
+
`accept`/`retrieve` fails closed (`FAILED` / `BACKEND_REJECTED`) rather than
|
|
288
|
+
being reported as an approved payment.
|
|
289
|
+
- **Retrieve failure is contained** — a failed `retrieve` emits `FAILED` with
|
|
290
|
+
`RETRIEVE_FAILED`; the request is not silently dropped.
|
|
291
|
+
- **Recipient mismatch** — if `payload.recipient` doesn't match `getCurrentUser()`,
|
|
292
|
+
the request fails with `RECIPIENT_MISMATCH` instead of being shown.
|
|
293
|
+
- **Logged-out → login resume** — a request received while logged out sits in
|
|
294
|
+
`AWAITING_AUTH`; when `isAuthenticated` flips true the provider calls
|
|
295
|
+
`resumeAuth()` and emits `request`.
|
|
296
|
+
|
|
297
|
+
### Cold start (killed app)
|
|
298
|
+
|
|
299
|
+
Register your PN handler early (before React mounts) and persist the payload:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
// index.js — before App renders
|
|
303
|
+
rtcp.onNotificationTapped(async (notification) => {
|
|
304
|
+
const payload = mapPushToRtp(notification);
|
|
305
|
+
if (payload) await persistence.setItem('rtp.pendingPayload', JSON.stringify(payload));
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`RtpProvider` drains that key on mount. The storage key is
|
|
310
|
+
`rtp.pendingPayload` (exported as `STORAGE_KEY` in the example).
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
export const persistence = {
|
|
314
|
+
getItem: (k) => AsyncStorage.getItem(k),
|
|
315
|
+
setItem: (k, v) => AsyncStorage.setItem(k, v),
|
|
316
|
+
removeItem: (k) => AsyncStorage.removeItem(k),
|
|
317
|
+
};
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Security notes
|
|
323
|
+
|
|
324
|
+
- **Never trust the push body for money movement.** It carries an opaque
|
|
325
|
+
`rtpId`/token; `accept` is verified server-side.
|
|
326
|
+
- **Recipient matching is enforced by the SDK** via `getCurrentUser()`, which
|
|
327
|
+
matters most in the logged-out flow where you have no session to lean on.
|
|
328
|
+
- **Idempotency is keyed by `rtpId`**, so replays can't double-action a request.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Cross-platform conformance
|
|
333
|
+
|
|
334
|
+
This package is one of several implementations of the same spec. To keep them
|
|
335
|
+
honest, `fixtures/conformance.json` holds platform-neutral test vectors with a
|
|
336
|
+
**fixed clock**: valid, logged-out, expired, malformed, wrong-recipient,
|
|
337
|
+
non-RTP, duplicate, and unknown-field cases.
|
|
338
|
+
|
|
339
|
+
Every implementation (React Native, Kotlin, Swift) must produce the same
|
|
340
|
+
outcome for each case. The vectors are the contract; the code is just one
|
|
341
|
+
realization of it.
|
|
342
|
+
|
|
343
|
+
> If you ever add signed payloads (HMAC), lock the canonical byte string
|
|
344
|
+
> (field order, encoding, key/digest encoding, MAC placement) in these vectors
|
|
345
|
+
> first — otherwise signatures silently diverge between platforms.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Example app
|
|
350
|
+
|
|
351
|
+
A runnable host app lives in [`example/`](./example). It ships a mock transport
|
|
352
|
+
and a control panel to simulate incoming PNs and every edge case:
|
|
353
|
+
|
|
354
|
+
```sh
|
|
355
|
+
yarn example start # then press i / a / w
|
|
356
|
+
yarn example web # or run it in a browser
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Buttons: deliver valid / expired / duplicate requests, simulate a cold start
|
|
360
|
+
(persist + remount provider), toggle authentication, and force accept failures.
|
|
361
|
+
The event log shows the `request` / `state` / `result` stream live.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Development
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
yarn typecheck
|
|
369
|
+
yarn lint
|
|
370
|
+
yarn test
|
|
371
|
+
yarn prepare # build lib/
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Contributing
|
|
375
|
+
|
|
376
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
377
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
378
|
+
- [Code of conduct](./CODE_OF_CONDUCT.md)
|
|
379
|
+
|
|
380
|
+
## License
|
|
381
|
+
|
|
382
|
+
MIT
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|