@retion-miniapp/sdk 0.4.0 → 0.5.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,19 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.5.0 — 2026-09-30
8
+
9
+ ### Changed
10
+ - `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).
11
+
12
+ ### Fixed
13
+ - 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.
14
+
15
+ ## 0.4.1 — 2026-09-30
16
+
17
+ ### Docs
18
+ - README rút gọn: chỉ còn API reference (từng hàm, field, token, lỗi).
19
+
7
20
  ## 0.4.0 — 2026-09-30
8
21
 
9
22
  ### Added
package/README.md CHANGED
@@ -1,237 +1,197 @@
1
1
  # @retion-miniapp/sdk
2
2
 
3
- Kết nối web mini app (Vue, React, ...) với app Retion.
4
-
5
- Mini app là web của bạn, được app tải về máy và mở full màn hình trong 1 conversation. SDK làm 3 việc:
6
-
7
- 1. Cho biết mini app đang mở ở **page nào, khách nào** → `getRouteParams()`
8
- 2. Đưa **token** để gọi API backend của bạn → `getAccessToken()`
9
- 3. **Đóng** mini app → `closeApp()`
10
-
11
- Cùng 1 source chạy được **cả trong app lẫn trên web** (trình duyệt / iframe cũ) — ngoài app, SDK đọc thông tin từ query URL như hiện tại.
12
-
13
- ---
14
-
15
- ## 1. Cài đặt
3
+ SDK cho web mini app chạy trong app Retion.
16
4
 
17
5
  ```bash
18
6
  npm i @retion-miniapp/sdk
19
- npm i -D @retion-miniapp/cli # build + deploy lên Retion
20
- npx retion-miniapp init --app-id <app_id> --name "<Tên mini app>"
21
7
  ```
22
8
 
23
- `app_id` lấy trên [Retion Mini App Console](https://retion-miniapp-platform.vercel.app/console) (trùng `app_id` của widget, vd `66515701a2be1c03411c5723`).
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 |
24
17
 
25
- ## 2. Cấu hình project
18
+ **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.
26
19
 
27
- Mini app chạy từ file trên máy (không phải từ domain của bạn), nên cần 3 chỉnh nhỏ:
20
+ ---
28
21
 
29
- **a. Đường dẫn asset tương đối** — `vite.config.ts`:
22
+ ## Ví dụ
30
23
 
31
24
  ```ts
32
- export default defineConfig({
33
- base: './',
34
- });
35
- ```
25
+ import { getRouteParams, getToken, onContextChange } from '@retion-miniapp/sdk';
36
26
 
37
- **b. Router dạng hash** (nếu dùng vue-router):
27
+ async function load() {
28
+ const { org_id, page_id } = getRouteParams();
29
+ const token = await getToken('access');
30
+ // gọi API của bạn với org_id, page_id, token
31
+ }
38
32
 
39
- ```ts
40
- createRouter({ history: createWebHashHistory(), routes });
33
+ load(); // lúc mở
34
+ onContextChange(load); // chuyển conversation
41
35
  ```
42
36
 
43
- **c. API gọi bằng URL tuyệt đối** + backend cho phép CORS origin `null`:
37
+ ---
38
+
39
+ ## `getRouteParams()`
40
+
41
+ Lấy param của mini app (như query trên URL): tổ chức, page, khách của conversation đang mở.
44
42
 
45
43
  ```ts
46
- const API_URL = import.meta.env.VITE_API_URL; // https://api.merchant.vn — không dùng "/api"
44
+ const { org_id, page_id, client_id } = getRouteParams();
47
45
  ```
48
46
 
49
- ## 3. Bọc SDK vào 1 file
50
-
51
- Không gọi SDK rải rác trong component. Tạo 1 file làm cầu nối duy nhất — sau này đổi gì chỉ sửa 1 chỗ:
47
+ | Field | Kiểu | Ý nghĩa |
48
+ |---|---|---|
49
+ | `org_id` | `string` | Tổ chức đang dùng app |
50
+ | `page_id` | `string` | Page (kênh) của conversation |
51
+ | `client_id` | `string` | Khách hàng của conversation |
52
+ | `message_id` | `string?` | Tin nhắn mở mini app (nếu mở từ 1 tin cụ thể) |
53
+ | `comment_id` | `string?` | Bình luận mở mini app (nếu mở từ bình luận) |
54
+ | `locale` | `string` | Ngôn ngữ app: `vi`, `en`, `id`, `lo`, `km`, `th`, `tl`, `zh` |
55
+ | `is_page_admin` | `1 \| 0` | `1` nếu người dùng là admin của page |
52
56
 
53
- ```ts
54
- // src/lib/miniapp.ts
55
- import axios from 'axios';
56
- import { getRouteParams, getAccessToken, closeApp, isInHost } from '@retion-miniapp/sdk';
57
+ 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"`).
57
58
 
