@retion-miniapp/sdk 0.4.1 → 0.6.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/CHANGELOG.md +17 -0
- package/README.md +154 -31
- package/dist/index.cjs +19 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -3
- package/dist/index.d.ts +55 -3
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.6.0 — 2026-10-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Chức năng native do app cài: `requestPayment(request)`, `printImage({ image })`, `openScreen({ screen, params })`. Thanh toán / in chờ tối đa 10 phút (không timeout 15s).
|
|
11
|
+
- `ErrorCode.CANCELLED` — người dùng huỷ thao tác; `ErrorCode.BUSY` — gọi trùng khi đang thanh toán / in.
|
|
12
|
+
|
|
13
|
+
### Docs
|
|
14
|
+
- Sửa mô tả `onContextChange` trong JSDoc.
|
|
15
|
+
|
|
16
|
+
## 0.5.0 — 2026-09-30
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- `onContextChange` bắt buộc — bỏ `context_update`, mini app luôn giữ nguyên khi chuyển page (đổi tổ chức vẫn nạp lại).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Báo app `contextApplied` sau khi áp dụng context mới → app không còn hiện nội dung page cũ khi chuyển page.
|
|
23
|
+
|
|
7
24
|
## 0.4.1 — 2026-09-30
|
|
8
25
|
|
|
9
26
|
### Docs
|
package/README.md
CHANGED
|
@@ -6,15 +6,42 @@ SDK cho web mini app chạy trong app Retion.
|
|
|
6
6
|
npm i @retion-miniapp/sdk
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
| API | Dùng để |
|
|
10
|
+
|---|---|
|
|
11
|
+
| [`getRouteParams()`](#getrouteparams) | Biết mini app đang mở ở tổ chức / page / khách nào |
|
|
12
|
+
| [`getToken(type)`](#gettokentype) | Lấy token của page đang mở để gọi API |
|
|
13
|
+
| [`onContextChange(cb)`](#oncontextchangecallback) | **Bắt buộc** — chuyển sang page / conversation khác |
|
|
14
|
+
| [`closeApp()`](#closeapp) | Đóng mini app |
|
|
15
|
+
| [`isInHost()`](#isinhost) | Đang chạy trong app hay trên trình duyệt |
|
|
16
|
+
| [`onAppPaused(cb)` / `onAppResumed(cb)`](#onapppausedcallback--onappresumedcallback) | Mini app bị đóng (chạy ngầm) / mở lại |
|
|
17
|
+
| [`requestPayment(req)`](#requestpaymentrequest) | Thanh toán qua cổng của app (Tap to Pay…) |
|
|
18
|
+
| [`printImage({ image })`](#printimage-image-) | In hoá đơn ra máy in của app |
|
|
19
|
+
| [`openScreen({ screen, params })`](#openscreen-screen-params-) | Mở 1 màn hình của app |
|
|
20
|
+
|
|
21
|
+
**1 mini app = 1 trang, load 1 lần.** Mở mini app ở page / conversation khác, app **không nạp lại trang** mà gọi `onContextChange` — thiếu nó web sẽ hiện dữ liệu page cũ. Đổi tổ chức thì app nạp lại trang.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Ví dụ
|
|
26
|
+
|
|
9
27
|
```ts
|
|
10
|
-
import { getRouteParams, getToken,
|
|
28
|
+
import { getRouteParams, getToken, onContextChange } from '@retion-miniapp/sdk';
|
|
29
|
+
|
|
30
|
+
async function load() {
|
|
31
|
+
const { org_id, page_id } = getRouteParams();
|
|
32
|
+
const token = await getToken('access');
|
|
33
|
+
// gọi API của bạn với org_id, page_id, token
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
load(); // lúc mở
|
|
37
|
+
onContextChange(load); // chuyển conversation
|
|
11
38
|
```
|
|
12
39
|
|
|
13
40
|
---
|
|
14
41
|
|
|
15
42
|
## `getRouteParams()`
|
|
16
43
|
|
|
17
|
-
|
|
44
|
+
Lấy param của mini app (như query trên URL): tổ chức, page, khách của conversation đang mở.
|
|
18
45
|
|
|
19
46
|
```ts
|
|
20
47
|
const { org_id, page_id, client_id } = getRouteParams();
|
|
@@ -30,35 +57,74 @@ const { org_id, page_id, client_id } = getRouteParams();
|
|
|
30
57
|
| `locale` | `string` | Ngôn ngữ app: `vi`, `en`, `id`, `lo`, `km`, `th`, `tl`, `zh` |
|
|
31
58
|
| `is_page_admin` | `1 \| 0` | `1` nếu người dùng là admin của page |
|
|
32
59
|
|
|
33
|
-
Ngoài app (trình duyệt / iframe cũ): đọc từ query URL.
|
|
60
|
+
Ngoài app (trình duyệt / iframe cũ): đọc từ query URL — mọi giá trị là **chuỗi** (`is_page_admin` là `"1"` / `"0"`).
|
|
34
61
|
|
|
35
62
|
---
|
|
36
63
|
|
|
37
64
|
## `getToken(type)`
|
|
38
65
|
|
|
39
|
-
Lấy token để gọi API. Luôn trả token **của page đang mở**. Gọi mỗi lần cần
|
|
66
|
+
Lấy token để gọi API. Luôn trả token **của page đang mở**. Gọi mỗi lần cần — **không lưu lại** (token có thể đổi khi chuyển page hoặc hết hạn).
|
|
40
67
|
|
|
41
68
|
```ts
|
|
42
|
-
const token = await getToken('
|
|
69
|
+
const token = await getToken('access');
|
|
43
70
|
```
|
|
44
71
|
|
|
45
72
|
| `type` | Token | Cần quyền |
|
|
46
73
|
|---|---|---|
|
|
47
74
|
| `'access'` | Token của mini app cho page hiện tại | Không |
|
|
48
75
|
| `'partner'` | Token đối tác của page | `partner_token` |
|
|
49
|
-
| `'chatbox'` | Token đăng nhập của người dùng app
|
|
76
|
+
| `'chatbox'` | Token đăng nhập của người dùng app — **toàn quyền** như người dùng | `chatbox_token` |
|
|
50
77
|
|
|
51
|
-
Quyền do admin bật trên Console (app → *Quyền lấy token*). Chưa bật → lỗi `PERMISSION_DENIED`.
|
|
78
|
+
- Quyền do admin bật trên Console (app → *Quyền lấy token*) — không khai trong code được. Chưa bật → lỗi `PERMISSION_DENIED`.
|
|
79
|
+
- Ngoài app: đọc `?access_token=` / `?partner_token=` / `?chatbox_token=` trên URL; không có → lỗi `NOT_IN_HOST`.
|
|
80
|
+
- `getAccessToken()` = `getToken('access')`.
|
|
52
81
|
|
|
53
|
-
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## `onContextChange(callback)`
|
|
85
|
+
|
|
86
|
+
Gọi khi mini app **đang mở sẵn** được mở lại ở **conversation khác** (khác page hoặc khác khách, cùng tổ chức). App **không nạp lại trang**, nên web phải tự tải lại dữ liệu ở đây. **Bắt buộc** với mọi mini app.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
onContextChange(() => load());
|
|
90
|
+
```
|
|
54
91
|
|
|
55
|
-
|
|
92
|
+
Callback nhận 2 tham số, dùng khi cần so sánh:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
onContextChange((next, prev) => {
|
|
96
|
+
if (next.page_id !== prev.page_id) {
|
|
97
|
+
// đổi page → xoá dữ liệu riêng của page cũ trước khi load
|
|
98
|
+
}
|
|
99
|
+
load();
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Tham số | Kiểu | Ý nghĩa |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `next` | `RouteParams` | Conversation mới — giống `getRouteParams()` gọi lúc này |
|
|
106
|
+
| `prev` | `RouteParams` | Conversation trước đó |
|
|
107
|
+
|
|
108
|
+
**Khi nào được gọi / không được gọi**
|
|
109
|
+
|
|
110
|
+
| Tình huống | App làm gì | Callback |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| Mở lại ở conversation khác, cùng tổ chức | Giữ nguyên trang, gửi thông tin mới | ✅ Gọi |
|
|
113
|
+
| Mở lại ở đúng conversation cũ | Giữ nguyên trang | ❌ Không gọi |
|
|
114
|
+
| Mở lại ở **tổ chức khác** | Nạp lại trang | ❌ Không gọi (code chạy lại từ đầu) |
|
|
115
|
+
| Mở lần đầu / trang chưa load xong | Nạp trang | ❌ Không gọi (code chạy lại từ đầu) |
|
|
116
|
+
|
|
117
|
+
**Lưu ý**
|
|
118
|
+
|
|
119
|
+
- Trong callback, `getRouteParams()` và `getToken()` đã trả thông tin + token của conversation mới.
|
|
120
|
+
- App tạm che mini app cho tới khi callback chạy xong → đổi giao diện sang trạng thái loading **ngay đầu** callback (trước lệnh `await` đầu tiên) để người dùng không thấy dữ liệu cũ.
|
|
121
|
+
- Trả về hàm huỷ lắng nghe: `const off = onContextChange(load); off();`
|
|
56
122
|
|
|
57
123
|
---
|
|
58
124
|
|
|
59
125
|
## `closeApp()`
|
|
60
126
|
|
|
61
|
-
Đóng mini app, quay về màn chat.
|
|
127
|
+
Đóng mini app, quay về màn chat. Mini app vẫn chạy ngầm để mở lại tức thì.
|
|
62
128
|
|
|
63
129
|
```ts
|
|
64
130
|
await closeApp();
|
|
@@ -70,7 +136,7 @@ Ngoài app → lỗi `NOT_IN_HOST`.
|
|
|
70
136
|
|
|
71
137
|
## `isInHost()`
|
|
72
138
|
|
|
73
|
-
`true` nếu đang chạy trong app Retion
|
|
139
|
+
`true` nếu đang chạy trong app Retion; `false` nếu trên trình duyệt / iframe cũ. Dùng khi 1 source chạy cả 2 nơi.
|
|
74
140
|
|
|
75
141
|
```ts
|
|
76
142
|
if (isInHost()) await closeApp();
|
|
@@ -79,49 +145,93 @@ else history.back();
|
|
|
79
145
|
|
|
80
146
|
---
|
|
81
147
|
|
|
82
|
-
## `
|
|
148
|
+
## `onAppPaused(callback)` / `onAppResumed(callback)`
|
|
83
149
|
|
|
84
|
-
Mini app
|
|
150
|
+
Mini app bị đóng (vẫn chạy ngầm) / được mở lại. Dừng việc chạy nền khi bị đóng để đỡ tốn pin.
|
|
85
151
|
|
|
86
152
|
```ts
|
|
87
|
-
|
|
153
|
+
onAppPaused(() => clearInterval(timer)); // polling, video, animation...
|
|
154
|
+
onAppResumed(() => load()); // làm mới dữ liệu nếu cần
|
|
88
155
|
```
|
|
89
156
|
|
|
90
|
-
|
|
157
|
+
Trả về hàm huỷ lắng nghe.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Chức năng native của app
|
|
162
|
+
|
|
163
|
+
Thanh toán, in, mở màn hình — **app tự cài** phần xử lý. Kết quả trả thẳng về chỗ `await`.
|
|
164
|
+
|
|
165
|
+
| Kết quả | Web nhận |
|
|
91
166
|
|---|---|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
167
|
+
| Thành công | Giá trị trả về của `await` |
|
|
168
|
+
| App không hỗ trợ | Lỗi `NOT_SUPPORTED` — ẩn nút hoặc báo người dùng |
|
|
169
|
+
| Người dùng huỷ | Lỗi `CANCELLED` |
|
|
170
|
+
| Đang thanh toán / in mà gọi lần nữa (bấm 2 lần) | Lỗi `BUSY` — lượt đầu vẫn chạy bình thường |
|
|
94
171
|
|
|
95
|
-
|
|
172
|
+
### `requestPayment(request)`
|
|
96
173
|
|
|
97
|
-
|
|
174
|
+
```ts
|
|
175
|
+
import { requestPayment, MiniAppError, ErrorCode } from '@retion-miniapp/sdk';
|
|
98
176
|
|
|
99
|
-
|
|
177
|
+
try {
|
|
178
|
+
const { transaction_id, provider } = await requestPayment({
|
|
179
|
+
amount: 12.5, // theo đơn vị tiền tệ
|
|
180
|
+
currency: 'CAD',
|
|
181
|
+
order_id: order.id,
|
|
182
|
+
});
|
|
183
|
+
await markOrderPaid(order.id, { transaction_id, provider });
|
|
184
|
+
} catch (e) {
|
|
185
|
+
if (e instanceof MiniAppError && e.code === ErrorCode.CANCELLED) {return;} // người dùng huỷ
|
|
186
|
+
showError(e);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
100
189
|
|
|
101
|
-
|
|
190
|
+
| Field | Kiểu | |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `amount` | `number` | Số tiền theo đơn vị tiền tệ (12.5 = 12,50) |
|
|
193
|
+
| `currency` | `string` | ISO 4217: `CAD`, `USD`, `VND`… |
|
|
194
|
+
| `order_id` | `string?` | Mã đơn của bạn — gắn vào giao dịch để đối soát |
|
|
195
|
+
| `note` | `string?` | Ghi chú giao dịch |
|
|
196
|
+
| `metadata` | `object?` | Dữ liệu thêm app cần |
|
|
197
|
+
|
|
198
|
+
Kết quả: `{ transaction_id, provider, ... }`. Chờ tối đa 10 phút (người dùng chạm thẻ), không bị timeout 15s.
|
|
102
199
|
|
|
103
|
-
|
|
200
|
+
> **Luôn đối soát qua backend theo `order_id`.** Web có thể bị đóng / hết thời gian chờ trong khi giao dịch phía app vẫn
|
|
201
|
+
> thành công — đừng chỉ dựa vào kết quả `await` để quyết định đơn đã trả hay chưa.
|
|
202
|
+
|
|
203
|
+
### `printImage({ image })`
|
|
204
|
+
|
|
205
|
+
In ảnh hoá đơn (base64, có hoặc không có `data:image/...;base64,`). App tự hiện chọn máy in. Resolve khi in xong.
|
|
104
206
|
|
|
105
207
|
```ts
|
|
106
|
-
|
|
107
|
-
onAppResumed(() => load()); // làm mới dữ liệu
|
|
208
|
+
await printImage({ image: canvas.toDataURL('image/png') });
|
|
108
209
|
```
|
|
109
210
|
|
|
110
|
-
|
|
211
|
+
### `openScreen({ screen, params })`
|
|
212
|
+
|
|
213
|
+
Mở 1 màn hình của app — app đóng mini app rồi chuyển màn. Tên màn hình do app định nghĩa (xem tài liệu của app);
|
|
214
|
+
màn không tồn tại → `NOT_SUPPORTED`.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
await openScreen({ screen: 'customer', params: { customer_id } });
|
|
218
|
+
```
|
|
111
219
|
|
|
112
220
|
---
|
|
113
221
|
|
|
114
222
|
## Lỗi
|
|
115
223
|
|
|
116
|
-
Mọi lỗi là `MiniAppError`, có `code` và `method
|
|
224
|
+
Mọi lỗi là `MiniAppError`, có `code` (loại lỗi) và `method` (hàm gây lỗi).
|
|
117
225
|
|
|
118
226
|
```ts
|
|
119
|
-
import { MiniAppError, ErrorCode } from '@retion-miniapp/sdk';
|
|
227
|
+
import { getToken, MiniAppError, ErrorCode } from '@retion-miniapp/sdk';
|
|
120
228
|
|
|
121
229
|
try {
|
|
122
230
|
await getToken('chatbox');
|
|
123
231
|
} catch (e) {
|
|
124
|
-
if (e instanceof MiniAppError && e.code === ErrorCode.PERMISSION_DENIED) {
|
|
232
|
+
if (e instanceof MiniAppError && e.code === ErrorCode.PERMISSION_DENIED) {
|
|
233
|
+
// chưa được cấp quyền chatbox_token
|
|
234
|
+
}
|
|
125
235
|
}
|
|
126
236
|
```
|
|
127
237
|
|
|
@@ -129,13 +239,26 @@ try {
|
|
|
129
239
|
|---|---|
|
|
130
240
|
| `NOT_IN_HOST` | Chạy ngoài app mà gọi hàm cần app |
|
|
131
241
|
| `PERMISSION_DENIED` | Chưa được cấp quyền lấy token đó |
|
|
132
|
-
| `TIMEOUT` | App không phản hồi sau 15s |
|
|
133
|
-
| `NOT_SUPPORTED` | Phiên bản app hiện tại chưa hỗ trợ |
|
|
242
|
+
| `TIMEOUT` | App không phản hồi sau 15s (thanh toán / in: 10 phút) |
|
|
243
|
+
| `NOT_SUPPORTED` | Phiên bản app hiện tại chưa hỗ trợ hàm này |
|
|
244
|
+
| `CANCELLED` | Người dùng huỷ (thanh toán, chọn máy in…) |
|
|
245
|
+
| `BUSY` | Đang thanh toán / in mà gọi lần nữa |
|
|
134
246
|
| `HOST_ERROR` | Lỗi khác phía app (vd page chưa có token) |
|
|
135
247
|
|
|
136
248
|
---
|
|
137
249
|
|
|
138
|
-
|
|
250
|
+
## TypeScript
|
|
251
|
+
|
|
252
|
+
Kèm sẵn type. `RouteParams`, `TokenType`, `PaymentRequest`, `PaymentResult`, `PrintImageRequest`, `OpenScreenRequest`,
|
|
253
|
+
`MiniAppError`, `ErrorCode` export từ package.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import type { RouteParams, TokenType } from '@retion-miniapp/sdk';
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
Build + deploy lên app: [`@retion-miniapp/cli`](https://www.npmjs.com/package/@retion-miniapp/cli).
|
|
139
262
|
|
|
140
263
|
## License
|
|
141
264
|
|
package/dist/index.cjs
CHANGED
|
@@ -10,6 +10,10 @@ var ErrorCode = {
|
|
|
10
10
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
11
11
|
/** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
|
|
12
12
|
NOT_SUPPORTED: "NOT_SUPPORTED",
|
|
13
|
+
/** Người dùng huỷ thao tác (đóng màn thanh toán, không chọn máy in…). */
|
|
14
|
+
CANCELLED: "CANCELLED",
|
|
15
|
+
/** Đang có 1 lượt cùng loại chạy (vd đang thanh toán mà gọi requestPayment lần nữa). */
|
|
16
|
+
BUSY: "BUSY",
|
|
13
17
|
/** Lỗi khác phía host. */
|
|
14
18
|
HOST_ERROR: "HOST_ERROR"
|
|
15
19
|
};
|
|
@@ -41,6 +45,7 @@ var MiniAppError = class _MiniAppError extends Error {
|
|
|
41
45
|
var PROTOCOL_VERSION = 1;
|
|
42
46
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
43
47
|
var CONTEXT_EVENT = "context";
|
|
48
|
+
var CONTEXT_APPLIED = "contextApplied";
|
|
44
49
|
var hasWindow = typeof window !== "undefined";
|
|
45
50
|
var readInitialContext = () => {
|
|
46
51
|
var _a;
|
|
@@ -65,11 +70,11 @@ var createState = () => {
|
|
|
65
70
|
if (message.event === CONTEXT_EVENT) {
|
|
66
71
|
const prev = state.context;
|
|
67
72
|
const next = { ...prev, ...message.data };
|
|
68
|
-
if (JSON.stringify(next)
|
|
69
|
-
|
|
73
|
+
if (JSON.stringify(next) !== JSON.stringify(prev)) {
|
|
74
|
+
state.context = next;
|
|
75
|
+
emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
|
|
70
76
|
}
|
|
71
|
-
|
|
72
|
-
emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
|
|
77
|
+
notify(CONTEXT_APPLIED);
|
|
73
78
|
return;
|
|
74
79
|
}
|
|
75
80
|
emit(state, message.event, message.data);
|
|
@@ -149,7 +154,7 @@ var on = (event, listener) => {
|
|
|
149
154
|
};
|
|
150
155
|
|
|
151
156
|
// src/version.ts
|
|
152
|
-
var SDK_VERSION = "0.
|
|
157
|
+
var SDK_VERSION = "0.6.0" ;
|
|
153
158
|
|
|
154
159
|
// src/apis/app.ts
|
|
155
160
|
var closeApp = () => call("closeApp");
|
|
@@ -201,6 +206,12 @@ var getToken = async (type) => {
|
|
|
201
206
|
return token;
|
|
202
207
|
};
|
|
203
208
|
|
|
209
|
+
// src/apis/native.ts
|
|
210
|
+
var USER_ACTION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
211
|
+
var requestPayment = (request) => call("requestPayment", request, USER_ACTION_TIMEOUT_MS);
|
|
212
|
+
var printImage = (request) => call("printImage", request, USER_ACTION_TIMEOUT_MS);
|
|
213
|
+
var openScreen = (request) => call("openScreen", request);
|
|
214
|
+
|
|
204
215
|
// src/index.ts
|
|
205
216
|
if (typeof window !== "undefined") {
|
|
206
217
|
getState();
|
|
@@ -222,5 +233,8 @@ exports.isInHost = isInHost;
|
|
|
222
233
|
exports.onAppPaused = onAppPaused;
|
|
223
234
|
exports.onAppResumed = onAppResumed;
|
|
224
235
|
exports.onContextChange = onContextChange;
|
|
236
|
+
exports.openScreen = openScreen;
|
|
237
|
+
exports.printImage = printImage;
|
|
238
|
+
exports.requestPayment = requestPayment;
|
|
225
239
|
//# sourceMappingURL=index.cjs.map
|
|
226
240
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */\n NOT_SUPPORTED: 'NOT_SUPPORTED',\n /** Lỗi khác phía host. */\n HOST_ERROR: 'HOST_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] | (string & {});\n\nexport class MiniAppError extends Error {\n readonly code: ErrorCode;\n readonly method?: string;\n\n constructor(code: ErrorCode, message: string, method?: string) {\n super(message);\n this.name = 'MiniAppError';\n this.code = code;\n this.method = method;\n }\n\n /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */\n static from(error: unknown, method?: string): MiniAppError {\n if (error instanceof MiniAppError) {\n return error;\n }\n if (error && typeof error === 'object') {\n const { code, message } = error as { code?: unknown; message?: unknown };\n return new MiniAppError(\n typeof code === 'string' ? code : ErrorCode.HOST_ERROR,\n typeof message === 'string' ? message : 'Unknown error',\n method,\n );\n }\n return new MiniAppError(ErrorCode.HOST_ERROR, String(error), method);\n }\n}\n","/**\n * Bridge web mini app ↔ host (app Retion). Nội bộ SDK — dev mini app không import trực tiếp.\n *\n * Giao thức v1\n * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))\n * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)\n * host inject trước khi trang chạy:\n * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK\n * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu\n *\n * Chỉ có ReactNativeWebView KHÔNG đủ để coi là host: màn iframe cũ của app cũng là RN WebView\n * nhưng không hiểu giao thức này (gọi API sẽ treo tới timeout). Thiếu marker → NOT_IN_HOST ngay.\n *\n * State đặt trên window (singleton): nhiều bản SDK trong cùng trang (trùng version trong\n * node_modules) vẫn dùng chung 1 bridge thay vì ghi đè hàm receive của nhau.\n */\nimport { ErrorCode, MiniAppError } from './errors';\nimport type { RouteParams } from './types';\n\nexport const PROTOCOL_VERSION = 1;\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const CONTEXT_EVENT = 'context';\n\nexport interface RequestMessage {\n __retion: typeof PROTOCOL_VERSION;\n /** Có id → host phải trả ResponseMessage. Không có → lệnh 1 chiều. */\n id?: string;\n method: string;\n params?: unknown;\n}\n\nexport type ResponseMessage =\n | { id: string; result?: unknown; error?: undefined }\n | { id: string; error: { code: string; message: string } };\n\nexport interface EventMessage {\n event: string;\n data?: unknown;\n}\n\nexport interface HostInfo {\n /** Phiên bản giao thức host hỗ trợ. */\n protocol: number;\n}\n\ntype Listener = (data: unknown) => void;\n\ninterface Pending {\n method: string;\n resolve: (value: unknown) => void;\n reject: (error: MiniAppError) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface BridgeState {\n protocol: number;\n seq: number;\n pending: Map<string, Pending>;\n listeners: Map<string, Set<Listener>>;\n context: RouteParams;\n receive: (message: ResponseMessage | EventMessage) => void;\n}\n\ndeclare global {\n interface Window {\n ReactNativeWebView?: { postMessage: (message: string) => void };\n __RETION_MINIAPP__?: BridgeState;\n __RETION_MINIAPP_CONTEXT__?: RouteParams;\n __RETION_MINIAPP_HOST__?: HostInfo;\n }\n}\n\nconst hasWindow = typeof window !== 'undefined';\n\nconst readInitialContext = (): RouteParams => {\n if (!hasWindow) {\n return {};\n }\n const query = Object.fromEntries(new URLSearchParams(window.location.search));\n return { ...query, ...(window.__RETION_MINIAPP_CONTEXT__ ?? {}) };\n};\n\nconst createState = (): BridgeState => {\n const state: BridgeState = {\n protocol: PROTOCOL_VERSION,\n seq: 0,\n pending: new Map(),\n listeners: new Map(),\n context: readInitialContext(),\n receive: (message) => {\n if (!message || typeof message !== 'object') {\n return;\n }\n if ('event' in message) {\n if (message.event === CONTEXT_EVENT) {\n // Cập nhật context TRƯỚC khi emit → getRouteParams() luôn đúng, kể cả không ai lắng nghe.\n const prev = state.context;\n const next = { ...prev, ...(message.data as RouteParams) };\n // Host đẩy lại context lúc \"ready\" dù không đổi → không báo onContextChange thừa.\n if (JSON.stringify(next) === JSON.stringify(prev)) {\n return;\n }\n state.context = next;\n emit(state, CONTEXT_EVENT, { next: { ...next }, prev });\n return;\n }\n emit(state, message.event, message.data);\n return;\n }\n const pending = state.pending.get(message.id);\n if (!pending) {\n return;\n }\n state.pending.delete(message.id);\n clearTimeout(pending.timer);\n if (message.error) {\n pending.reject(MiniAppError.from(message.error, pending.method));\n } else {\n pending.resolve(message.result);\n }\n },\n };\n return state;\n};\n\nconst emit = (state: BridgeState, event: string, data: unknown) => {\n state.listeners.get(event)?.forEach((listener) => {\n try {\n listener(data);\n } catch (error) {\n console.error(`[retion-miniapp] listener \"${event}\" lỗi`, error);\n }\n });\n};\n\nlet fallback_state: BridgeState | null = null;\n\n/** @internal */\nexport const getState = (): BridgeState => {\n if (!hasWindow) {\n return (fallback_state ??= createState());\n }\n return (window.__RETION_MINIAPP__ ??= createState());\n};\n\n/** Đang chạy trong app Retion có hỗ trợ SDK (host đã inject marker + kênh postMessage). */\nexport const isInHost = (): boolean =>\n hasWindow &&\n typeof window.__RETION_MINIAPP_HOST__?.protocol === 'number' &&\n typeof window.ReactNativeWebView?.postMessage === 'function';\n\nconst post = (message: RequestMessage) => {\n window.ReactNativeWebView!.postMessage(JSON.stringify(message));\n};\n\n/** Gửi lệnh lên host và chờ kết quả. Ngoài app → reject NOT_IN_HOST. */\nexport const call = <T>(method: string, params?: unknown, timeout_ms = DEFAULT_TIMEOUT_MS): Promise<T> => {\n const state = getState();\n\n if (!isInHost()) {\n return Promise.reject(new MiniAppError(ErrorCode.NOT_IN_HOST, `${method} chỉ chạy trong app Retion`, method));\n }\n\n const id = `${Date.now().toString(36)}-${++state.seq}`;\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n state.pending.delete(id);\n reject(new MiniAppError(ErrorCode.TIMEOUT, `${method}: host không phản hồi sau ${timeout_ms}ms`, method));\n }, timeout_ms);\n state.pending.set(id, { method, resolve: resolve as (v: unknown) => void, reject, timer });\n post({ __retion: PROTOCOL_VERSION, id, method, params });\n });\n};\n\n/** Lệnh 1 chiều (không chờ kết quả). Ngoài app: bỏ qua. */\nexport const notify = (method: string, params?: unknown): void => {\n if (isInHost()) {\n post({ __retion: PROTOCOL_VERSION, method, params });\n }\n};\n\n/** Lắng nghe event host đẩy xuống. Trả về hàm huỷ. */\nexport const on = (event: string, listener: Listener): (() => void) => {\n const { listeners } = getState();\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n set.add(listener);\n return () => {\n set.delete(listener);\n };\n};\n","declare const __SDK_VERSION__: string;\n\n/** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */\nexport const SDK_VERSION: string = typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev';\n","import { call, notify, on } from '../bridge';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Đóng mini app, quay về màn chat. Ngoài app → reject NOT_IN_HOST (web tự xử lý, vd router.back()).\n */\nexport const closeApp = (): Promise<void> => call<void>('closeApp');\n\n/** @internal SDK tự gọi khi trang sẵn sàng — host ẩn splash + đẩy context mới nhất. */\nexport const notifyReady = (): void => notify('ready', { sdkVersion: SDK_VERSION });\n\n/**\n * Mini app bị đóng (ẩn, vẫn chạy ngầm) — dừng polling / timer / video ở đây cho đỡ tốn pin.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppPaused = (listener: () => void): (() => void) => on('paused', () => listener());\n\n/**\n * Mini app được mở lại (trang vẫn giữ nguyên) — làm mới dữ liệu nếu cần.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppResumed = (listener: () => void): (() => void) => on('resumed', () => listener());\n","import { CONTEXT_EVENT, getState, on } from '../bridge';\nimport type { RouteParams } from '../types';\n\n/**\n * Thông tin conversation lúc mở mini app (đồng bộ, gọi bất cứ lúc nào).\n * @example\n * const { page_id, client_id } = getRouteParams();\n */\nexport const getRouteParams = (): RouteParams => ({ ...getState().context });\n\n/**\n * Mini app đang mở được chuyển sang page / conversation khác (không nạp lại trang).\n * Gọi lại API theo thông tin mới ở đây. Chỉ xảy ra khi `miniapp.config.json` có `context_update: true`.\n * @returns hàm huỷ lắng nghe\n * @example\n * onContextChange(({ page_id, client_id }) => loadData(page_id, client_id));\n */\nexport const onContextChange = (listener: (next: RouteParams, prev: RouteParams) => void): (() => void) =>\n on(CONTEXT_EVENT, (data) => {\n const { next, prev } = data as { next: RouteParams; prev: RouteParams };\n listener(next, prev);\n });\n","import { call, isInHost } from '../bridge';\nimport { ErrorCode, MiniAppError } from '../errors';\n\n/** Tên query param chứa token khi chạy ngoài app (giống URL iframe hiện tại). */\nconst TOKEN_QUERY_PARAM = 'access_token';\n\nconst readTokenFromUrl = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n return new URLSearchParams(window.location.search).get(TOKEN_QUERY_PARAM);\n};\n\n/**\n * Token để mini app gọi API backend của chính mình (Authorization: Bearer ...).\n * - Trong app: lấy từ host, luôn là token của page hiện tại, host tự refresh.\n * - Ngoài app: đọc `?access_token=` trên URL (như iframe hiện tại).\n * Gọi lại mỗi khi cần — không lưu token lâu phía web.\n */\nexport const getAccessToken = async (): Promise<string> => {\n if (isInHost()) {\n return call<string>('getAccessToken');\n }\n const token = readTokenFromUrl();\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${TOKEN_QUERY_PARAM}= trên URL`, 'getAccessToken');\n }\n return token;\n};\n\n/** Loại token: `access` luôn được phép; `partner` / `chatbox` cần admin bật quyền trên Console. */\nexport type TokenType = 'access' | 'partner' | 'chatbox';\n\n/** Tên query param tương ứng khi chạy ngoài app (giống URL iframe hiện tại). */\nconst QUERY_PARAM: Record<TokenType, string> = {\n access: 'access_token',\n partner: 'partner_token',\n chatbox: 'chatbox_token',\n};\n\n/**\n * Lấy token theo loại.\n * - `access`: token của mini app cho page hiện tại (= getAccessToken()).\n * - `partner`: token đối tác của page — cần quyền `partner_token`.\n * - `chatbox`: token đăng nhập của người dùng app (toàn quyền) — cần quyền `chatbox_token`.\n * Trong app, chưa được cấp quyền → MiniAppError PERMISSION_DENIED. Ngoài app: đọc query URL.\n */\nexport const getToken = async (type: TokenType): Promise<string> => {\n if (!(type in QUERY_PARAM)) {\n throw new MiniAppError(ErrorCode.HOST_ERROR, `Loại token không hợp lệ: ${String(type)}`, 'getToken');\n }\n if (isInHost()) {\n return call<string>('getToken', { type });\n }\n const token = typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get(QUERY_PARAM[type]);\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${QUERY_PARAM[type]}= trên URL`, 'getToken');\n }\n return token;\n};\n","import { getState } from './bridge';\nimport { notifyReady } from './apis/app';\n\nexport { getRouteParams, onContextChange } from './apis/route';\nexport { getAccessToken, getToken, type TokenType } from './apis/user';\nexport { closeApp, onAppPaused, onAppResumed } from './apis/app';\nexport { isInHost } from './bridge';\nexport { MiniAppError, ErrorCode } from './errors';\nexport { SDK_VERSION } from './version';\nexport type { RouteParams } from './types';\n\n// Khởi tạo bridge ngay khi import (host có thể đẩy context trước khi app gọi hàm nào),\n// rồi báo \"ready\" khi DOM sẵn sàng.\nif (typeof window !== 'undefined') {\n getState();\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', notifyReady, { once: true });\n } else {\n queueMicrotask(notifyReady);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/apis/native.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */\n NOT_SUPPORTED: 'NOT_SUPPORTED',\n /** Người dùng huỷ thao tác (đóng màn thanh toán, không chọn máy in…). */\n CANCELLED: 'CANCELLED',\n /** Đang có 1 lượt cùng loại chạy (vd đang thanh toán mà gọi requestPayment lần nữa). */\n BUSY: 'BUSY',\n /** Lỗi khác phía host. */\n HOST_ERROR: 'HOST_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] | (string & {});\n\nexport class MiniAppError extends Error {\n readonly code: ErrorCode;\n readonly method?: string;\n\n constructor(code: ErrorCode, message: string, method?: string) {\n super(message);\n this.name = 'MiniAppError';\n this.code = code;\n this.method = method;\n }\n\n /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */\n static from(error: unknown, method?: string): MiniAppError {\n if (error instanceof MiniAppError) {\n return error;\n }\n if (error && typeof error === 'object') {\n const { code, message } = error as { code?: unknown; message?: unknown };\n return new MiniAppError(\n typeof code === 'string' ? code : ErrorCode.HOST_ERROR,\n typeof message === 'string' ? message : 'Unknown error',\n method,\n );\n }\n return new MiniAppError(ErrorCode.HOST_ERROR, String(error), method);\n }\n}\n","/**\n * Bridge web mini app ↔ host (app Retion). Nội bộ SDK — dev mini app không import trực tiếp.\n *\n * Giao thức v1\n * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))\n * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)\n * host → web event \"context\" → web gọi onContextChange → web → host \"contextApplied\"\n * host inject trước khi trang chạy:\n * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK\n * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu\n *\n * Chỉ có ReactNativeWebView KHÔNG đủ để coi là host: màn iframe cũ của app cũng là RN WebView\n * nhưng không hiểu giao thức này (gọi API sẽ treo tới timeout). Thiếu marker → NOT_IN_HOST ngay.\n *\n * State đặt trên window (singleton): nhiều bản SDK trong cùng trang (trùng version trong\n * node_modules) vẫn dùng chung 1 bridge thay vì ghi đè hàm receive của nhau.\n */\nimport { ErrorCode, MiniAppError } from './errors';\nimport type { RouteParams } from './types';\n\nexport const PROTOCOL_VERSION = 1;\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const CONTEXT_EVENT = 'context';\n/** web → host: đã áp dụng context mới (sau khi gọi các listener onContextChange). */\nexport const CONTEXT_APPLIED = 'contextApplied';\n\nexport interface RequestMessage {\n __retion: typeof PROTOCOL_VERSION;\n /** Có id → host phải trả ResponseMessage. Không có → lệnh 1 chiều. */\n id?: string;\n method: string;\n params?: unknown;\n}\n\nexport type ResponseMessage =\n | { id: string; result?: unknown; error?: undefined }\n | { id: string; error: { code: string; message: string } };\n\nexport interface EventMessage {\n event: string;\n data?: unknown;\n}\n\nexport interface HostInfo {\n /** Phiên bản giao thức host hỗ trợ. */\n protocol: number;\n}\n\ntype Listener = (data: unknown) => void;\n\ninterface Pending {\n method: string;\n resolve: (value: unknown) => void;\n reject: (error: MiniAppError) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface BridgeState {\n protocol: number;\n seq: number;\n pending: Map<string, Pending>;\n listeners: Map<string, Set<Listener>>;\n context: RouteParams;\n receive: (message: ResponseMessage | EventMessage) => void;\n}\n\ndeclare global {\n interface Window {\n ReactNativeWebView?: { postMessage: (message: string) => void };\n __RETION_MINIAPP__?: BridgeState;\n __RETION_MINIAPP_CONTEXT__?: RouteParams;\n __RETION_MINIAPP_HOST__?: HostInfo;\n }\n}\n\nconst hasWindow = typeof window !== 'undefined';\n\nconst readInitialContext = (): RouteParams => {\n if (!hasWindow) {\n return {};\n }\n const query = Object.fromEntries(new URLSearchParams(window.location.search));\n return { ...query, ...(window.__RETION_MINIAPP_CONTEXT__ ?? {}) };\n};\n\nconst createState = (): BridgeState => {\n const state: BridgeState = {\n protocol: PROTOCOL_VERSION,\n seq: 0,\n pending: new Map(),\n listeners: new Map(),\n context: readInitialContext(),\n receive: (message) => {\n if (!message || typeof message !== 'object') {\n return;\n }\n if ('event' in message) {\n if (message.event === CONTEXT_EVENT) {\n // Cập nhật context TRƯỚC khi emit → getRouteParams() luôn đúng, kể cả không ai lắng nghe.\n const prev = state.context;\n const next = { ...prev, ...(message.data as RouteParams) };\n // Host đẩy lại context lúc \"ready\" dù không đổi → không báo onContextChange thừa.\n if (JSON.stringify(next) !== JSON.stringify(prev)) {\n state.context = next;\n emit(state, CONTEXT_EVENT, { next: { ...next }, prev });\n }\n // Báo host: web đã nhận context (listener đã chạy) → host bỏ lớp che nội dung page cũ.\n notify(CONTEXT_APPLIED);\n return;\n }\n emit(state, message.event, message.data);\n return;\n }\n const pending = state.pending.get(message.id);\n if (!pending) {\n return;\n }\n state.pending.delete(message.id);\n clearTimeout(pending.timer);\n if (message.error) {\n pending.reject(MiniAppError.from(message.error, pending.method));\n } else {\n pending.resolve(message.result);\n }\n },\n };\n return state;\n};\n\nconst emit = (state: BridgeState, event: string, data: unknown) => {\n state.listeners.get(event)?.forEach((listener) => {\n try {\n listener(data);\n } catch (error) {\n console.error(`[retion-miniapp] listener \"${event}\" lỗi`, error);\n }\n });\n};\n\nlet fallback_state: BridgeState | null = null;\n\n/** @internal */\nexport const getState = (): BridgeState => {\n if (!hasWindow) {\n return (fallback_state ??= createState());\n }\n return (window.__RETION_MINIAPP__ ??= createState());\n};\n\n/** Đang chạy trong app Retion có hỗ trợ SDK (host đã inject marker + kênh postMessage). */\nexport const isInHost = (): boolean =>\n hasWindow &&\n typeof window.__RETION_MINIAPP_HOST__?.protocol === 'number' &&\n typeof window.ReactNativeWebView?.postMessage === 'function';\n\nconst post = (message: RequestMessage) => {\n window.ReactNativeWebView!.postMessage(JSON.stringify(message));\n};\n\n/** Gửi lệnh lên host và chờ kết quả. Ngoài app → reject NOT_IN_HOST. */\nexport const call = <T>(method: string, params?: unknown, timeout_ms = DEFAULT_TIMEOUT_MS): Promise<T> => {\n const state = getState();\n\n if (!isInHost()) {\n return Promise.reject(new MiniAppError(ErrorCode.NOT_IN_HOST, `${method} chỉ chạy trong app Retion`, method));\n }\n\n const id = `${Date.now().toString(36)}-${++state.seq}`;\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n state.pending.delete(id);\n reject(new MiniAppError(ErrorCode.TIMEOUT, `${method}: host không phản hồi sau ${timeout_ms}ms`, method));\n }, timeout_ms);\n state.pending.set(id, { method, resolve: resolve as (v: unknown) => void, reject, timer });\n post({ __retion: PROTOCOL_VERSION, id, method, params });\n });\n};\n\n/** Lệnh 1 chiều (không chờ kết quả). Ngoài app: bỏ qua. */\nexport const notify = (method: string, params?: unknown): void => {\n if (isInHost()) {\n post({ __retion: PROTOCOL_VERSION, method, params });\n }\n};\n\n/** Lắng nghe event host đẩy xuống. Trả về hàm huỷ. */\nexport const on = (event: string, listener: Listener): (() => void) => {\n const { listeners } = getState();\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n set.add(listener);\n return () => {\n set.delete(listener);\n };\n};\n","declare const __SDK_VERSION__: string;\n\n/** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */\nexport const SDK_VERSION: string = typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev';\n","import { call, notify, on } from '../bridge';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Đóng mini app, quay về màn chat. Ngoài app → reject NOT_IN_HOST (web tự xử lý, vd router.back()).\n */\nexport const closeApp = (): Promise<void> => call<void>('closeApp');\n\n/** @internal SDK tự gọi khi trang sẵn sàng — host ẩn splash + đẩy context mới nhất. */\nexport const notifyReady = (): void => notify('ready', { sdkVersion: SDK_VERSION });\n\n/**\n * Mini app bị đóng (ẩn, vẫn chạy ngầm) — dừng polling / timer / video ở đây cho đỡ tốn pin.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppPaused = (listener: () => void): (() => void) => on('paused', () => listener());\n\n/**\n * Mini app được mở lại (trang vẫn giữ nguyên) — làm mới dữ liệu nếu cần.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppResumed = (listener: () => void): (() => void) => on('resumed', () => listener());\n","import { CONTEXT_EVENT, getState, on } from '../bridge';\nimport type { RouteParams } from '../types';\n\n/**\n * Thông tin conversation lúc mở mini app (đồng bộ, gọi bất cứ lúc nào).\n * @example\n * const { page_id, client_id } = getRouteParams();\n */\nexport const getRouteParams = (): RouteParams => ({ ...getState().context });\n\n/**\n * Mini app đang mở sẵn được mở lại ở conversation khác (khác page / khách, cùng tổ chức).\n * App không nạp lại trang → gọi lại API theo thông tin mới ở đây. Đổi tổ chức thì app nạp lại trang.\n * @returns hàm huỷ lắng nghe\n * @example\n * onContextChange(({ page_id, client_id }) => loadData(page_id, client_id));\n */\nexport const onContextChange = (listener: (next: RouteParams, prev: RouteParams) => void): (() => void) =>\n on(CONTEXT_EVENT, (data) => {\n const { next, prev } = data as { next: RouteParams; prev: RouteParams };\n listener(next, prev);\n });\n","import { call, isInHost } from '../bridge';\nimport { ErrorCode, MiniAppError } from '../errors';\n\n/** Tên query param chứa token khi chạy ngoài app (giống URL iframe hiện tại). */\nconst TOKEN_QUERY_PARAM = 'access_token';\n\nconst readTokenFromUrl = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n return new URLSearchParams(window.location.search).get(TOKEN_QUERY_PARAM);\n};\n\n/**\n * Token để mini app gọi API backend của chính mình (Authorization: Bearer ...).\n * - Trong app: lấy từ host, luôn là token của page hiện tại, host tự refresh.\n * - Ngoài app: đọc `?access_token=` trên URL (như iframe hiện tại).\n * Gọi lại mỗi khi cần — không lưu token lâu phía web.\n */\nexport const getAccessToken = async (): Promise<string> => {\n if (isInHost()) {\n return call<string>('getAccessToken');\n }\n const token = readTokenFromUrl();\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${TOKEN_QUERY_PARAM}= trên URL`, 'getAccessToken');\n }\n return token;\n};\n\n/** Loại token: `access` luôn được phép; `partner` / `chatbox` cần admin bật quyền trên Console. */\nexport type TokenType = 'access' | 'partner' | 'chatbox';\n\n/** Tên query param tương ứng khi chạy ngoài app (giống URL iframe hiện tại). */\nconst QUERY_PARAM: Record<TokenType, string> = {\n access: 'access_token',\n partner: 'partner_token',\n chatbox: 'chatbox_token',\n};\n\n/**\n * Lấy token theo loại.\n * - `access`: token của mini app cho page hiện tại (= getAccessToken()).\n * - `partner`: token đối tác của page — cần quyền `partner_token`.\n * - `chatbox`: token đăng nhập của người dùng app (toàn quyền) — cần quyền `chatbox_token`.\n * Trong app, chưa được cấp quyền → MiniAppError PERMISSION_DENIED. Ngoài app: đọc query URL.\n */\nexport const getToken = async (type: TokenType): Promise<string> => {\n if (!(type in QUERY_PARAM)) {\n throw new MiniAppError(ErrorCode.HOST_ERROR, `Loại token không hợp lệ: ${String(type)}`, 'getToken');\n }\n if (isInHost()) {\n return call<string>('getToken', { type });\n }\n const token = typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get(QUERY_PARAM[type]);\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${QUERY_PARAM[type]}= trên URL`, 'getToken');\n }\n return token;\n};\n","import { call } from '../bridge';\n\n/**\n * Chức năng native do app cung cấp (thanh toán, in, mở màn hình của app).\n * Mỗi app tự cài phần xử lý — app không hỗ trợ → MiniAppError NOT_SUPPORTED (web ẩn nút / báo người dùng).\n * Người dùng huỷ (đóng màn thanh toán, không chọn máy in…) → MiniAppError CANCELLED.\n * Đang thanh toán / in mà gọi lần nữa (bấm 2 lần) → MiniAppError BUSY — lượt đầu vẫn chạy bình thường.\n */\n\n/** Chờ người dùng thao tác (chạm thẻ, chọn máy in) — lâu hơn timeout mặc định 15s. */\nconst USER_ACTION_TIMEOUT_MS = 10 * 60 * 1000;\n\nexport interface PaymentRequest {\n /** Số tiền theo đơn vị tiền tệ (vd 12.5 = 12,50 CAD). */\n amount: number;\n /** Mã tiền tệ ISO 4217: CAD, USD, VND… */\n currency: string;\n /** Mã đơn của bạn — app gắn vào giao dịch để đối soát. */\n order_id?: string;\n /** Ghi chú hiện trên giao dịch. */\n note?: string;\n /** Dữ liệu thêm app cần (vd chi tiết đơn) — tuỳ app. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface PaymentResult {\n /** Mã giao dịch phía cổng thanh toán. */\n transaction_id: string;\n /** Cổng thanh toán: SQUARE, STRIPE, VNPAY… */\n provider: string;\n [key: string]: unknown;\n}\n\n/**\n * Thanh toán qua cổng của app (vd Tap to Pay). Resolve khi thanh toán thành công.\n * Luôn đối soát lại qua backend theo `order_id`: web có thể đóng / hết thời gian chờ trong khi giao dịch\n * phía app vẫn thành công.\n * @example\n * const { transaction_id, provider } = await requestPayment({ amount: 12.5, currency: 'CAD', order_id });\n */\nexport const requestPayment = (request: PaymentRequest): Promise<PaymentResult> =>\n call<PaymentResult>('requestPayment', request, USER_ACTION_TIMEOUT_MS);\n\nexport interface PrintImageRequest {\n /** Ảnh hoá đơn base64 — có hoặc không có tiền tố `data:image/png;base64,`. */\n image: string;\n}\n\n/**\n * In ảnh (hoá đơn) ra máy in của app. App tự hiện chọn máy in nếu cần. Resolve khi in xong.\n */\nexport const printImage = (request: PrintImageRequest): Promise<void> =>\n call<void>('printImage', request, USER_ACTION_TIMEOUT_MS);\n\nexport interface OpenScreenRequest {\n /** Tên màn hình app định nghĩa (vd `conversation`, `customer`) — xem tài liệu của app. */\n screen: string;\n params?: Record<string, unknown>;\n}\n\n/**\n * Mở 1 màn hình của app (app đóng mini app rồi chuyển màn). Màn không tồn tại → NOT_SUPPORTED.\n * @example\n * await openScreen({ screen: 'customer', params: { customer_id } });\n */\nexport const openScreen = (request: OpenScreenRequest): Promise<void> =>\n call<void>('openScreen', request);\n","import { getState } from './bridge';\nimport { notifyReady } from './apis/app';\n\nexport { getRouteParams, onContextChange } from './apis/route';\nexport { getAccessToken, getToken, type TokenType } from './apis/user';\nexport { closeApp, onAppPaused, onAppResumed } from './apis/app';\nexport {\n openScreen,\n printImage,\n requestPayment,\n type OpenScreenRequest,\n type PaymentRequest,\n type PaymentResult,\n type PrintImageRequest,\n} from './apis/native';\nexport { isInHost } from './bridge';\nexport { MiniAppError, ErrorCode } from './errors';\nexport { SDK_VERSION } from './version';\nexport type { RouteParams } from './types';\n\n// Khởi tạo bridge ngay khi import (host có thể đẩy context trước khi app gọi hàm nào),\n// rồi báo \"ready\" khi DOM sẵn sàng.\nif (typeof window !== 'undefined') {\n getState();\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', notifyReady, { once: true });\n } else {\n queueMicrotask(notifyReady);\n }\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -25,8 +25,8 @@ interface RouteParams {
|
|
|
25
25
|
*/
|
|
26
26
|
declare const getRouteParams: () => RouteParams;
|
|
27
27
|
/**
|
|
28
|
-
* Mini app đang mở được
|
|
29
|
-
*
|
|
28
|
+
* Mini app đang mở sẵn được mở lại ở conversation khác (khác page / khách, cùng tổ chức).
|
|
29
|
+
* App không nạp lại trang → gọi lại API theo thông tin mới ở đây. Đổi tổ chức thì app nạp lại trang.
|
|
30
30
|
* @returns hàm huỷ lắng nghe
|
|
31
31
|
* @example
|
|
32
32
|
* onContextChange(({ page_id, client_id }) => loadData(page_id, client_id));
|
|
@@ -66,6 +66,53 @@ declare const onAppPaused: (listener: () => void) => (() => void);
|
|
|
66
66
|
*/
|
|
67
67
|
declare const onAppResumed: (listener: () => void) => (() => void);
|
|
68
68
|
|
|
69
|
+
interface PaymentRequest {
|
|
70
|
+
/** Số tiền theo đơn vị tiền tệ (vd 12.5 = 12,50 CAD). */
|
|
71
|
+
amount: number;
|
|
72
|
+
/** Mã tiền tệ ISO 4217: CAD, USD, VND… */
|
|
73
|
+
currency: string;
|
|
74
|
+
/** Mã đơn của bạn — app gắn vào giao dịch để đối soát. */
|
|
75
|
+
order_id?: string;
|
|
76
|
+
/** Ghi chú hiện trên giao dịch. */
|
|
77
|
+
note?: string;
|
|
78
|
+
/** Dữ liệu thêm app cần (vd chi tiết đơn) — tuỳ app. */
|
|
79
|
+
metadata?: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
interface PaymentResult {
|
|
82
|
+
/** Mã giao dịch phía cổng thanh toán. */
|
|
83
|
+
transaction_id: string;
|
|
84
|
+
/** Cổng thanh toán: SQUARE, STRIPE, VNPAY… */
|
|
85
|
+
provider: string;
|
|
86
|
+
[key: string]: unknown;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Thanh toán qua cổng của app (vd Tap to Pay). Resolve khi thanh toán thành công.
|
|
90
|
+
* Luôn đối soát lại qua backend theo `order_id`: web có thể đóng / hết thời gian chờ trong khi giao dịch
|
|
91
|
+
* phía app vẫn thành công.
|
|
92
|
+
* @example
|
|
93
|
+
* const { transaction_id, provider } = await requestPayment({ amount: 12.5, currency: 'CAD', order_id });
|
|
94
|
+
*/
|
|
95
|
+
declare const requestPayment: (request: PaymentRequest) => Promise<PaymentResult>;
|
|
96
|
+
interface PrintImageRequest {
|
|
97
|
+
/** Ảnh hoá đơn base64 — có hoặc không có tiền tố `data:image/png;base64,`. */
|
|
98
|
+
image: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* In ảnh (hoá đơn) ra máy in của app. App tự hiện chọn máy in nếu cần. Resolve khi in xong.
|
|
102
|
+
*/
|
|
103
|
+
declare const printImage: (request: PrintImageRequest) => Promise<void>;
|
|
104
|
+
interface OpenScreenRequest {
|
|
105
|
+
/** Tên màn hình app định nghĩa (vd `conversation`, `customer`) — xem tài liệu của app. */
|
|
106
|
+
screen: string;
|
|
107
|
+
params?: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Mở 1 màn hình của app (app đóng mini app rồi chuyển màn). Màn không tồn tại → NOT_SUPPORTED.
|
|
111
|
+
* @example
|
|
112
|
+
* await openScreen({ screen: 'customer', params: { customer_id } });
|
|
113
|
+
*/
|
|
114
|
+
declare const openScreen: (request: OpenScreenRequest) => Promise<void>;
|
|
115
|
+
|
|
69
116
|
declare const ErrorCode: {
|
|
70
117
|
/** Không chạy trong app Retion (trình duyệt / iframe cũ). */
|
|
71
118
|
readonly NOT_IN_HOST: "NOT_IN_HOST";
|
|
@@ -75,6 +122,10 @@ declare const ErrorCode: {
|
|
|
75
122
|
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
76
123
|
/** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
|
|
77
124
|
readonly NOT_SUPPORTED: "NOT_SUPPORTED";
|
|
125
|
+
/** Người dùng huỷ thao tác (đóng màn thanh toán, không chọn máy in…). */
|
|
126
|
+
readonly CANCELLED: "CANCELLED";
|
|
127
|
+
/** Đang có 1 lượt cùng loại chạy (vd đang thanh toán mà gọi requestPayment lần nữa). */
|
|
128
|
+
readonly BUSY: "BUSY";
|
|
78
129
|
/** Lỗi khác phía host. */
|
|
79
130
|
readonly HOST_ERROR: "HOST_ERROR";
|
|
80
131
|
};
|
|
@@ -93,6 +144,7 @@ declare class MiniAppError extends Error {
|
|
|
93
144
|
* Giao thức v1
|
|
94
145
|
* web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))
|
|
95
146
|
* host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)
|
|
147
|
+
* host → web event "context" → web gọi onContextChange → web → host "contextApplied"
|
|
96
148
|
* host inject trước khi trang chạy:
|
|
97
149
|
* window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK
|
|
98
150
|
* window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu
|
|
@@ -154,4 +206,4 @@ declare const isInHost: () => boolean;
|
|
|
154
206
|
/** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */
|
|
155
207
|
declare const SDK_VERSION: string;
|
|
156
208
|
|
|
157
|
-
export { ErrorCode, MiniAppError, type RouteParams, SDK_VERSION, type TokenType, closeApp, getAccessToken, getRouteParams, getToken, isInHost, onAppPaused, onAppResumed, onContextChange };
|
|
209
|
+
export { ErrorCode, MiniAppError, type OpenScreenRequest, type PaymentRequest, type PaymentResult, type PrintImageRequest, type RouteParams, SDK_VERSION, type TokenType, closeApp, getAccessToken, getRouteParams, getToken, isInHost, onAppPaused, onAppResumed, onContextChange, openScreen, printImage, requestPayment };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,8 +25,8 @@ interface RouteParams {
|
|
|
25
25
|
*/
|
|
26
26
|
declare const getRouteParams: () => RouteParams;
|
|
27
27
|
/**
|
|
28
|
-
* Mini app đang mở được
|
|
29
|
-
*
|
|
28
|
+
* Mini app đang mở sẵn được mở lại ở conversation khác (khác page / khách, cùng tổ chức).
|
|
29
|
+
* App không nạp lại trang → gọi lại API theo thông tin mới ở đây. Đổi tổ chức thì app nạp lại trang.
|
|
30
30
|
* @returns hàm huỷ lắng nghe
|
|
31
31
|
* @example
|
|
32
32
|
* onContextChange(({ page_id, client_id }) => loadData(page_id, client_id));
|
|
@@ -66,6 +66,53 @@ declare const onAppPaused: (listener: () => void) => (() => void);
|
|
|
66
66
|
*/
|
|
67
67
|
declare const onAppResumed: (listener: () => void) => (() => void);
|
|
68
68
|
|
|
69
|
+
interface PaymentRequest {
|
|
70
|
+
/** Số tiền theo đơn vị tiền tệ (vd 12.5 = 12,50 CAD). */
|
|
71
|
+
amount: number;
|
|
72
|
+
/** Mã tiền tệ ISO 4217: CAD, USD, VND… */
|
|
73
|
+
currency: string;
|
|
74
|
+
/** Mã đơn của bạn — app gắn vào giao dịch để đối soát. */
|
|
75
|
+
order_id?: string;
|
|
76
|
+
/** Ghi chú hiện trên giao dịch. */
|
|
77
|
+
note?: string;
|
|
78
|
+
/** Dữ liệu thêm app cần (vd chi tiết đơn) — tuỳ app. */
|
|
79
|
+
metadata?: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
interface PaymentResult {
|
|
82
|
+
/** Mã giao dịch phía cổng thanh toán. */
|
|
83
|
+
transaction_id: string;
|
|
84
|
+
/** Cổng thanh toán: SQUARE, STRIPE, VNPAY… */
|
|
85
|
+
provider: string;
|
|
86
|
+
[key: string]: unknown;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Thanh toán qua cổng của app (vd Tap to Pay). Resolve khi thanh toán thành công.
|
|
90
|
+
* Luôn đối soát lại qua backend theo `order_id`: web có thể đóng / hết thời gian chờ trong khi giao dịch
|
|
91
|
+
* phía app vẫn thành công.
|
|
92
|
+
* @example
|
|
93
|
+
* const { transaction_id, provider } = await requestPayment({ amount: 12.5, currency: 'CAD', order_id });
|
|
94
|
+
*/
|
|
95
|
+
declare const requestPayment: (request: PaymentRequest) => Promise<PaymentResult>;
|
|
96
|
+
interface PrintImageRequest {
|
|
97
|
+
/** Ảnh hoá đơn base64 — có hoặc không có tiền tố `data:image/png;base64,`. */
|
|
98
|
+
image: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* In ảnh (hoá đơn) ra máy in của app. App tự hiện chọn máy in nếu cần. Resolve khi in xong.
|
|
102
|
+
*/
|
|
103
|
+
declare const printImage: (request: PrintImageRequest) => Promise<void>;
|
|
104
|
+
interface OpenScreenRequest {
|
|
105
|
+
/** Tên màn hình app định nghĩa (vd `conversation`, `customer`) — xem tài liệu của app. */
|
|
106
|
+
screen: string;
|
|
107
|
+
params?: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Mở 1 màn hình của app (app đóng mini app rồi chuyển màn). Màn không tồn tại → NOT_SUPPORTED.
|
|
111
|
+
* @example
|
|
112
|
+
* await openScreen({ screen: 'customer', params: { customer_id } });
|
|
113
|
+
*/
|
|
114
|
+
declare const openScreen: (request: OpenScreenRequest) => Promise<void>;
|
|
115
|
+
|
|
69
116
|
declare const ErrorCode: {
|
|
70
117
|
/** Không chạy trong app Retion (trình duyệt / iframe cũ). */
|
|
71
118
|
readonly NOT_IN_HOST: "NOT_IN_HOST";
|
|
@@ -75,6 +122,10 @@ declare const ErrorCode: {
|
|
|
75
122
|
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
76
123
|
/** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
|
|
77
124
|
readonly NOT_SUPPORTED: "NOT_SUPPORTED";
|
|
125
|
+
/** Người dùng huỷ thao tác (đóng màn thanh toán, không chọn máy in…). */
|
|
126
|
+
readonly CANCELLED: "CANCELLED";
|
|
127
|
+
/** Đang có 1 lượt cùng loại chạy (vd đang thanh toán mà gọi requestPayment lần nữa). */
|
|
128
|
+
readonly BUSY: "BUSY";
|
|
78
129
|
/** Lỗi khác phía host. */
|
|
79
130
|
readonly HOST_ERROR: "HOST_ERROR";
|
|
80
131
|
};
|
|
@@ -93,6 +144,7 @@ declare class MiniAppError extends Error {
|
|
|
93
144
|
* Giao thức v1
|
|
94
145
|
* web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))
|
|
95
146
|
* host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)
|
|
147
|
+
* host → web event "context" → web gọi onContextChange → web → host "contextApplied"
|
|
96
148
|
* host inject trước khi trang chạy:
|
|
97
149
|
* window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK
|
|
98
150
|
* window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu
|
|
@@ -154,4 +206,4 @@ declare const isInHost: () => boolean;
|
|
|
154
206
|
/** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */
|
|
155
207
|
declare const SDK_VERSION: string;
|
|
156
208
|
|
|
157
|
-
export { ErrorCode, MiniAppError, type RouteParams, SDK_VERSION, type TokenType, closeApp, getAccessToken, getRouteParams, getToken, isInHost, onAppPaused, onAppResumed, onContextChange };
|
|
209
|
+
export { ErrorCode, MiniAppError, type OpenScreenRequest, type PaymentRequest, type PaymentResult, type PrintImageRequest, type RouteParams, SDK_VERSION, type TokenType, closeApp, getAccessToken, getRouteParams, getToken, isInHost, onAppPaused, onAppResumed, onContextChange, openScreen, printImage, requestPayment };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,10 @@ var ErrorCode = {
|
|
|
8
8
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
9
9
|
/** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
|
|
10
10
|
NOT_SUPPORTED: "NOT_SUPPORTED",
|
|
11
|
+
/** Người dùng huỷ thao tác (đóng màn thanh toán, không chọn máy in…). */
|
|
12
|
+
CANCELLED: "CANCELLED",
|
|
13
|
+
/** Đang có 1 lượt cùng loại chạy (vd đang thanh toán mà gọi requestPayment lần nữa). */
|
|
14
|
+
BUSY: "BUSY",
|
|
11
15
|
/** Lỗi khác phía host. */
|
|
12
16
|
HOST_ERROR: "HOST_ERROR"
|
|
13
17
|
};
|
|
@@ -39,6 +43,7 @@ var MiniAppError = class _MiniAppError extends Error {
|
|
|
39
43
|
var PROTOCOL_VERSION = 1;
|
|
40
44
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
41
45
|
var CONTEXT_EVENT = "context";
|
|
46
|
+
var CONTEXT_APPLIED = "contextApplied";
|
|
42
47
|
var hasWindow = typeof window !== "undefined";
|
|
43
48
|
var readInitialContext = () => {
|
|
44
49
|
var _a;
|
|
@@ -63,11 +68,11 @@ var createState = () => {
|
|
|
63
68
|
if (message.event === CONTEXT_EVENT) {
|
|
64
69
|
const prev = state.context;
|
|
65
70
|
const next = { ...prev, ...message.data };
|
|
66
|
-
if (JSON.stringify(next)
|
|
67
|
-
|
|
71
|
+
if (JSON.stringify(next) !== JSON.stringify(prev)) {
|
|
72
|
+
state.context = next;
|
|
73
|
+
emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
|
|
68
74
|
}
|
|
69
|
-
|
|
70
|
-
emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
|
|
75
|
+
notify(CONTEXT_APPLIED);
|
|
71
76
|
return;
|
|
72
77
|
}
|
|
73
78
|
emit(state, message.event, message.data);
|
|
@@ -147,7 +152,7 @@ var on = (event, listener) => {
|
|
|
147
152
|
};
|
|
148
153
|
|
|
149
154
|
// src/version.ts
|
|
150
|
-
var SDK_VERSION = "0.
|
|
155
|
+
var SDK_VERSION = "0.6.0" ;
|
|
151
156
|
|
|
152
157
|
// src/apis/app.ts
|
|
153
158
|
var closeApp = () => call("closeApp");
|
|
@@ -199,6 +204,12 @@ var getToken = async (type) => {
|
|
|
199
204
|
return token;
|
|
200
205
|
};
|
|
201
206
|
|
|
207
|
+
// src/apis/native.ts
|
|
208
|
+
var USER_ACTION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
209
|
+
var requestPayment = (request) => call("requestPayment", request, USER_ACTION_TIMEOUT_MS);
|
|
210
|
+
var printImage = (request) => call("printImage", request, USER_ACTION_TIMEOUT_MS);
|
|
211
|
+
var openScreen = (request) => call("openScreen", request);
|
|
212
|
+
|
|
202
213
|
// src/index.ts
|
|
203
214
|
if (typeof window !== "undefined") {
|
|
204
215
|
getState();
|
|
@@ -209,6 +220,6 @@ if (typeof window !== "undefined") {
|
|
|
209
220
|
}
|
|
210
221
|
}
|
|
211
222
|
|
|
212
|
-
export { ErrorCode, MiniAppError, SDK_VERSION, closeApp, getAccessToken, getRouteParams, getToken, isInHost, onAppPaused, onAppResumed, onContextChange };
|
|
223
|
+
export { ErrorCode, MiniAppError, SDK_VERSION, closeApp, getAccessToken, getRouteParams, getToken, isInHost, onAppPaused, onAppResumed, onContextChange, openScreen, printImage, requestPayment };
|
|
213
224
|
//# sourceMappingURL=index.js.map
|
|
214
225
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */\n NOT_SUPPORTED: 'NOT_SUPPORTED',\n /** Lỗi khác phía host. */\n HOST_ERROR: 'HOST_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] | (string & {});\n\nexport class MiniAppError extends Error {\n readonly code: ErrorCode;\n readonly method?: string;\n\n constructor(code: ErrorCode, message: string, method?: string) {\n super(message);\n this.name = 'MiniAppError';\n this.code = code;\n this.method = method;\n }\n\n /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */\n static from(error: unknown, method?: string): MiniAppError {\n if (error instanceof MiniAppError) {\n return error;\n }\n if (error && typeof error === 'object') {\n const { code, message } = error as { code?: unknown; message?: unknown };\n return new MiniAppError(\n typeof code === 'string' ? code : ErrorCode.HOST_ERROR,\n typeof message === 'string' ? message : 'Unknown error',\n method,\n );\n }\n return new MiniAppError(ErrorCode.HOST_ERROR, String(error), method);\n }\n}\n","/**\n * Bridge web mini app ↔ host (app Retion). Nội bộ SDK — dev mini app không import trực tiếp.\n *\n * Giao thức v1\n * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))\n * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)\n * host inject trước khi trang chạy:\n * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK\n * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu\n *\n * Chỉ có ReactNativeWebView KHÔNG đủ để coi là host: màn iframe cũ của app cũng là RN WebView\n * nhưng không hiểu giao thức này (gọi API sẽ treo tới timeout). Thiếu marker → NOT_IN_HOST ngay.\n *\n * State đặt trên window (singleton): nhiều bản SDK trong cùng trang (trùng version trong\n * node_modules) vẫn dùng chung 1 bridge thay vì ghi đè hàm receive của nhau.\n */\nimport { ErrorCode, MiniAppError } from './errors';\nimport type { RouteParams } from './types';\n\nexport const PROTOCOL_VERSION = 1;\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const CONTEXT_EVENT = 'context';\n\nexport interface RequestMessage {\n __retion: typeof PROTOCOL_VERSION;\n /** Có id → host phải trả ResponseMessage. Không có → lệnh 1 chiều. */\n id?: string;\n method: string;\n params?: unknown;\n}\n\nexport type ResponseMessage =\n | { id: string; result?: unknown; error?: undefined }\n | { id: string; error: { code: string; message: string } };\n\nexport interface EventMessage {\n event: string;\n data?: unknown;\n}\n\nexport interface HostInfo {\n /** Phiên bản giao thức host hỗ trợ. */\n protocol: number;\n}\n\ntype Listener = (data: unknown) => void;\n\ninterface Pending {\n method: string;\n resolve: (value: unknown) => void;\n reject: (error: MiniAppError) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface BridgeState {\n protocol: number;\n seq: number;\n pending: Map<string, Pending>;\n listeners: Map<string, Set<Listener>>;\n context: RouteParams;\n receive: (message: ResponseMessage | EventMessage) => void;\n}\n\ndeclare global {\n interface Window {\n ReactNativeWebView?: { postMessage: (message: string) => void };\n __RETION_MINIAPP__?: BridgeState;\n __RETION_MINIAPP_CONTEXT__?: RouteParams;\n __RETION_MINIAPP_HOST__?: HostInfo;\n }\n}\n\nconst hasWindow = typeof window !== 'undefined';\n\nconst readInitialContext = (): RouteParams => {\n if (!hasWindow) {\n return {};\n }\n const query = Object.fromEntries(new URLSearchParams(window.location.search));\n return { ...query, ...(window.__RETION_MINIAPP_CONTEXT__ ?? {}) };\n};\n\nconst createState = (): BridgeState => {\n const state: BridgeState = {\n protocol: PROTOCOL_VERSION,\n seq: 0,\n pending: new Map(),\n listeners: new Map(),\n context: readInitialContext(),\n receive: (message) => {\n if (!message || typeof message !== 'object') {\n return;\n }\n if ('event' in message) {\n if (message.event === CONTEXT_EVENT) {\n // Cập nhật context TRƯỚC khi emit → getRouteParams() luôn đúng, kể cả không ai lắng nghe.\n const prev = state.context;\n const next = { ...prev, ...(message.data as RouteParams) };\n // Host đẩy lại context lúc \"ready\" dù không đổi → không báo onContextChange thừa.\n if (JSON.stringify(next) === JSON.stringify(prev)) {\n return;\n }\n state.context = next;\n emit(state, CONTEXT_EVENT, { next: { ...next }, prev });\n return;\n }\n emit(state, message.event, message.data);\n return;\n }\n const pending = state.pending.get(message.id);\n if (!pending) {\n return;\n }\n state.pending.delete(message.id);\n clearTimeout(pending.timer);\n if (message.error) {\n pending.reject(MiniAppError.from(message.error, pending.method));\n } else {\n pending.resolve(message.result);\n }\n },\n };\n return state;\n};\n\nconst emit = (state: BridgeState, event: string, data: unknown) => {\n state.listeners.get(event)?.forEach((listener) => {\n try {\n listener(data);\n } catch (error) {\n console.error(`[retion-miniapp] listener \"${event}\" lỗi`, error);\n }\n });\n};\n\nlet fallback_state: BridgeState | null = null;\n\n/** @internal */\nexport const getState = (): BridgeState => {\n if (!hasWindow) {\n return (fallback_state ??= createState());\n }\n return (window.__RETION_MINIAPP__ ??= createState());\n};\n\n/** Đang chạy trong app Retion có hỗ trợ SDK (host đã inject marker + kênh postMessage). */\nexport const isInHost = (): boolean =>\n hasWindow &&\n typeof window.__RETION_MINIAPP_HOST__?.protocol === 'number' &&\n typeof window.ReactNativeWebView?.postMessage === 'function';\n\nconst post = (message: RequestMessage) => {\n window.ReactNativeWebView!.postMessage(JSON.stringify(message));\n};\n\n/** Gửi lệnh lên host và chờ kết quả. Ngoài app → reject NOT_IN_HOST. */\nexport const call = <T>(method: string, params?: unknown, timeout_ms = DEFAULT_TIMEOUT_MS): Promise<T> => {\n const state = getState();\n\n if (!isInHost()) {\n return Promise.reject(new MiniAppError(ErrorCode.NOT_IN_HOST, `${method} chỉ chạy trong app Retion`, method));\n }\n\n const id = `${Date.now().toString(36)}-${++state.seq}`;\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n state.pending.delete(id);\n reject(new MiniAppError(ErrorCode.TIMEOUT, `${method}: host không phản hồi sau ${timeout_ms}ms`, method));\n }, timeout_ms);\n state.pending.set(id, { method, resolve: resolve as (v: unknown) => void, reject, timer });\n post({ __retion: PROTOCOL_VERSION, id, method, params });\n });\n};\n\n/** Lệnh 1 chiều (không chờ kết quả). Ngoài app: bỏ qua. */\nexport const notify = (method: string, params?: unknown): void => {\n if (isInHost()) {\n post({ __retion: PROTOCOL_VERSION, method, params });\n }\n};\n\n/** Lắng nghe event host đẩy xuống. Trả về hàm huỷ. */\nexport const on = (event: string, listener: Listener): (() => void) => {\n const { listeners } = getState();\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n set.add(listener);\n return () => {\n set.delete(listener);\n };\n};\n","declare const __SDK_VERSION__: string;\n\n/** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */\nexport const SDK_VERSION: string = typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev';\n","import { call, notify, on } from '../bridge';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Đóng mini app, quay về màn chat. Ngoài app → reject NOT_IN_HOST (web tự xử lý, vd router.back()).\n */\nexport const closeApp = (): Promise<void> => call<void>('closeApp');\n\n/** @internal SDK tự gọi khi trang sẵn sàng — host ẩn splash + đẩy context mới nhất. */\nexport const notifyReady = (): void => notify('ready', { sdkVersion: SDK_VERSION });\n\n/**\n * Mini app bị đóng (ẩn, vẫn chạy ngầm) — dừng polling / timer / video ở đây cho đỡ tốn pin.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppPaused = (listener: () => void): (() => void) => on('paused', () => listener());\n\n/**\n * Mini app được mở lại (trang vẫn giữ nguyên) — làm mới dữ liệu nếu cần.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppResumed = (listener: () => void): (() => void) => on('resumed', () => listener());\n","import { CONTEXT_EVENT, getState, on } from '../bridge';\nimport type { RouteParams } from '../types';\n\n/**\n * Thông tin conversation lúc mở mini app (đồng bộ, gọi bất cứ lúc nào).\n * @example\n * const { page_id, client_id } = getRouteParams();\n */\nexport const getRouteParams = (): RouteParams => ({ ...getState().context });\n\n/**\n * Mini app đang mở được chuyển sang page / conversation khác (không nạp lại trang).\n * Gọi lại API theo thông tin mới ở đây. Chỉ xảy ra khi `miniapp.config.json` có `context_update: true`.\n * @returns hàm huỷ lắng nghe\n * @example\n * onContextChange(({ page_id, client_id }) => loadData(page_id, client_id));\n */\nexport const onContextChange = (listener: (next: RouteParams, prev: RouteParams) => void): (() => void) =>\n on(CONTEXT_EVENT, (data) => {\n const { next, prev } = data as { next: RouteParams; prev: RouteParams };\n listener(next, prev);\n });\n","import { call, isInHost } from '../bridge';\nimport { ErrorCode, MiniAppError } from '../errors';\n\n/** Tên query param chứa token khi chạy ngoài app (giống URL iframe hiện tại). */\nconst TOKEN_QUERY_PARAM = 'access_token';\n\nconst readTokenFromUrl = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n return new URLSearchParams(window.location.search).get(TOKEN_QUERY_PARAM);\n};\n\n/**\n * Token để mini app gọi API backend của chính mình (Authorization: Bearer ...).\n * - Trong app: lấy từ host, luôn là token của page hiện tại, host tự refresh.\n * - Ngoài app: đọc `?access_token=` trên URL (như iframe hiện tại).\n * Gọi lại mỗi khi cần — không lưu token lâu phía web.\n */\nexport const getAccessToken = async (): Promise<string> => {\n if (isInHost()) {\n return call<string>('getAccessToken');\n }\n const token = readTokenFromUrl();\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${TOKEN_QUERY_PARAM}= trên URL`, 'getAccessToken');\n }\n return token;\n};\n\n/** Loại token: `access` luôn được phép; `partner` / `chatbox` cần admin bật quyền trên Console. */\nexport type TokenType = 'access' | 'partner' | 'chatbox';\n\n/** Tên query param tương ứng khi chạy ngoài app (giống URL iframe hiện tại). */\nconst QUERY_PARAM: Record<TokenType, string> = {\n access: 'access_token',\n partner: 'partner_token',\n chatbox: 'chatbox_token',\n};\n\n/**\n * Lấy token theo loại.\n * - `access`: token của mini app cho page hiện tại (= getAccessToken()).\n * - `partner`: token đối tác của page — cần quyền `partner_token`.\n * - `chatbox`: token đăng nhập của người dùng app (toàn quyền) — cần quyền `chatbox_token`.\n * Trong app, chưa được cấp quyền → MiniAppError PERMISSION_DENIED. Ngoài app: đọc query URL.\n */\nexport const getToken = async (type: TokenType): Promise<string> => {\n if (!(type in QUERY_PARAM)) {\n throw new MiniAppError(ErrorCode.HOST_ERROR, `Loại token không hợp lệ: ${String(type)}`, 'getToken');\n }\n if (isInHost()) {\n return call<string>('getToken', { type });\n }\n const token = typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get(QUERY_PARAM[type]);\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${QUERY_PARAM[type]}= trên URL`, 'getToken');\n }\n return token;\n};\n","import { getState } from './bridge';\nimport { notifyReady } from './apis/app';\n\nexport { getRouteParams, onContextChange } from './apis/route';\nexport { getAccessToken, getToken, type TokenType } from './apis/user';\nexport { closeApp, onAppPaused, onAppResumed } from './apis/app';\nexport { isInHost } from './bridge';\nexport { MiniAppError, ErrorCode } from './errors';\nexport { SDK_VERSION } from './version';\nexport type { RouteParams } from './types';\n\n// Khởi tạo bridge ngay khi import (host có thể đẩy context trước khi app gọi hàm nào),\n// rồi báo \"ready\" khi DOM sẵn sàng.\nif (typeof window !== 'undefined') {\n getState();\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', notifyReady, { once: true });\n } else {\n queueMicrotask(notifyReady);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/apis/native.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */\n NOT_SUPPORTED: 'NOT_SUPPORTED',\n /** Người dùng huỷ thao tác (đóng màn thanh toán, không chọn máy in…). */\n CANCELLED: 'CANCELLED',\n /** Đang có 1 lượt cùng loại chạy (vd đang thanh toán mà gọi requestPayment lần nữa). */\n BUSY: 'BUSY',\n /** Lỗi khác phía host. */\n HOST_ERROR: 'HOST_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] | (string & {});\n\nexport class MiniAppError extends Error {\n readonly code: ErrorCode;\n readonly method?: string;\n\n constructor(code: ErrorCode, message: string, method?: string) {\n super(message);\n this.name = 'MiniAppError';\n this.code = code;\n this.method = method;\n }\n\n /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */\n static from(error: unknown, method?: string): MiniAppError {\n if (error instanceof MiniAppError) {\n return error;\n }\n if (error && typeof error === 'object') {\n const { code, message } = error as { code?: unknown; message?: unknown };\n return new MiniAppError(\n typeof code === 'string' ? code : ErrorCode.HOST_ERROR,\n typeof message === 'string' ? message : 'Unknown error',\n method,\n );\n }\n return new MiniAppError(ErrorCode.HOST_ERROR, String(error), method);\n }\n}\n","/**\n * Bridge web mini app ↔ host (app Retion). Nội bộ SDK — dev mini app không import trực tiếp.\n *\n * Giao thức v1\n * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))\n * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)\n * host → web event \"context\" → web gọi onContextChange → web → host \"contextApplied\"\n * host inject trước khi trang chạy:\n * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK\n * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu\n *\n * Chỉ có ReactNativeWebView KHÔNG đủ để coi là host: màn iframe cũ của app cũng là RN WebView\n * nhưng không hiểu giao thức này (gọi API sẽ treo tới timeout). Thiếu marker → NOT_IN_HOST ngay.\n *\n * State đặt trên window (singleton): nhiều bản SDK trong cùng trang (trùng version trong\n * node_modules) vẫn dùng chung 1 bridge thay vì ghi đè hàm receive của nhau.\n */\nimport { ErrorCode, MiniAppError } from './errors';\nimport type { RouteParams } from './types';\n\nexport const PROTOCOL_VERSION = 1;\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const CONTEXT_EVENT = 'context';\n/** web → host: đã áp dụng context mới (sau khi gọi các listener onContextChange). */\nexport const CONTEXT_APPLIED = 'contextApplied';\n\nexport interface RequestMessage {\n __retion: typeof PROTOCOL_VERSION;\n /** Có id → host phải trả ResponseMessage. Không có → lệnh 1 chiều. */\n id?: string;\n method: string;\n params?: unknown;\n}\n\nexport type ResponseMessage =\n | { id: string; result?: unknown; error?: undefined }\n | { id: string; error: { code: string; message: string } };\n\nexport interface EventMessage {\n event: string;\n data?: unknown;\n}\n\nexport interface HostInfo {\n /** Phiên bản giao thức host hỗ trợ. */\n protocol: number;\n}\n\ntype Listener = (data: unknown) => void;\n\ninterface Pending {\n method: string;\n resolve: (value: unknown) => void;\n reject: (error: MiniAppError) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface BridgeState {\n protocol: number;\n seq: number;\n pending: Map<string, Pending>;\n listeners: Map<string, Set<Listener>>;\n context: RouteParams;\n receive: (message: ResponseMessage | EventMessage) => void;\n}\n\ndeclare global {\n interface Window {\n ReactNativeWebView?: { postMessage: (message: string) => void };\n __RETION_MINIAPP__?: BridgeState;\n __RETION_MINIAPP_CONTEXT__?: RouteParams;\n __RETION_MINIAPP_HOST__?: HostInfo;\n }\n}\n\nconst hasWindow = typeof window !== 'undefined';\n\nconst readInitialContext = (): RouteParams => {\n if (!hasWindow) {\n return {};\n }\n const query = Object.fromEntries(new URLSearchParams(window.location.search));\n return { ...query, ...(window.__RETION_MINIAPP_CONTEXT__ ?? {}) };\n};\n\nconst createState = (): BridgeState => {\n const state: BridgeState = {\n protocol: PROTOCOL_VERSION,\n seq: 0,\n pending: new Map(),\n listeners: new Map(),\n context: readInitialContext(),\n receive: (message) => {\n if (!message || typeof message !== 'object') {\n return;\n }\n if ('event' in message) {\n if (message.event === CONTEXT_EVENT) {\n // Cập nhật context TRƯỚC khi emit → getRouteParams() luôn đúng, kể cả không ai lắng nghe.\n const prev = state.context;\n const next = { ...prev, ...(message.data as RouteParams) };\n // Host đẩy lại context lúc \"ready\" dù không đổi → không báo onContextChange thừa.\n if (JSON.stringify(next) !== JSON.stringify(prev)) {\n state.context = next;\n emit(state, CONTEXT_EVENT, { next: { ...next }, prev });\n }\n // Báo host: web đã nhận context (listener đã chạy) → host bỏ lớp che nội dung page cũ.\n notify(CONTEXT_APPLIED);\n return;\n }\n emit(state, message.event, message.data);\n return;\n }\n const pending = state.pending.get(message.id);\n if (!pending) {\n return;\n }\n state.pending.delete(message.id);\n clearTimeout(pending.timer);\n if (message.error) {\n pending.reject(MiniAppError.from(message.error, pending.method));\n } else {\n pending.resolve(message.result);\n }\n },\n };\n return state;\n};\n\nconst emit = (state: BridgeState, event: string, data: unknown) => {\n state.listeners.get(event)?.forEach((listener) => {\n try {\n listener(data);\n } catch (error) {\n console.error(`[retion-miniapp] listener \"${event}\" lỗi`, error);\n }\n });\n};\n\nlet fallback_state: BridgeState | null = null;\n\n/** @internal */\nexport const getState = (): BridgeState => {\n if (!hasWindow) {\n return (fallback_state ??= createState());\n }\n return (window.__RETION_MINIAPP__ ??= createState());\n};\n\n/** Đang chạy trong app Retion có hỗ trợ SDK (host đã inject marker + kênh postMessage). */\nexport const isInHost = (): boolean =>\n hasWindow &&\n typeof window.__RETION_MINIAPP_HOST__?.protocol === 'number' &&\n typeof window.ReactNativeWebView?.postMessage === 'function';\n\nconst post = (message: RequestMessage) => {\n window.ReactNativeWebView!.postMessage(JSON.stringify(message));\n};\n\n/** Gửi lệnh lên host và chờ kết quả. Ngoài app → reject NOT_IN_HOST. */\nexport const call = <T>(method: string, params?: unknown, timeout_ms = DEFAULT_TIMEOUT_MS): Promise<T> => {\n const state = getState();\n\n if (!isInHost()) {\n return Promise.reject(new MiniAppError(ErrorCode.NOT_IN_HOST, `${method} chỉ chạy trong app Retion`, method));\n }\n\n const id = `${Date.now().toString(36)}-${++state.seq}`;\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n state.pending.delete(id);\n reject(new MiniAppError(ErrorCode.TIMEOUT, `${method}: host không phản hồi sau ${timeout_ms}ms`, method));\n }, timeout_ms);\n state.pending.set(id, { method, resolve: resolve as (v: unknown) => void, reject, timer });\n post({ __retion: PROTOCOL_VERSION, id, method, params });\n });\n};\n\n/** Lệnh 1 chiều (không chờ kết quả). Ngoài app: bỏ qua. */\nexport const notify = (method: string, params?: unknown): void => {\n if (isInHost()) {\n post({ __retion: PROTOCOL_VERSION, method, params });\n }\n};\n\n/** Lắng nghe event host đẩy xuống. Trả về hàm huỷ. */\nexport const on = (event: string, listener: Listener): (() => void) => {\n const { listeners } = getState();\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n set.add(listener);\n return () => {\n set.delete(listener);\n };\n};\n","declare const __SDK_VERSION__: string;\n\n/** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */\nexport const SDK_VERSION: string = typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev';\n","import { call, notify, on } from '../bridge';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Đóng mini app, quay về màn chat. Ngoài app → reject NOT_IN_HOST (web tự xử lý, vd router.back()).\n */\nexport const closeApp = (): Promise<void> => call<void>('closeApp');\n\n/** @internal SDK tự gọi khi trang sẵn sàng — host ẩn splash + đẩy context mới nhất. */\nexport const notifyReady = (): void => notify('ready', { sdkVersion: SDK_VERSION });\n\n/**\n * Mini app bị đóng (ẩn, vẫn chạy ngầm) — dừng polling / timer / video ở đây cho đỡ tốn pin.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppPaused = (listener: () => void): (() => void) => on('paused', () => listener());\n\n/**\n * Mini app được mở lại (trang vẫn giữ nguyên) — làm mới dữ liệu nếu cần.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppResumed = (listener: () => void): (() => void) => on('resumed', () => listener());\n","import { CONTEXT_EVENT, getState, on } from '../bridge';\nimport type { RouteParams } from '../types';\n\n/**\n * Thông tin conversation lúc mở mini app (đồng bộ, gọi bất cứ lúc nào).\n * @example\n * const { page_id, client_id } = getRouteParams();\n */\nexport const getRouteParams = (): RouteParams => ({ ...getState().context });\n\n/**\n * Mini app đang mở sẵn được mở lại ở conversation khác (khác page / khách, cùng tổ chức).\n * App không nạp lại trang → gọi lại API theo thông tin mới ở đây. Đổi tổ chức thì app nạp lại trang.\n * @returns hàm huỷ lắng nghe\n * @example\n * onContextChange(({ page_id, client_id }) => loadData(page_id, client_id));\n */\nexport const onContextChange = (listener: (next: RouteParams, prev: RouteParams) => void): (() => void) =>\n on(CONTEXT_EVENT, (data) => {\n const { next, prev } = data as { next: RouteParams; prev: RouteParams };\n listener(next, prev);\n });\n","import { call, isInHost } from '../bridge';\nimport { ErrorCode, MiniAppError } from '../errors';\n\n/** Tên query param chứa token khi chạy ngoài app (giống URL iframe hiện tại). */\nconst TOKEN_QUERY_PARAM = 'access_token';\n\nconst readTokenFromUrl = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n return new URLSearchParams(window.location.search).get(TOKEN_QUERY_PARAM);\n};\n\n/**\n * Token để mini app gọi API backend của chính mình (Authorization: Bearer ...).\n * - Trong app: lấy từ host, luôn là token của page hiện tại, host tự refresh.\n * - Ngoài app: đọc `?access_token=` trên URL (như iframe hiện tại).\n * Gọi lại mỗi khi cần — không lưu token lâu phía web.\n */\nexport const getAccessToken = async (): Promise<string> => {\n if (isInHost()) {\n return call<string>('getAccessToken');\n }\n const token = readTokenFromUrl();\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${TOKEN_QUERY_PARAM}= trên URL`, 'getAccessToken');\n }\n return token;\n};\n\n/** Loại token: `access` luôn được phép; `partner` / `chatbox` cần admin bật quyền trên Console. */\nexport type TokenType = 'access' | 'partner' | 'chatbox';\n\n/** Tên query param tương ứng khi chạy ngoài app (giống URL iframe hiện tại). */\nconst QUERY_PARAM: Record<TokenType, string> = {\n access: 'access_token',\n partner: 'partner_token',\n chatbox: 'chatbox_token',\n};\n\n/**\n * Lấy token theo loại.\n * - `access`: token của mini app cho page hiện tại (= getAccessToken()).\n * - `partner`: token đối tác của page — cần quyền `partner_token`.\n * - `chatbox`: token đăng nhập của người dùng app (toàn quyền) — cần quyền `chatbox_token`.\n * Trong app, chưa được cấp quyền → MiniAppError PERMISSION_DENIED. Ngoài app: đọc query URL.\n */\nexport const getToken = async (type: TokenType): Promise<string> => {\n if (!(type in QUERY_PARAM)) {\n throw new MiniAppError(ErrorCode.HOST_ERROR, `Loại token không hợp lệ: ${String(type)}`, 'getToken');\n }\n if (isInHost()) {\n return call<string>('getToken', { type });\n }\n const token = typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get(QUERY_PARAM[type]);\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${QUERY_PARAM[type]}= trên URL`, 'getToken');\n }\n return token;\n};\n","import { call } from '../bridge';\n\n/**\n * Chức năng native do app cung cấp (thanh toán, in, mở màn hình của app).\n * Mỗi app tự cài phần xử lý — app không hỗ trợ → MiniAppError NOT_SUPPORTED (web ẩn nút / báo người dùng).\n * Người dùng huỷ (đóng màn thanh toán, không chọn máy in…) → MiniAppError CANCELLED.\n * Đang thanh toán / in mà gọi lần nữa (bấm 2 lần) → MiniAppError BUSY — lượt đầu vẫn chạy bình thường.\n */\n\n/** Chờ người dùng thao tác (chạm thẻ, chọn máy in) — lâu hơn timeout mặc định 15s. */\nconst USER_ACTION_TIMEOUT_MS = 10 * 60 * 1000;\n\nexport interface PaymentRequest {\n /** Số tiền theo đơn vị tiền tệ (vd 12.5 = 12,50 CAD). */\n amount: number;\n /** Mã tiền tệ ISO 4217: CAD, USD, VND… */\n currency: string;\n /** Mã đơn của bạn — app gắn vào giao dịch để đối soát. */\n order_id?: string;\n /** Ghi chú hiện trên giao dịch. */\n note?: string;\n /** Dữ liệu thêm app cần (vd chi tiết đơn) — tuỳ app. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface PaymentResult {\n /** Mã giao dịch phía cổng thanh toán. */\n transaction_id: string;\n /** Cổng thanh toán: SQUARE, STRIPE, VNPAY… */\n provider: string;\n [key: string]: unknown;\n}\n\n/**\n * Thanh toán qua cổng của app (vd Tap to Pay). Resolve khi thanh toán thành công.\n * Luôn đối soát lại qua backend theo `order_id`: web có thể đóng / hết thời gian chờ trong khi giao dịch\n * phía app vẫn thành công.\n * @example\n * const { transaction_id, provider } = await requestPayment({ amount: 12.5, currency: 'CAD', order_id });\n */\nexport const requestPayment = (request: PaymentRequest): Promise<PaymentResult> =>\n call<PaymentResult>('requestPayment', request, USER_ACTION_TIMEOUT_MS);\n\nexport interface PrintImageRequest {\n /** Ảnh hoá đơn base64 — có hoặc không có tiền tố `data:image/png;base64,`. */\n image: string;\n}\n\n/**\n * In ảnh (hoá đơn) ra máy in của app. App tự hiện chọn máy in nếu cần. Resolve khi in xong.\n */\nexport const printImage = (request: PrintImageRequest): Promise<void> =>\n call<void>('printImage', request, USER_ACTION_TIMEOUT_MS);\n\nexport interface OpenScreenRequest {\n /** Tên màn hình app định nghĩa (vd `conversation`, `customer`) — xem tài liệu của app. */\n screen: string;\n params?: Record<string, unknown>;\n}\n\n/**\n * Mở 1 màn hình của app (app đóng mini app rồi chuyển màn). Màn không tồn tại → NOT_SUPPORTED.\n * @example\n * await openScreen({ screen: 'customer', params: { customer_id } });\n */\nexport const openScreen = (request: OpenScreenRequest): Promise<void> =>\n call<void>('openScreen', request);\n","import { getState } from './bridge';\nimport { notifyReady } from './apis/app';\n\nexport { getRouteParams, onContextChange } from './apis/route';\nexport { getAccessToken, getToken, type TokenType } from './apis/user';\nexport { closeApp, onAppPaused, onAppResumed } from './apis/app';\nexport {\n openScreen,\n printImage,\n requestPayment,\n type OpenScreenRequest,\n type PaymentRequest,\n type PaymentResult,\n type PrintImageRequest,\n} from './apis/native';\nexport { isInHost } from './bridge';\nexport { MiniAppError, ErrorCode } from './errors';\nexport { SDK_VERSION } from './version';\nexport type { RouteParams } from './types';\n\n// Khởi tạo bridge ngay khi import (host có thể đẩy context trước khi app gọi hàm nào),\n// rồi báo \"ready\" khi DOM sẵn sàng.\nif (typeof window !== 'undefined') {\n getState();\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', notifyReady, { once: true });\n } else {\n queueMicrotask(notifyReady);\n }\n}\n"]}
|