@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 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, closeApp } from '@retion-miniapp/sdk';
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
- Thông tin conversation đang mở mini app. Đồng bộ, gọi lúc nào cũng được.
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, không lưu lại.
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('chatbox');
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 (toàn quyền như người dùng) | `chatbox_token` |
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
- Ngoài app: đọc `?access_token=` / `?partner_token=` / `?chatbox_token=` trên URL.
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
- > `getAccessToken()` = `getToken('access')`.
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, `false` nếu trên trình duyệt / iframe cũ.
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
- ## `onContextChange(callback)`
148
+ ## `onAppPaused(callback)` / `onAppResumed(callback)`
83
149
 
84
- Mini app được mở ở page / conversation khác **mà không nạp lại trang**. Gọi lại API ở đây.
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
- onContextChange((next, prev) => load());
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
- | Tham số | Ý nghĩa |
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
- | `next` | `RouteParams` mới (như `getRouteParams()`) |
93
- | `prev` | `RouteParams` trước đó |
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
- Chỉ xảy ra khi `miniapp.config.json` có `"context_update": true`. Mặc định (`false`) app tự nạp lại trang → không cần hàm này. Đổi tổ chức luôn nạp lại trang.
172
+ ### `requestPayment(request)`
96
173
 
97
- Trả về hàm huỷ lắng nghe.
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
- ## `onAppPaused(callback)` / `onAppResumed(callback)`
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
- Mini app bị đóng (vẫn chạy ngầm) / được mở lại.
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
- onAppPaused(() => clearInterval(timer)); // dừng polling, video...
107
- onAppResumed(() => load()); // làm mới dữ liệu
208
+ await printImage({ image: canvas.toDataURL('image/png') });
108
209
  ```
109
210
 
110
- Trả về hàm huỷ lắng nghe.
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) { /* chưa được cấp quyền */ }
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
- Deploy lên app: [`@retion-miniapp/cli`](https://www.npmjs.com/package/@retion-miniapp/cli).
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) === JSON.stringify(prev)) {
69
- return;
73
+ if (JSON.stringify(next) !== JSON.stringify(prev)) {
74
+ state.context = next;
75
+ emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
70
76
  }
71
- state.context = next;
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.4.1" ;
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
@@ -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 chuyển sang page / conversation khác (không nạp lại trang).
29
- * Gọi lại API theo thông tin mới ở đây. Chỉ xảy ra khi `miniapp.config.json` có `context_update: true`.
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 chuyển sang page / conversation khác (không nạp lại trang).
29
- * Gọi lại API theo thông tin mới ở đây. Chỉ xảy ra khi `miniapp.config.json` có `context_update: true`.
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) === JSON.stringify(prev)) {
67
- return;
71
+ if (JSON.stringify(next) !== JSON.stringify(prev)) {
72
+ state.context = next;
73
+ emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
68
74
  }
69
- state.context = next;
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.4.1" ;
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":";AAAO,IAAM,SAAA,GAAY;AAAA;AAAA,EAEvB,WAAA,EAAa,aAAA;AAAA;AAAA,EAEb,OAAA,EAAS,SAAA;AAAA;AAAA,EAET,iBAAA,EAAmB,mBAAA;AAAA;AAAA,EAEnB,aAAA,EAAe,eAAA;AAAA;AAAA,EAEf,UAAA,EAAY;AACd;AAIO,IAAM,YAAA,GAAN,MAAM,aAAA,SAAqB,KAAA,CAAM;AAAA,EAItC,WAAA,CAAY,IAAA,EAAiB,OAAA,EAAiB,MAAA,EAAiB;AAC7D,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA,EAGA,OAAO,IAAA,CAAK,KAAA,EAAgB,MAAA,EAA+B;AACzD,IAAA,IAAI,iBAAiB,aAAA,EAAc;AACjC,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACtC,MAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAQ,GAAI,KAAA;AAC1B,MAAA,OAAO,IAAI,aAAA;AAAA,QACT,OAAO,IAAA,KAAS,QAAA,GAAW,IAAA,GAAO,SAAA,CAAU,UAAA;AAAA,QAC5C,OAAO,OAAA,KAAY,QAAA,GAAW,OAAA,GAAU,eAAA;AAAA,QACxC;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAO,IAAI,aAAA,CAAa,SAAA,CAAU,YAAY,MAAA,CAAO,KAAK,GAAG,MAAM,CAAA;AAAA,EACrE;AACF;;;ACtBO,IAAM,gBAAA,GAAmB,CAAA;AACzB,IAAM,kBAAA,GAAqB,IAAA;AAC3B,IAAM,aAAA,GAAgB,SAAA;AAmD7B,IAAM,SAAA,GAAY,OAAO,MAAA,KAAW,WAAA;AAEpC,IAAM,qBAAqB,MAAmB;AA1E9C,EAAA,IAAA,EAAA;AA2EE,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO,EAAC;AAAA,EACV;AACA,EAAA,MAAM,KAAA,GAAQ,OAAO,WAAA,CAAY,IAAI,gBAAgB,MAAA,CAAO,QAAA,CAAS,MAAM,CAAC,CAAA;AAC5E,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,GAAA,CAAI,YAAO,0BAAA,KAAP,IAAA,GAAA,EAAA,GAAqC,EAAC,EAAG;AAClE,CAAA;AAEA,IAAM,cAAc,MAAmB;AACrC,EAAA,MAAM,KAAA,GAAqB;AAAA,IACzB,QAAA,EAAU,gBAAA;AAAA,IACV,GAAA,EAAK,CAAA;AAAA,IACL,OAAA,sBAAa,GAAA,EAAI;AAAA,IACjB,SAAA,sBAAe,GAAA,EAAI;AAAA,IACnB,SAAS,kBAAA,EAAmB;AAAA,IAC5B,OAAA,EAAS,CAAC,OAAA,KAAY;AACpB,MAAA,IAAI,CAAC,OAAA,IAAW,OAAO,OAAA,KAAY,QAAA,EAAU;AAC3C,QAAA;AAAA,MACF;AACA,MAAA,IAAI,WAAW,OAAA,EAAS;AACtB,QAAA,IAAI,OAAA,CAAQ,UAAU,aAAA,EAAe;AAEnC,UAAA,MAAM,OAAO,KAAA,CAAM,OAAA;AACnB,UAAA,MAAM,OAAO,EAAE,GAAG,IAAA,EAAM,GAAI,QAAQ,IAAA,EAAqB;AAEzD,UAAA,IAAI,KAAK,SAAA,CAAU,IAAI,MAAM,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,EAAG;AACjD,YAAA;AAAA,UACF;AACA,UAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAChB,UAAA,IAAA,CAAK,KAAA,EAAO,eAAe,EAAE,IAAA,EAAM,EAAE,GAAG,IAAA,EAAK,EAAG,IAAA,EAAM,CAAA;AACtD,UAAA;AAAA,QACF;AACA,QAAA,IAAA,CAAK,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,OAAA,CAAQ,IAAI,CAAA;AACvC,QAAA;AAAA,MACF;AACA,MAAA,MAAM,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,QAAQ,EAAE,CAAA;AAC5C,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA;AAAA,MACF;AACA,MAAA,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,EAAE,CAAA;AAC/B,MAAA,YAAA,CAAa,QAAQ,KAAK,CAAA;AAC1B,MAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,QAAA,OAAA,CAAQ,OAAO,YAAA,CAAa,IAAA,CAAK,QAAQ,KAAA,EAAO,OAAA,CAAQ,MAAM,CAAC,CAAA;AAAA,MACjE,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,OAAA,CAAQ,QAAQ,MAAM,CAAA;AAAA,MAChC;AAAA,IACF;AAAA,GACF;AACA,EAAA,OAAO,KAAA;AACT,CAAA;AAEA,IAAM,IAAA,GAAO,CAAC,KAAA,EAAoB,KAAA,EAAe,IAAA,KAAkB;AA7HnE,EAAA,IAAA,EAAA;AA8HE,EAAA,CAAA,EAAA,GAAA,KAAA,CAAM,UAAU,GAAA,CAAI,KAAK,MAAzB,IAAA,GAAA,MAAA,GAAA,EAAA,CAA4B,OAAA,CAAQ,CAAC,QAAA,KAAa;AAChD,IAAA,IAAI;AACF,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,2BAAA,EAA8B,KAAK,CAAA,UAAA,CAAA,EAAS,KAAK,CAAA;AAAA,IACjE;AAAA,EACF,CAAA,CAAA;AACF,CAAA;AAEA,IAAI,cAAA,GAAqC,IAAA;AAGlC,IAAM,WAAW,MAAmB;AA1I3C,EAAA,IAAA,EAAA;AA2IE,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAQ,2DAAmB,WAAA,EAAY;AAAA,EACzC;AACA,EAAA,OAAA,CAAQ,EAAA,GAAA,MAAA,CAAO,kBAAA,KAAP,IAAA,GAAA,EAAA,GAAA,MAAA,CAAO,kBAAA,GAAuB,WAAA,EAAY;AACpD,CAAA;AAGO,IAAM,WAAW,MAAY;AAlJpC,EAAA,IAAA,EAAA,EAAA,EAAA;AAmJE,EAAA,OAAA,SAAA,IACA,QAAA,CAAO,EAAA,GAAA,MAAA,CAAO,uBAAA,KAAP,IAAA,GAAA,MAAA,GAAA,EAAA,CAAgC,QAAA,CAAA,KAAa,YACpD,QAAA,CAAO,EAAA,GAAA,MAAA,CAAO,kBAAA,KAAP,IAAA,GAAA,MAAA,GAAA,EAAA,CAA2B,WAAA,CAAA,KAAgB,UAAA;AAAA;AAEpD,IAAM,IAAA,GAAO,CAAC,OAAA,KAA4B;AACxC,EAAA,MAAA,CAAO,kBAAA,CAAoB,WAAA,CAAY,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAChE,CAAA;AAGO,IAAM,IAAA,GAAO,CAAI,MAAA,EAAgB,MAAA,EAAkB,aAAa,kBAAA,KAAmC;AACxG,EAAA,MAAM,QAAQ,QAAA,EAAS;AAEvB,EAAA,IAAI,CAAC,UAAS,EAAG;AACf,IAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,IAAI,YAAA,CAAa,SAAA,CAAU,aAAa,CAAA,EAAG,MAAM,CAAA,oCAAA,CAAA,EAA8B,MAAM,CAAC,CAAA;AAAA,EAC9G;AAEA,EAAA,MAAM,EAAA,GAAK,CAAA,EAAG,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA,EAAI,EAAE,KAAA,CAAM,GAAG,CAAA,CAAA;AACpD,EAAA,OAAO,IAAI,OAAA,CAAW,CAAC,OAAA,EAAS,MAAA,KAAW;AACzC,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,KAAA,CAAM,OAAA,CAAQ,OAAO,EAAE,CAAA;AACvB,MAAA,MAAA,CAAO,IAAI,YAAA,CAAa,SAAA,CAAU,OAAA,EAAS,CAAA,EAAG,MAAM,CAAA,uCAAA,EAA6B,UAAU,CAAA,EAAA,CAAA,EAAM,MAAM,CAAC,CAAA;AAAA,IAC1G,GAAG,UAAU,CAAA;AACb,IAAA,KAAA,CAAM,OAAA,CAAQ,IAAI,EAAA,EAAI,EAAE,QAAQ,OAAA,EAA0C,MAAA,EAAQ,OAAO,CAAA;AACzF,IAAA,IAAA,CAAK,EAAE,QAAA,EAAU,gBAAA,EAAkB,EAAA,EAAI,MAAA,EAAQ,QAAQ,CAAA;AAAA,EACzD,CAAC,CAAA;AACH,CAAA;AAGO,IAAM,MAAA,GAAS,CAAC,MAAA,EAAgB,MAAA,KAA2B;AAChE,EAAA,IAAI,UAAS,EAAG;AACd,IAAA,IAAA,CAAK,EAAE,QAAA,EAAU,gBAAA,EAAkB,MAAA,EAAQ,QAAQ,CAAA;AAAA,EACrD;AACF,CAAA;AAGO,IAAM,EAAA,GAAK,CAAC,KAAA,EAAe,QAAA,KAAqC;AACrE,EAAA,MAAM,EAAE,SAAA,EAAU,GAAI,QAAA,EAAS;AAC/B,EAAA,IAAI,GAAA,GAAM,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAC7B,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,GAAA,uBAAU,GAAA,EAAI;AACd,IAAA,SAAA,CAAU,GAAA,CAAI,OAAO,GAAG,CAAA;AAAA,EAC1B;AACA,EAAA,GAAA,CAAI,IAAI,QAAQ,CAAA;AAChB,EAAA,OAAO,MAAM;AACX,IAAA,GAAA,CAAI,OAAO,QAAQ,CAAA;AAAA,EACrB,CAAA;AACF,CAAA;;;AC9LO,IAAM,WAAA,GAA4D,OAAA;;;ACGlE,IAAM,QAAA,GAAW,MAAqB,IAAA,CAAW,UAAU;AAG3D,IAAM,cAAc,MAAY,MAAA,CAAO,SAAS,EAAE,UAAA,EAAY,aAAa,CAAA;AAM3E,IAAM,cAAc,CAAC,QAAA,KAAuC,GAAG,QAAA,EAAU,MAAM,UAAU;AAMzF,IAAM,eAAe,CAAC,QAAA,KAAuC,GAAG,SAAA,EAAW,MAAM,UAAU;;;ACb3F,IAAM,iBAAiB,OAAoB,EAAE,GAAG,QAAA,GAAW,OAAA,EAAQ;AASnE,IAAM,kBAAkB,CAAC,QAAA,KAC9B,EAAA,CAAG,aAAA,EAAe,CAAC,IAAA,KAAS;AAC1B,EAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAK,GAAI,IAAA;AACvB,EAAA,QAAA,CAAS,MAAM,IAAI,CAAA;AACrB,CAAC;;;ACjBH,IAAM,iBAAA,GAAoB,cAAA;AAE1B,IAAM,mBAAmB,MAAqB;AAC5C,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAI,eAAA,CAAgB,MAAA,CAAO,SAAS,MAAM,CAAA,CAAE,IAAI,iBAAiB,CAAA;AAC1E,CAAA;AAQO,IAAM,iBAAiB,YAA6B;AACzD,EAAA,IAAI,UAAS,EAAG;AACd,IAAA,OAAO,KAAa,gBAAgB,CAAA;AAAA,EACtC;AACA,EAAA,MAAM,QAAQ,gBAAA,EAAiB;AAC/B,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,YAAA,CAAa,SAAA,CAAU,aAAa,CAAA,0BAAA,EAAqB,iBAAiB,iBAAc,gBAAgB,CAAA;AAAA,EACpH;AACA,EAAA,OAAO,KAAA;AACT;AAMA,IAAM,WAAA,GAAyC;AAAA,EAC7C,MAAA,EAAQ,cAAA;AAAA,EACR,OAAA,EAAS,eAAA;AAAA,EACT,OAAA,EAAS;AACX,CAAA;AASO,IAAM,QAAA,GAAW,OAAO,IAAA,KAAqC;AAClE,EAAA,IAAI,EAAE,QAAQ,WAAA,CAAA,EAAc;AAC1B,IAAA,MAAM,IAAI,aAAa,SAAA,CAAU,UAAA,EAAY,8CAA4B,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA,EAAI,UAAU,CAAA;AAAA,EACrG;AACA,EAAA,IAAI,UAAS,EAAG;AACd,IAAA,OAAO,IAAA,CAAa,UAAA,EAAY,EAAE,IAAA,EAAM,CAAA;AAAA,EAC1C;AACA,EAAA,MAAM,KAAA,GAAQ,OAAO,MAAA,KAAW,WAAA,GAAc,OAAO,IAAI,eAAA,CAAgB,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,CAAE,GAAA,CAAI,WAAA,CAAY,IAAI,CAAC,CAAA;AACtH,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,aAAa,SAAA,CAAU,WAAA,EAAa,6BAAqB,WAAA,CAAY,IAAI,CAAC,CAAA,aAAA,CAAA,EAAc,UAAU,CAAA;AAAA,EAC9G;AACA,EAAA,OAAO,KAAA;AACT;;;AC9CA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,EAAA,QAAA,EAAS;AACT,EAAA,IAAI,QAAA,CAAS,eAAe,SAAA,EAAW;AACrC,IAAA,QAAA,CAAS,iBAAiB,kBAAA,EAAoB,WAAA,EAAa,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,EAC3E,CAAA,MAAO;AACL,IAAA,cAAA,CAAe,WAAW,CAAA;AAAA,EAC5B;AACF","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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retion-miniapp/sdk",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Retion Mini App SDK — kết nối web mini app với app Retion",
5
5
  "keywords": [
6
6
  "retion",