58
- /** Page + khách của conversation đang mở. */
59
- export const conversation = getRouteParams();
59
+ ---
60
60
 
61
- /** HTTP client gọi backend của bạn — tự gắn token của page hiện tại. */
62
- export const api = axios.create({ baseURL: import.meta.env.VITE_API_URL });
61
+ ## `getToken(type)`
63
62
 
64
- api.interceptors.request.use(async (config) => {
65
- config.headers.Authorization = `Bearer ${await getAccessToken()}`;
66
- return config;
67
- });
63
+ 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).
68
64
 
69
- /** Đóng mini app. Chạy trên web thì quay lại trang trước. */
70
- export const close = () => (isInHost() ? closeApp() : history.back());
65
+ ```ts
66
+ const token = await getToken('access');
71
67
  ```
72
68
 
73
- > Dùng `fetch` thay axios: gọi `await getAccessToken()` rồi gắn vào header y hệt.
74
-
75
- ## 4. Dùng trong component
69
+ | `type` | Token | Cần quyền |
70
+ |---|---|---|
71
+ | `'access'` | Token của mini app cho page hiện tại | Không |
72
+ | `'partner'` | Token đối tác của page | `partner_token` |
73
+ | `'chatbox'` | Token đăng nhập của người dùng app — **toàn quyền** như người dùng | `chatbox_token` |
76
74
 
77
- ```vue
78
- <script setup lang="ts">
79
- import { ref, onMounted } from 'vue';
80
- import { api, conversation, close } from '@/lib/miniapp';
75
+ - 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`.
76
+ - Ngoài app: đọc `?access_token=` / `?partner_token=` / `?chatbox_token=` trên URL; không có → lỗi `NOT_IN_HOST`.
77
+ - `getAccessToken()` = `getToken('access')`.
81
78
 
82
- const orders = ref([]);
79
+ ---
83
80
 
84
- onMounted(async () => {
85
- const { data } = await api.get('/orders', { params: { client_id: conversation.client_id } });
86
- orders.value = data;
87
- });
88
- </script>
89
-
90
- <template>
91
- <header>
92
- <h1>Đơn hàng</h1>
93
- </header>
94
- <OrderList :orders="orders" />
95
- <button @click="close">Xong</button>
96
- </template>
97
- ```
81
+ ## `onContextChange(callback)`
98
82
 
99
- Chừa khoảng **100px bên phải header** — app phủ nút `••• | ✕` ở góc trên phải.
83
+ 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.
100
84
 
101
- ## 5. Chạy thử trên trình duyệt
85
+ ```ts
86
+ onContextChange(() => load());
87
+ ```
102
88
 
103
- Truyền thông tin qua query URL như iframe hiện tại:
89
+ Callback nhận 2 tham số, dùng khi cần so sánh:
104
90
 
105
- ```
106
- http://localhost:5173/?page_id=100179064765476&client_id=8385907281421695&access_token=<token test>
91
+ ```ts
92
+ onContextChange((next, prev) => {
93
+ if (next.page_id !== prev.page_id) {
94
+ // đổi page → xoá dữ liệu riêng của page cũ trước khi load
95
+ }
96
+ load();
97
+ });
107
98
  ```
108
99
 
109
- `getRouteParams()` và `getAccessToken()` đọc từ URL này; `closeApp()` không chạy được ngoài app (hàm `close` ở trên sẽ `history.back()`).
100
+ | Tham số | Kiểu | Ý nghĩa |
101
+ |---|---|---|
102
+ | `next` | `RouteParams` | Conversation mới — giống `getRouteParams()` gọi lúc này |
103
+ | `prev` | `RouteParams` | Conversation trước đó |
110
104
 
111
- ## 6. Deploy
105
+ **Khi nào được gọi / không được gọi**
112
106
 
113
- ```bash
114
- export RETION_MINIAPP_API=https://retion-miniapp-platform.vercel.app/api
115
- export RETION_MINIAPP_TOKEN=<deploy token của app — lấy trên Console>
107
+ | Tình huống | App làm gì | Callback |
108
+ |---|---|---|
109
+ | Mở lại ở conversation khác, cùng tổ chức | Giữ nguyên trang, gửi thông tin mới | ✅ Gọi |
110
+ | Mở lại ở đúng conversation cũ | Giữ nguyên trang | ❌ Không gọi |
111
+ | Mở lại ở **tổ chức khác** | Nạp lại trang | ❌ Không gọi (code chạy lại từ đầu) |
112
+ | Mở lần đầu / trang chưa load xong | Nạp trang | ❌ Không gọi (code chạy lại từ đầu) |
116
113
 
117
- npx retion-miniapp deploy --channel testing # tự tăng version → build → upload
118
- ```
114
+ **Lưu ý**
119
115
 
120
- Lên Console → version vừa deploy → **→ production**. Người dùng nhận bản mới ở lần mở mini app kế tiếp (app tải nền, không cần cập nhật app).
116
+ - Trong callback, `getRouteParams()` và `getToken()` đã trả thông tin + token của conversation mới.
117
+ - 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ũ.
118
+ - Trả về hàm huỷ lắng nghe: `const off = onContextChange(load); off();`
121
119
 
122
120
  ---
123
121
 
124
- ## API
122
+ ## `closeApp()`
125
123
 
126
- | Hàm | Trả về | Trong app | Ngoài app (web / iframe) |
127
- |---|---|---|---|
128
- | `getRouteParams()` | `RouteParams` | Thông tin app truyền vào | Đọc query URL |
129
- | `getAccessToken()` | `Promise<string>` | Token của page hiện tại (app tự refresh) | Đọc `?access_token=` — không có thì lỗi `NOT_IN_HOST` |
130
- | `getToken(type)` | `Promise<string>` | `'access'` \| `'partner'` \| `'chatbox'` — `partner` / `chatbox` cần quyền (xem dưới) | Đọc `?access_token=` / `?partner_token=` / `?chatbox_token=` |
131
- | `onContextChange(cb)` | `() => void` | Gọi `cb` khi chuyển page / conversation (không nạp lại trang) | Không bao giờ gọi |
132
- | `closeApp()` | `Promise<void>` | Đóng mini app | Lỗi `NOT_IN_HOST` |
133
- | `onAppPaused(cb)` / `onAppResumed(cb)` | `() => void` | Mini app bị đóng (chạy ngầm) / mở lại | Không bao giờ gọi |
134
- | `isInHost()` | `boolean` | `true` | `false` |
124
+ Đóng mini app, quay về màn chat. Mini app vẫn chạy ngầm để mở lại tức thì.
135
125
 
136
- `RouteParams`: `page_id`, `client_id`, `org_id`, `message_id`, `comment_id`, `locale`, `is_page_admin`.
126
+ ```ts
127
+ await closeApp();
128
+ ```
137
129
 
138
- ### Lỗi
130
+ Ngoài app → lỗi `NOT_IN_HOST`.
139
131
 
140
- Mọi lỗi là `MiniAppError` có `code`:
132
+ ---
141
133
 
142
- | `code` | Khi nào |
143
- |---|---|
144
- | `NOT_IN_HOST` | Chạy ngoài app mà gọi hàm cần app |
145
- | `PERMISSION_DENIED` | Mini app chưa được cấp quyền lấy token đó |
146
- | `TIMEOUT` | App không phản hồi sau 15s |
147
- | `NOT_SUPPORTED` | Phiên bản app hiện tại chưa hỗ trợ |
148
- | `HOST_ERROR` | Lỗi khác phía app (vd page chưa có token) |
134
+ ## `isInHost()`
149
135
 
150
- ```ts
151
- import { getAccessToken, MiniAppError, ErrorCode } from '@retion-miniapp/sdk';
136
+ `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.
152
137
 
153
- try {
154
- await getAccessToken();
155
- } catch (e) {
156
- if (e instanceof MiniAppError && e.code === ErrorCode.HOST_ERROR) showLoginAgain();
157
- }
138
+ ```ts
139
+ if (isInHost()) await closeApp();
140
+ else history.back();
158
141
  ```
159
142
 
160
- ### Token khác ngoài `access_token`
143
+ ---
161
144
 
162
- | Token | Là gì | Cần quyền |
163
- |---|---|---|
164
- | `access` | Token của mini app cho page hiện tại | Không — luôn được |
165
- | `partner` | Token đối tác của page | `partner_token` |
166
- | `chatbox` | Token đăng nhập của người dùng app (**toàn quyền** như người dùng) | `chatbox_token` |
145
+ ## `onAppPaused(callback)` / `onAppResumed(callback)`
167
146
 
168
- 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 → `getToken()` lỗi `PERMISSION_DENIED`, và token đó cũng **không có trên URL**.
147
+ 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.
169
148
 
170
149
  ```ts
171
- const partnerToken = await getToken('partner');
150
+ onAppPaused(() => clearInterval(timer)); // polling, video, animation...
151
+ onAppResumed(() => load()); // làm mới dữ liệu nếu cần
172
152
  ```
173
153
 
174
- ## Chuyển page / conversation
154
+ Trả về hàm huỷ lắng nghe.
175
155
 
176
- 1 mini app = 1 phiên duy nhất (theo `app_id`), load 1 lần. Khi người dùng mở mini app ở page / conversation khác:
156
+ ---
177
157
 
178
- **Mặc định — không cần làm gì.** App nạp lại trang (cùng WebView, không tạo mới) với thông tin mới; code đọc param lúc mở như bình thường.
158
+ ## Lỗi
179
159
 
180
- **Muốn nhanh hơn — không nạp lại trang:** đặt `"context_update": true` trong `miniapp.config.json` và thêm 1 dòng:
160
+ Mọi lỗi là `MiniAppError`, có `code` (loại lỗi) và `method` (hàm gây lỗi).
181
161
 
182
162
  ```ts
183
- onMounted(load); // đã có
184
- onContextChange(load); // đổi page / conversation → chạy lại load()
163
+ import { getToken, MiniAppError, ErrorCode } from '@retion-miniapp/sdk';
164
+
165
+ try {
166
+ await getToken('chatbox');
167
+ } catch (e) {
168
+ if (e instanceof MiniAppError && e.code === ErrorCode.PERMISSION_DENIED) {
169
+ // chưa được cấp quyền chatbox_token
170
+ }
171
+ }
185
172
  ```
186
173
 
187
- Trong `load()` vẫn gọi `getRouteParams()` / `getToken()` — luôn trả thông tin + token của page mới. Bỏ kết quả của request cũ nếu người dùng đổi page khi nó chưa về:
174
+ | `code` | Khi nào |
175
+ |---|---|
176
+ | `NOT_IN_HOST` | Chạy ngoài app mà gọi hàm cần app |
177
+ | `PERMISSION_DENIED` | Chưa được cấp quyền lấy token đó |
178
+ | `TIMEOUT` | App không phản hồi sau 15s |
179
+ | `NOT_SUPPORTED` | Phiên bản app hiện tại chưa hỗ trợ hàm này |
180
+ | `HOST_ERROR` | Lỗi khác phía app (vd page chưa có token) |
188
181
 
189
- ```ts
190
- let controller: AbortController | undefined;
191
-
192
- const load = async () => {
193
- controller?.abort(); // huỷ request của page trước
194
- controller = new AbortController();
195
- const { page_id } = getRouteParams();
196
- const res = await fetch(`${API}/pages/${page_id}`, {
197
- headers: { Authorization: await getToken('access') },
198
- signal: controller.signal,
199
- });
200
- // ...
201
- };
202
- ```
182
+ ---
203
183
 
204
- > Đổi **tổ chức (org)** luôn nạp lại trang — kể cả khi `context_update: true` — để dữ liệu org cũ không còn trong bộ nhớ.
184
+ ## TypeScript
205
185
 
206
- **Đóng / mở lại:** mini app đóng lại vẫn chạy ngầm để mở lại tức thì. Dừng việc chạy nền khi bị đóng:
186
+ Kèm sẵn type. `RouteParams`, `TokenType`, `MiniAppError`, `ErrorCode` export từ package.
207
187
 
208
188
  ```ts
209
- onAppPaused(() => clearInterval(pollTimer));
210
- onAppResumed(() => load());
189
+ import type { RouteParams, TokenType } from '@retion-miniapp/sdk';
211
190
  ```
212
191
 
213
- ## Hỏi đáp
214
-
215
- **Web đang chạy (iframe / trình duyệt) có bị ảnh hưởng khi cài SDK?**
216
- Không. Ngoài app SDK không gọi mạng, không đụng DOM; `getRouteParams` / `getAccessToken` đọc query URL như trước.
217
-
218
- **Token hết hạn?**
219
- Gọi `getAccessToken()` mỗi lần cần (interceptor ở mục 3 đã làm vậy) — app tự trả token còn hạn. Không lưu token vào localStorage.
220
-
221
- **Có phải tải lại mỗi lần mở?**
222
- Code mini app nằm sẵn trên máy, chỉ tải khi có version mới. Đóng rồi mở lại trong lúc app còn chạy → hiện ngay.
223
-
224
192
  ---
225
193
 
226
- ## Phát triển SDK
227
-
228
- ```bash
229
- npm i
230
- npm run check # typecheck + lint + test + build + publint
231
- npm run release:minor # ghi CHANGELOG mục "## Unreleased" trước
232
- ```
233
-
234
- Giao thức với app: xem chú thích đầu [`src/bridge.ts`](./src/bridge.ts).
194
+ Build + deploy lên app: [`@retion-miniapp/cli`](https://www.npmjs.com/package/@retion-miniapp/cli).
235
195
 
236
196
  ## License
237
197
 
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ var MiniAppError = class _MiniAppError extends Error {
41
41
  var PROTOCOL_VERSION = 1;
42
42
  var DEFAULT_TIMEOUT_MS = 15e3;
43
43
  var CONTEXT_EVENT = "context";
44
+ var CONTEXT_APPLIED = "contextApplied";
44
45
  var hasWindow = typeof window !== "undefined";
45
46
  var readInitialContext = () => {
46
47
  var _a;
@@ -65,11 +66,11 @@ var createState = () => {
65
66
  if (message.event === CONTEXT_EVENT) {
66
67
  const prev = state.context;
67
68
  const next = { ...prev, ...message.data };
68
- if (JSON.stringify(next) === JSON.stringify(prev)) {
69
- return;
69
+ if (JSON.stringify(next) !== JSON.stringify(prev)) {
70
+ state.context = next;
71
+ emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
70
72
  }
71
- state.context = next;
72
- emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
73
+ notify(CONTEXT_APPLIED);
73
74
  return;
74
75
  }
75
76
  emit(state, message.event, message.data);
@@ -149,7 +150,7 @@ var on = (event, listener) => {
149
150
  };
150
151
 
151
152
  // src/version.ts
152
- var SDK_VERSION = "0.4.0" ;
153
+ var SDK_VERSION = "0.5.0" ;
153
154
 
154
155
  // src/apis/app.ts
155
156
  var closeApp = () => call("closeApp");
@@ -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.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/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 → 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ở đượ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"]}
package/dist/index.d.cts CHANGED
@@ -93,6 +93,7 @@ declare class MiniAppError extends Error {
93
93
  * Giao thức v1
94
94
  * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))
95
95
  * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)
96
+ * host → web event "context" → web gọi onContextChange → web → host "contextApplied"
96
97
  * host inject trước khi trang chạy:
97
98
  * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK
98
99
  * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu
package/dist/index.d.ts CHANGED
@@ -93,6 +93,7 @@ declare class MiniAppError extends Error {
93
93
  * Giao thức v1
94
94
  * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))
95
95
  * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)
96
+ * host → web event "context" → web gọi onContextChange → web → host "contextApplied"
96
97
  * host inject trước khi trang chạy:
97
98
  * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK
98
99
  * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ var MiniAppError = class _MiniAppError extends Error {
39
39
  var PROTOCOL_VERSION = 1;
40
40
  var DEFAULT_TIMEOUT_MS = 15e3;
41
41
  var CONTEXT_EVENT = "context";
42
+ var CONTEXT_APPLIED = "contextApplied";
42
43
  var hasWindow = typeof window !== "undefined";
43
44
  var readInitialContext = () => {
44
45
  var _a;
@@ -63,11 +64,11 @@ var createState = () => {
63
64
  if (message.event === CONTEXT_EVENT) {
64
65
  const prev = state.context;
65
66
  const next = { ...prev, ...message.data };
66
- if (JSON.stringify(next) === JSON.stringify(prev)) {
67
- return;
67
+ if (JSON.stringify(next) !== JSON.stringify(prev)) {
68
+ state.context = next;
69
+ emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
68
70
  }
69
- state.context = next;
70
- emit(state, CONTEXT_EVENT, { next: { ...next }, prev });
71
+ notify(CONTEXT_APPLIED);
71
72
  return;
72
73
  }
73
74
  emit(state, message.event, message.data);
@@ -147,7 +148,7 @@ var on = (event, listener) => {
147
148
  };
148
149
 
149
150
  // src/version.ts
150
- var SDK_VERSION = "0.4.0" ;
151
+ var SDK_VERSION = "0.5.0" ;
151
152
 
152
153
  // src/apis/app.ts
153
154
  var closeApp = () => call("closeApp");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */\n NOT_SUPPORTED: 'NOT_SUPPORTED',\n /** Lỗi khác phía host. */\n HOST_ERROR: 'HOST_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] | (string & {});\n\nexport class MiniAppError extends Error {\n readonly code: ErrorCode;\n readonly method?: string;\n\n constructor(code: ErrorCode, message: string, method?: string) {\n super(message);\n this.name = 'MiniAppError';\n this.code = code;\n this.method = method;\n }\n\n /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */\n static from(error: unknown, method?: string): MiniAppError {\n if (error instanceof MiniAppError) {\n return error;\n }\n if (error && typeof error === 'object') {\n const { code, message } = error as { code?: unknown; message?: unknown };\n return new MiniAppError(\n typeof code === 'string' ? code : ErrorCode.HOST_ERROR,\n typeof message === 'string' ? message : 'Unknown error',\n method,\n );\n }\n return new MiniAppError(ErrorCode.HOST_ERROR, String(error), method);\n }\n}\n","/**\n * Bridge web mini app ↔ host (app Retion). Nội bộ SDK — dev mini app không import trực tiếp.\n *\n * Giao thức v1\n * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))\n * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)\n * host inject trước khi trang chạy:\n * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK\n * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu\n *\n * Chỉ có ReactNativeWebView KHÔNG đủ để coi là host: màn iframe cũ của app cũng là RN WebView\n * nhưng không hiểu giao thức này (gọi API sẽ treo tới timeout). Thiếu marker → NOT_IN_HOST ngay.\n *\n * State đặt trên window (singleton): nhiều bản SDK trong cùng trang (trùng version trong\n * node_modules) vẫn dùng chung 1 bridge thay vì ghi đè hàm receive của nhau.\n */\nimport { ErrorCode, MiniAppError } from './errors';\nimport type { RouteParams } from './types';\n\nexport const PROTOCOL_VERSION = 1;\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const CONTEXT_EVENT = 'context';\n\nexport interface RequestMessage {\n __retion: typeof PROTOCOL_VERSION;\n /** Có id → host phải trả ResponseMessage. Không có → lệnh 1 chiều. */\n id?: string;\n method: string;\n params?: unknown;\n}\n\nexport type ResponseMessage =\n | { id: string; result?: unknown; error?: undefined }\n | { id: string; error: { code: string; message: string } };\n\nexport interface EventMessage {\n event: string;\n data?: unknown;\n}\n\nexport interface HostInfo {\n /** Phiên bản giao thức host hỗ trợ. */\n protocol: number;\n}\n\ntype Listener = (data: unknown) => void;\n\ninterface Pending {\n method: string;\n resolve: (value: unknown) => void;\n reject: (error: MiniAppError) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface BridgeState {\n protocol: number;\n seq: number;\n pending: Map<string, Pending>;\n listeners: Map<string, Set<Listener>>;\n context: RouteParams;\n receive: (message: ResponseMessage | EventMessage) => void;\n}\n\ndeclare global {\n interface Window {\n ReactNativeWebView?: { postMessage: (message: string) => void };\n __RETION_MINIAPP__?: BridgeState;\n __RETION_MINIAPP_CONTEXT__?: RouteParams;\n __RETION_MINIAPP_HOST__?: HostInfo;\n }\n}\n\nconst hasWindow = typeof window !== 'undefined';\n\nconst readInitialContext = (): RouteParams => {\n if (!hasWindow) {\n return {};\n }\n const query = Object.fromEntries(new URLSearchParams(window.location.search));\n return { ...query, ...(window.__RETION_MINIAPP_CONTEXT__ ?? {}) };\n};\n\nconst createState = (): BridgeState => {\n const state: BridgeState = {\n protocol: PROTOCOL_VERSION,\n seq: 0,\n pending: new Map(),\n listeners: new Map(),\n context: readInitialContext(),\n receive: (message) => {\n if (!message || typeof message !== 'object') {\n return;\n }\n if ('event' in message) {\n if (message.event === CONTEXT_EVENT) {\n // Cập nhật context TRƯỚC khi emit → getRouteParams() luôn đúng, kể cả không ai lắng nghe.\n const prev = state.context;\n const next = { ...prev, ...(message.data as RouteParams) };\n // Host đẩy lại context lúc \"ready\" dù không đổi → không báo onContextChange thừa.\n if (JSON.stringify(next) === JSON.stringify(prev)) {\n return;\n }\n state.context = next;\n emit(state, CONTEXT_EVENT, { next: { ...next }, prev });\n return;\n }\n emit(state, message.event, message.data);\n return;\n }\n const pending = state.pending.get(message.id);\n if (!pending) {\n return;\n }\n state.pending.delete(message.id);\n clearTimeout(pending.timer);\n if (message.error) {\n pending.reject(MiniAppError.from(message.error, pending.method));\n } else {\n pending.resolve(message.result);\n }\n },\n };\n return state;\n};\n\nconst emit = (state: BridgeState, event: string, data: unknown) => {\n state.listeners.get(event)?.forEach((listener) => {\n try {\n listener(data);\n } catch (error) {\n console.error(`[retion-miniapp] listener \"${event}\" lỗi`, error);\n }\n });\n};\n\nlet fallback_state: BridgeState | null = null;\n\n/** @internal */\nexport const getState = (): BridgeState => {\n if (!hasWindow) {\n return (fallback_state ??= createState());\n }\n return (window.__RETION_MINIAPP__ ??= createState());\n};\n\n/** Đang chạy trong app Retion có hỗ trợ SDK (host đã inject marker + kênh postMessage). */\nexport const isInHost = (): boolean =>\n hasWindow &&\n typeof window.__RETION_MINIAPP_HOST__?.protocol === 'number' &&\n typeof window.ReactNativeWebView?.postMessage === 'function';\n\nconst post = (message: RequestMessage) => {\n window.ReactNativeWebView!.postMessage(JSON.stringify(message));\n};\n\n/** Gửi lệnh lên host và chờ kết quả. Ngoài app → reject NOT_IN_HOST. */\nexport const call = <T>(method: string, params?: unknown, timeout_ms = DEFAULT_TIMEOUT_MS): Promise<T> => {\n const state = getState();\n\n if (!isInHost()) {\n return Promise.reject(new MiniAppError(ErrorCode.NOT_IN_HOST, `${method} chỉ chạy trong app Retion`, method));\n }\n\n const id = `${Date.now().toString(36)}-${++state.seq}`;\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n state.pending.delete(id);\n reject(new MiniAppError(ErrorCode.TIMEOUT, `${method}: host không phản hồi sau ${timeout_ms}ms`, method));\n }, timeout_ms);\n state.pending.set(id, { method, resolve: resolve as (v: unknown) => void, reject, timer });\n post({ __retion: PROTOCOL_VERSION, id, method, params });\n });\n};\n\n/** Lệnh 1 chiều (không chờ kết quả). Ngoài app: bỏ qua. */\nexport const notify = (method: string, params?: unknown): void => {\n if (isInHost()) {\n post({ __retion: PROTOCOL_VERSION, method, params });\n }\n};\n\n/** Lắng nghe event host đẩy xuống. Trả về hàm huỷ. */\nexport const on = (event: string, listener: Listener): (() => void) => {\n const { listeners } = getState();\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n set.add(listener);\n return () => {\n set.delete(listener);\n };\n};\n","declare const __SDK_VERSION__: string;\n\n/** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */\nexport const SDK_VERSION: string = typeof __SDK_VERSION__ === 'string' ? __SDK_VERSION__ : '0.0.0-dev';\n","import { call, notify, on } from '../bridge';\nimport { SDK_VERSION } from '../version';\n\n/**\n * Đóng mini app, quay về màn chat. Ngoài app → reject NOT_IN_HOST (web tự xử lý, vd router.back()).\n */\nexport const closeApp = (): Promise<void> => call<void>('closeApp');\n\n/** @internal SDK tự gọi khi trang sẵn sàng — host ẩn splash + đẩy context mới nhất. */\nexport const notifyReady = (): void => notify('ready', { sdkVersion: SDK_VERSION });\n\n/**\n * Mini app bị đóng (ẩn, vẫn chạy ngầm) — dừng polling / timer / video ở đây cho đỡ tốn pin.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppPaused = (listener: () => void): (() => void) => on('paused', () => listener());\n\n/**\n * Mini app được mở lại (trang vẫn giữ nguyên) — làm mới dữ liệu nếu cần.\n * @returns hàm huỷ lắng nghe\n */\nexport const onAppResumed = (listener: () => void): (() => void) => on('resumed', () => listener());\n","import { CONTEXT_EVENT, getState, on } from '../bridge';\nimport type { RouteParams } from '../types';\n\n/**\n * Thông tin conversation lúc mở mini app (đồng bộ, gọi bất cứ lúc nào).\n * @example\n * const { page_id, client_id } = getRouteParams();\n */\nexport const getRouteParams = (): RouteParams => ({ ...getState().context });\n\n/**\n * Mini app đang mở được chuyển sang page / conversation khác (không nạp lại trang).\n * Gọi lại API theo thông tin mới ở đây. Chỉ xảy ra khi `miniapp.config.json` có `context_update: true`.\n * @returns hàm huỷ lắng nghe\n * @example\n * onContextChange(({ page_id, client_id }) => loadData(page_id, client_id));\n */\nexport const onContextChange = (listener: (next: RouteParams, prev: RouteParams) => void): (() => void) =>\n on(CONTEXT_EVENT, (data) => {\n const { next, prev } = data as { next: RouteParams; prev: RouteParams };\n listener(next, prev);\n });\n","import { call, isInHost } from '../bridge';\nimport { ErrorCode, MiniAppError } from '../errors';\n\n/** Tên query param chứa token khi chạy ngoài app (giống URL iframe hiện tại). */\nconst TOKEN_QUERY_PARAM = 'access_token';\n\nconst readTokenFromUrl = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n return new URLSearchParams(window.location.search).get(TOKEN_QUERY_PARAM);\n};\n\n/**\n * Token để mini app gọi API backend của chính mình (Authorization: Bearer ...).\n * - Trong app: lấy từ host, luôn là token của page hiện tại, host tự refresh.\n * - Ngoài app: đọc `?access_token=` trên URL (như iframe hiện tại).\n * Gọi lại mỗi khi cần — không lưu token lâu phía web.\n */\nexport const getAccessToken = async (): Promise<string> => {\n if (isInHost()) {\n return call<string>('getAccessToken');\n }\n const token = readTokenFromUrl();\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${TOKEN_QUERY_PARAM}= trên URL`, 'getAccessToken');\n }\n return token;\n};\n\n/** Loại token: `access` luôn được phép; `partner` / `chatbox` cần admin bật quyền trên Console. */\nexport type TokenType = 'access' | 'partner' | 'chatbox';\n\n/** Tên query param tương ứng khi chạy ngoài app (giống URL iframe hiện tại). */\nconst QUERY_PARAM: Record<TokenType, string> = {\n access: 'access_token',\n partner: 'partner_token',\n chatbox: 'chatbox_token',\n};\n\n/**\n * Lấy token theo loại.\n * - `access`: token của mini app cho page hiện tại (= getAccessToken()).\n * - `partner`: token đối tác của page — cần quyền `partner_token`.\n * - `chatbox`: token đăng nhập của người dùng app (toàn quyền) — cần quyền `chatbox_token`.\n * Trong app, chưa được cấp quyền → MiniAppError PERMISSION_DENIED. Ngoài app: đọc query URL.\n */\nexport const getToken = async (type: TokenType): Promise<string> => {\n if (!(type in QUERY_PARAM)) {\n throw new MiniAppError(ErrorCode.HOST_ERROR, `Loại token không hợp lệ: ${String(type)}`, 'getToken');\n }\n if (isInHost()) {\n return call<string>('getToken', { type });\n }\n const token = typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get(QUERY_PARAM[type]);\n if (!token) {\n throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngoài app: thiếu ?${QUERY_PARAM[type]}= trên URL`, 'getToken');\n }\n return token;\n};\n","import { getState } from './bridge';\nimport { notifyReady } from './apis/app';\n\nexport { getRouteParams, onContextChange } from './apis/route';\nexport { getAccessToken, getToken, type TokenType } from './apis/user';\nexport { closeApp, onAppPaused, onAppResumed } from './apis/app';\nexport { isInHost } from './bridge';\nexport { MiniAppError, ErrorCode } from './errors';\nexport { SDK_VERSION } from './version';\nexport type { RouteParams } from './types';\n\n// Khởi tạo bridge ngay khi import (host có thể đẩy context trước khi app gọi hàm nào),\n// rồi báo \"ready\" khi DOM sẵn sàng.\nif (typeof window !== 'undefined') {\n getState();\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', notifyReady, { once: true });\n } else {\n queueMicrotask(notifyReady);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */\n NOT_SUPPORTED: 'NOT_SUPPORTED',\n /** Lỗi khác phía host. */\n HOST_ERROR: 'HOST_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] | (string & {});\n\nexport class MiniAppError extends Error {\n readonly code: ErrorCode;\n readonly method?: string;\n\n constructor(code: ErrorCode, message: string, method?: string) {\n super(message);\n this.name = 'MiniAppError';\n this.code = code;\n this.method = method;\n }\n\n /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */\n static from(error: unknown, method?: string): MiniAppError {\n if (error instanceof MiniAppError) {\n return error;\n }\n if (error && typeof error === 'object') {\n const { code, message } = error as { code?: unknown; message?: unknown };\n return new MiniAppError(\n typeof code === 'string' ? code : ErrorCode.HOST_ERROR,\n typeof message === 'string' ? message : 'Unknown error',\n method,\n );\n }\n return new MiniAppError(ErrorCode.HOST_ERROR, String(error), method);\n }\n}\n","/**\n * Bridge web mini app ↔ host (app Retion). Nội bộ SDK — dev mini app không import trực tiếp.\n *\n * Giao thức v1\n * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))\n * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)\n * host → 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ở đượ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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retion-miniapp/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",