@retion-miniapp/sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,16 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.2.0 — 2026-09-30
8
+
9
+ ### Breaking
10
+ - Thu gọn API về đúng nhu cầu mini app: `getRouteParams`, `getAccessToken`, `closeApp`, `isInHost`.
11
+ - Bỏ `onContextChange`, `getPageInfo`, `getUserInfo`, `sendMessage`, `insertText`, `openWebview`, `showToast`, `getSystemInfo`, `setMockHandler`, callback `{ success, fail }` và entry `@retion-miniapp/sdk/vue`.
12
+
13
+ ### Changed
14
+ - `getAccessToken()` ngoài app đọc `?access_token=` trên URL (thay cho mock handler) — web cũ / iframe dùng được ngay.
15
+
16
+ ## 0.1.0 — 2026-09-30
17
+
7
18
  ### Added
8
- - Bridge giao thức v1 với host React Native (request/response theo id, timeout, event).
9
- - API: `getRouteParams`, `onContextChange`, `getPageInfo`, `getAccessToken`, `getUserInfo`, `sendMessage`, `insertText`, `closeApp`, `openWebview`, `showToast`, `getSystemInfo`, `isInHost`, `setMockHandler`.
10
- - `MiniAppError` + `ErrorCode`.
11
- - Vue composable `useRouteParams` (`@retion-miniapp/sdk/vue`).
19
+ - Bản đầu: bridge giao thức v1, các API cơ bản, `MiniAppError` + `ErrorCode`.
package/README.md CHANGED
@@ -1,134 +1,183 @@
1
1
  # @retion-miniapp/sdk
2
2
 
3
- SDK cho web mini app chạy trong app Retion. Bridge với app được ẩn hoàn toàn — chỉ cần import hàm.
3
+ Kết nối web mini app (Vue, React, ...) với app Retion.
4
4
 
5
- - Chạy được ở **cả web lẫn app**: ngoài app, SDK không lỗi, `getRouteParams()` đọc query URL, API cần app reject `NOT_IN_HOST`.
6
- - Mọi API trả `Promise`, hoặc dùng callback `{ success, fail }`.
7
- - TypeScript, ESM + CJS, tree-shake theo từng hàm, có composable Vue.
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 chỉ làm 3 việc:
8
6
 
9
- ## Cài đặt
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
10
16
 
11
17
  ```bash
12
18
  npm i @retion-miniapp/sdk
13
- npm i -D @retion-miniapp/cli # build + deploy
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>"
14
21
  ```
15
22
 
16
- ## Dùng nhanh
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`).
17
24
 
18
- ```js
19
- import { getRouteParams, getAccessToken, insertText, closeApp, isInHost } from '@retion-miniapp/sdk';
25
+ ## 2. Cấu hình project
20
26
 
21
- const { page_id, client_id } = getRouteParams();
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ỏ:
22
28
 
23
- const token = await getAccessToken();
24
- const order = await fetch(`https://api.your-app.com/orders?client=${client_id}`, {
25
- headers: { Authorization: `Bearer ${token}` },
26
- }).then((r) => r.json());
29
+ **a. Đường dẫn asset tương đối** — `vite.config.ts`:
27
30
 
28
- if (isInHost()) {
29
- await insertText({ text: `Đơn #${order.id}: ${order.total}đ` });
30
- await closeApp();
31
- } else {
32
- router.back(); // chạy trên web: giữ hành vi cũ
33
- }
31
+ ```ts
32
+ export default defineConfig({
33
+ base: './',
34
+ });
34
35
  ```
35
36
 
36
- ## Mini app theo page
37
+ **b. Router dạng hash** (nếu dùng vue-router):
37
38
 
38
- Mini app được cài theo **page** (kênh). Mỗi page có token, quyền, dữ liệu riêng — app chạy **1 instance mini app cho mỗi page**:
39
+ ```ts
40
+ createRouter({ history: createWebHashHistory(), routes });
41
+ ```
39
42
 
40
- - Đổi conversation **trong cùng page** → `onContextChange` (nếu `context_update: true`), hoặc reload với query mới.
41
- - Đổi **page** → instance khác, mini app khởi động lại với `page_id` mới. Không cần tự xử lý.
42
- - `getAccessToken()` luôn trả token của page hiện tại — gọi lại mỗi khi cần, không lưu lâu.
43
+ **c. API gọi bằng URL tuyệt đối** + backend cho phép CORS origin `null`:
43
44
 
44
- ## API
45
+ ```ts
46
+ const API_URL = import.meta.env.VITE_API_URL; // https://api.merchant.vn — không dùng "/api"
47
+ ```
45
48
 
46
- | API | Trả về | Mô tả |
47
- |---|---|---|
48
- | `getRouteParams()` | `RouteParams` | `page_id, client_id, org_id, message_id, locale, is_page_admin, ...` (đồng bộ) |
49
- | `onContextChange(cb)` | `() => void` | Đổi conversation trong cùng page mà không reload |
50
- | `getPageInfo()` | `PageInfo` | `{ page_id, name, avatar, platform }` |
51
- | `getAccessToken()` | `string` | Token của page hiện tại |
52
- | `getUserInfo()` | `UserInfo` | Nhân viên đang đăng nhập |
53
- | `sendMessage({ text })` | `void` | Gửi tin vào conversation |
54
- | `insertText({ text })` | `void` | Chèn text vào ô nhập chat |
55
- | `closeApp()` | `void` | Đóng mini app |
56
- | `openWebview({ url })` | `void` | Mở link http(s) |
57
- | `showToast({ message })` | `void` | Toast native |
58
- | `getSystemInfo()` | `SystemInfo` | `platform, appVersion, sdkVersion, inHost, theme` — ngoài app không lỗi |
59
- | `isInHost()` | `boolean` | Đang chạy trong app Retion có hỗ trợ SDK |
49
+ ## 3. Bọc SDK vào 1 file
60
50
 
61
- ### Lỗi
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ỗ:
62
52
 
63
- Mọi lỗi là `MiniAppError` với `code`:
53
+ ```ts
54
+ // src/lib/miniapp.ts
55
+ import axios from 'axios';
56
+ import { getRouteParams, getAccessToken, closeApp, isInHost } from '@retion-miniapp/sdk';
64
57
 
65
- | code | Khi nào |
66
- |---|---|
67
- | `NOT_IN_HOST` | Chạy ngoài app (web / màn iframe cũ của app) và không có mock |
68
- | `TIMEOUT` | Host không phản hồi (15s) |
69
- | `NOT_SUPPORTED` | App version hiện tại chưa hỗ trợ API |
70
- | `INVALID_PARAMS` | Tham số sai (url không phải http(s), text rỗng, ...) |
71
- | `USER_CANCELLED` | User huỷ thao tác |
72
- | `HOST_ERROR` | Lỗi khác phía app |
58
+ /** Page + khách của conversation đang mở. */
59
+ export const conversation = getRouteParams();
73
60
 
74
- ```js
75
- import { closeApp, MiniAppError, ErrorCode } from '@retion-miniapp/sdk';
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 });
76
63
 
77
- try {
78
- await closeApp();
79
- } catch (e) {
80
- if (e instanceof MiniAppError && e.code === ErrorCode.NOT_IN_HOST) { /* đang chạy trên web */ }
81
- }
64
+ api.interceptors.request.use(async (config) => {
65
+ config.headers.Authorization = `Bearer ${await getAccessToken()}`;
66
+ return config;
67
+ });
68
+
69
+ /** Đóng mini app. Chạy trên web thì quay lại trang trước. */
70
+ export const close = () => (isInHost() ? closeApp() : history.back());
82
71
  ```
83
72
 
84
- ## Vue
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
76
+
77
+ ```vue
78
+ <script setup lang="ts">
79
+ import { ref, onMounted } from 'vue';
80
+ import { api, conversation, close } from '@/lib/miniapp';
81
+
82
+ const orders = ref([]);
85
83
 
86
- ```js
87
- import { useRouteParams } from '@retion-miniapp/sdk/vue';
84
+ onMounted(async () => {
85
+ const { data } = await api.get('/orders', { params: { client_id: conversation.client_id } });
86
+ orders.value = data;
87
+ });
88
+ </script>
88
89
 
89
- const params = useRouteParams(); // ShallowRef<RouteParams>, tự cập nhật khi đổi conversation
90
- watch(() => params.value.client_id, fetchData, { immediate: true });
90
+ <template>
91
+ <header>
92
+ <h1>Đơn hàng</h1>
93
+ </header>
94
+ <OrderList :orders="orders" />
95
+ <button @click="close">Xong</button>
96
+ </template>
91
97
  ```
92
98
 
93
- ## Dev trên trình duyệt
99
+ Chừa khoảng **100px bên phải header** — app phủ nút `••• | ✕` ở góc trên phải.
94
100
 
95
- ```js
96
- import { setMockHandler } from '@retion-miniapp/sdk';
101
+ ## 5. Chạy thử trên trình duyệt
97
102
 
98
- if (import.meta.env.DEV) {
99
- setMockHandler((method, params) => {
100
- if (method === 'getAccessToken') return 'dev-token';
101
- if (method === 'getPageInfo') return { page_id: 'p1', name: 'Page test', platform: 'facebook' };
102
- });
103
- }
103
+ Truyền thông tin qua query URL như iframe hiện tại:
104
+
105
+ ```
106
+ http://localhost:5173/?page_id=100179064765476&client_id=8385907281421695&access_token=<token test>
104
107
  ```
105
108
 
106
- ## Build cho app
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()`).
107
110
 
108
- `vite.config`: `base: './'` (chạy offline từ file local). Sau đó dùng [`@retion-miniapp/cli`](https://www.npmjs.com/package/@retion-miniapp/cli):
111
+ ## 6. Deploy
109
112
 
110
113
  ```bash
111
- npx retion-miniapp init
112
- npx retion-miniapp deploy
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>
116
+
117
+ npx retion-miniapp deploy --channel testing # tự tăng version → build → upload
118
+ ```
119
+
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).
121
+
122
+ ---
123
+
124
+ ## API
125
+
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
+ | `closeApp()` | `Promise<void>` | Đóng mini app | Lỗi `NOT_IN_HOST` |
131
+ | `isInHost()` | `boolean` | `true` | `false` |
132
+
133
+ `RouteParams`: `page_id`, `client_id`, `org_id`, `message_id`, `comment_id`, `locale`, `is_page_admin`.
134
+
135
+ ### Lỗi
136
+
137
+ Mọi lỗi là `MiniAppError` có `code`:
138
+
139
+ | `code` | Khi nào |
140
+ |---|---|
141
+ | `NOT_IN_HOST` | Chạy ngoài app mà gọi hàm cần app |
142
+ | `TIMEOUT` | App không phản hồi sau 15s |
143
+ | `NOT_SUPPORTED` | Phiên bản app hiện tại chưa hỗ trợ |
144
+ | `HOST_ERROR` | Lỗi khác phía app (vd page chưa có token) |
145
+
146
+ ```ts
147
+ import { getAccessToken, MiniAppError, ErrorCode } from '@retion-miniapp/sdk';
148
+
149
+ try {
150
+ await getAccessToken();
151
+ } catch (e) {
152
+ if (e instanceof MiniAppError && e.code === ErrorCode.HOST_ERROR) showLoginAgain();
153
+ }
113
154
  ```
114
155
 
115
- ## Giao thức (cho team app)
156
+ ## Hỏi đáp
116
157
 
117
- Xem chú thích đầu [`src/bridge.ts`](./src/bridge.ts). Host phải inject `window.__RETION_MINIAPP_HOST__ = { protocol: 1 }` trước khi trang chạy — thiếu marker, SDK coi như chạy ngoài app.
158
+ **Web đang chạy (iframe / trình duyệt) có bị ảnh hưởng khi cài SDK?**
159
+ Không. Ngoài app SDK không gọi mạng, không đụng DOM; `getRouteParams` / `getAccessToken` đọc query URL như trước.
118
160
 
119
- ## Phát triển
161
+ **Token hết hạn?**
162
+ 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.
163
+
164
+ **Mở mini app ở page khác?**
165
+ Mỗi page là 1 phiên mini app riêng (token, dữ liệu riêng). Code không cần xử lý gì.
166
+
167
+ **Có phải tải lại mỗi lần mở?**
168
+ 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.
169
+
170
+ ---
171
+
172
+ ## Phát triển SDK
120
173
 
121
174
  ```bash
122
175
  npm i
123
- npm run check # typecheck + lint + test + build + publint
124
- npm run release:patch # (hoặc minor/major) — xem mục Release
176
+ npm run check # typecheck + lint + test + build + publint
177
+ npm run release:minor # ghi CHANGELOG mục "## Unreleased" trước
125
178
  ```
126
179
 
127
- ### Release
128
-
129
- 1. Ghi thay đổi vào `CHANGELOG.md` dưới `## Unreleased`.
130
- 2. `npm run release:patch` → chạy `check` → tăng version → cập nhật CHANGELOG → commit + tag `vX.Y.Z` → push.
131
- 3. GitHub Actions (`release.yml`) thấy tag → publish lên npm (cần secret `NPM_TOKEN`).
180
+ Giao thức với app: xem chú thích đầu [`src/bridge.ts`](./src/bridge.ts).
132
181
 
133
182
  ## License
134
183
 
package/dist/index.cjs CHANGED
@@ -1,91 +1,167 @@
1
1
  'use strict';
2
2
 
3
- var chunkC2AZ33LI_cjs = require('./chunk-C2AZ33LI.cjs');
3
+ // src/errors.ts
4
+ var ErrorCode = {
5
+ /** Không chạy trong app Retion (trình duyệt / iframe cũ). */
6
+ NOT_IN_HOST: "NOT_IN_HOST",
7
+ /** Host không phản hồi trong thời gian chờ. */
8
+ TIMEOUT: "TIMEOUT",
9
+ /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
10
+ NOT_SUPPORTED: "NOT_SUPPORTED",
11
+ /** Lỗi khác phía host. */
12
+ HOST_ERROR: "HOST_ERROR"
13
+ };
14
+ var MiniAppError = class _MiniAppError extends Error {
15
+ constructor(code, message, method) {
16
+ super(message);
17
+ this.name = "MiniAppError";
18
+ this.code = code;
19
+ this.method = method;
20
+ }
21
+ /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */
22
+ static from(error, method) {
23
+ if (error instanceof _MiniAppError) {
24
+ return error;
25
+ }
26
+ if (error && typeof error === "object") {
27
+ const { code, message } = error;
28
+ return new _MiniAppError(
29
+ typeof code === "string" ? code : ErrorCode.HOST_ERROR,
30
+ typeof message === "string" ? message : "Unknown error",
31
+ method
32
+ );
33
+ }
34
+ return new _MiniAppError(ErrorCode.HOST_ERROR, String(error), method);
35
+ }
36
+ };
4
37
 
5
- // src/invoke.ts
6
- var invoke = (method, params, options) => {
7
- const promise = chunkC2AZ33LI_cjs.call(method, params);
8
- if ((options == null ? void 0 : options.success) || (options == null ? void 0 : options.fail)) {
9
- promise.then(options.success, (error) => {
10
- var _a;
11
- return (_a = options.fail) == null ? void 0 : _a.call(options, chunkC2AZ33LI_cjs.MiniAppError.from(error, method));
12
- });
38
+ // src/bridge.ts
39
+ var PROTOCOL_VERSION = 1;
40
+ var DEFAULT_TIMEOUT_MS = 15e3;
41
+ var CONTEXT_EVENT = "context";
42
+ var hasWindow = typeof window !== "undefined";
43
+ var readInitialContext = () => {
44
+ var _a;
45
+ if (!hasWindow) {
46
+ return {};
13
47
  }
14
- return promise;
48
+ const query = Object.fromEntries(new URLSearchParams(window.location.search));
49
+ return { ...query, ...(_a = window.__RETION_MINIAPP_CONTEXT__) != null ? _a : {} };
15
50
  };
16
- var splitOptions = (options) => {
17
- const { success, fail, ...params } = options;
18
- return [params, { success, fail }];
51
+ var createState = () => {
52
+ const state = {
53
+ protocol: PROTOCOL_VERSION,
54
+ seq: 0,
55
+ pending: /* @__PURE__ */ new Map(),
56
+ listeners: /* @__PURE__ */ new Map(),
57
+ context: readInitialContext(),
58
+ receive: (message) => {
59
+ if (!message || typeof message !== "object") {
60
+ return;
61
+ }
62
+ if ("event" in message) {
63
+ if (message.event === CONTEXT_EVENT) {
64
+ const prev = state.context;
65
+ state.context = { ...prev, ...message.data };
66
+ emit(state, CONTEXT_EVENT, { next: { ...state.context }, prev });
67
+ return;
68
+ }
69
+ emit(state, message.event, message.data);
70
+ return;
71
+ }
72
+ const pending = state.pending.get(message.id);
73
+ if (!pending) {
74
+ return;
75
+ }
76
+ state.pending.delete(message.id);
77
+ clearTimeout(pending.timer);
78
+ if (message.error) {
79
+ pending.reject(MiniAppError.from(message.error, pending.method));
80
+ } else {
81
+ pending.resolve(message.result);
82
+ }
83
+ }
84
+ };
85
+ return state;
19
86
  };
20
-
21
- // src/version.ts
22
- var SDK_VERSION = "0.1.0" ;
23
-
24
- // src/apis/app.ts
25
- var closeApp = (options) => invoke("closeApp", void 0, options);
26
- var openWebview = (options) => {
27
- var _a, _b;
28
- const [params, callbacks] = splitOptions(options);
29
- if (!/^https?:\/\//i.test((_a = params.url) != null ? _a : "")) {
30
- const error = new chunkC2AZ33LI_cjs.MiniAppError(chunkC2AZ33LI_cjs.ErrorCode.INVALID_PARAMS, "url ph\u1EA3i b\u1EAFt \u0111\u1EA7u b\u1EB1ng http:// ho\u1EB7c https://", "openWebview");
31
- (_b = callbacks.fail) == null ? void 0 : _b.call(callbacks, error);
32
- return Promise.reject(error);
87
+ var emit = (state, event, data) => {
88
+ var _a;
89
+ (_a = state.listeners.get(event)) == null ? void 0 : _a.forEach((listener) => {
90
+ try {
91
+ listener(data);
92
+ } catch (error) {
93
+ console.error(`[retion-miniapp] listener "${event}" l\u1ED7i`, error);
94
+ }
95
+ });
96
+ };
97
+ var fallback_state = null;
98
+ var getState = () => {
99
+ var _a;
100
+ if (!hasWindow) {
101
+ return fallback_state != null ? fallback_state : fallback_state = createState();
33
102
  }
34
- return invoke("openWebview", params, callbacks);
103
+ return (_a = window.__RETION_MINIAPP__) != null ? _a : window.__RETION_MINIAPP__ = createState();
35
104
  };
36
- var showToast = (options) => {
37
- const [params, callbacks] = splitOptions(options);
38
- return invoke("showToast", params, callbacks);
105
+ var isInHost = () => {
106
+ var _a, _b;
107
+ return hasWindow && typeof ((_a = window.__RETION_MINIAPP_HOST__) == null ? void 0 : _a.protocol) === "number" && typeof ((_b = window.ReactNativeWebView) == null ? void 0 : _b.postMessage) === "function";
108
+ };
109
+ var post = (message) => {
110
+ window.ReactNativeWebView.postMessage(JSON.stringify(message));
39
111
  };
40
- var getSystemInfo = async (options) => {
41
- var _a, _b, _c;
42
- const base = { platform: "web", sdkVersion: SDK_VERSION, inHost: chunkC2AZ33LI_cjs.isInHost() };
43
- if (!chunkC2AZ33LI_cjs.isInHost()) {
44
- (_a = options == null ? void 0 : options.success) == null ? void 0 : _a.call(options, base);
45
- return base;
112
+ var call = (method, params, timeout_ms = DEFAULT_TIMEOUT_MS) => {
113
+ const state = getState();
114
+ if (!isInHost()) {
115
+ return Promise.reject(new MiniAppError(ErrorCode.NOT_IN_HOST, `${method} ch\u1EC9 ch\u1EA1y trong app Retion`, method));
46
116
  }
47
- try {
48
- const host = await invoke("getSystemInfo");
49
- const result = { ...base, ...host, sdkVersion: SDK_VERSION, inHost: true };
50
- (_b = options == null ? void 0 : options.success) == null ? void 0 : _b.call(options, result);
51
- return result;
52
- } catch (error) {
53
- const normalized = chunkC2AZ33LI_cjs.MiniAppError.from(error, "getSystemInfo");
54
- (_c = options == null ? void 0 : options.fail) == null ? void 0 : _c.call(options, normalized);
55
- throw normalized;
117
+ const id = `${Date.now().toString(36)}-${++state.seq}`;
118
+ return new Promise((resolve, reject) => {
119
+ const timer = setTimeout(() => {
120
+ state.pending.delete(id);
121
+ reject(new MiniAppError(ErrorCode.TIMEOUT, `${method}: host kh\xF4ng ph\u1EA3n h\u1ED3i sau ${timeout_ms}ms`, method));
122
+ }, timeout_ms);
123
+ state.pending.set(id, { method, resolve, reject, timer });
124
+ post({ __retion: PROTOCOL_VERSION, id, method, params });
125
+ });
126
+ };
127
+ var notify = (method, params) => {
128
+ if (isInHost()) {
129
+ post({ __retion: PROTOCOL_VERSION, method, params });
56
130
  }
57
131
  };
58
- var notifyReady = () => chunkC2AZ33LI_cjs.notify("ready", { sdkVersion: SDK_VERSION });
59
132
 
60
- // src/apis/user.ts
61
- var getAccessToken = (options) => invoke("getAccessToken", void 0, options);
62
- var getUserInfo = (options) => invoke("getUserInfo", void 0, options);
133
+ // src/version.ts
134
+ var SDK_VERSION = "0.2.0" ;
135
+
136
+ // src/apis/app.ts
137
+ var closeApp = () => call("closeApp");
138
+ var notifyReady = () => notify("ready", { sdkVersion: SDK_VERSION });
63
139
 
64
- // src/apis/page.ts
65
- var getPageInfo = (options) => invoke("getPageInfo", void 0, options);
140
+ // src/apis/route.ts
141
+ var getRouteParams = () => ({ ...getState().context });
66
142
 
67
- // src/apis/chat.ts
68
- var withText = (method, options) => {
69
- var _a;
70
- const [params, callbacks] = splitOptions(options);
71
- if (typeof params.text !== "string" || !params.text.trim()) {
72
- const error = new chunkC2AZ33LI_cjs.MiniAppError(chunkC2AZ33LI_cjs.ErrorCode.INVALID_PARAMS, "text kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng", method);
73
- (_a = callbacks.fail) == null ? void 0 : _a.call(callbacks, error);
74
- return Promise.reject(error);
143
+ // src/apis/user.ts
144
+ var TOKEN_QUERY_PARAM = "access_token";
145
+ var readTokenFromUrl = () => {
146
+ if (typeof window === "undefined") {
147
+ return null;
75
148
  }
76
- return invoke(method, params, callbacks);
149
+ return new URLSearchParams(window.location.search).get(TOKEN_QUERY_PARAM);
77
150
  };
78
- var sendMessage = (options) => withText("sendMessage", options);
79
- var insertText = (options) => withText("insertText", options);
80
-
81
- // src/apis/dev.ts
82
- var setMockHandler = (handler) => {
83
- chunkC2AZ33LI_cjs.getState().mock = handler;
151
+ var getAccessToken = async () => {
152
+ if (isInHost()) {
153
+ return call("getAccessToken");
154
+ }
155
+ const token = readTokenFromUrl();
156
+ if (!token) {
157
+ throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngo\xE0i app: thi\u1EBFu ?${TOKEN_QUERY_PARAM}= tr\xEAn URL`, "getAccessToken");
158
+ }
159
+ return token;
84
160
  };
85
161
 
86
162
  // src/index.ts
87
163
  if (typeof window !== "undefined") {
88
- chunkC2AZ33LI_cjs.getState();
164
+ getState();
89
165
  if (document.readyState === "loading") {
90
166
  document.addEventListener("DOMContentLoaded", notifyReady, { once: true });
91
167
  } else {
@@ -93,40 +169,12 @@ if (typeof window !== "undefined") {
93
169
  }
94
170
  }
95
171
 
96
- Object.defineProperty(exports, "ErrorCode", {
97
- enumerable: true,
98
- get: function () { return chunkC2AZ33LI_cjs.ErrorCode; }
99
- });
100
- Object.defineProperty(exports, "MiniAppError", {
101
- enumerable: true,
102
- get: function () { return chunkC2AZ33LI_cjs.MiniAppError; }
103
- });
104
- Object.defineProperty(exports, "PROTOCOL_VERSION", {
105
- enumerable: true,
106
- get: function () { return chunkC2AZ33LI_cjs.PROTOCOL_VERSION; }
107
- });
108
- Object.defineProperty(exports, "getRouteParams", {
109
- enumerable: true,
110
- get: function () { return chunkC2AZ33LI_cjs.getRouteParams; }
111
- });
112
- Object.defineProperty(exports, "isInHost", {
113
- enumerable: true,
114
- get: function () { return chunkC2AZ33LI_cjs.isInHost; }
115
- });
116
- Object.defineProperty(exports, "onContextChange", {
117
- enumerable: true,
118
- get: function () { return chunkC2AZ33LI_cjs.onContextChange; }
119
- });
172
+ exports.ErrorCode = ErrorCode;
173
+ exports.MiniAppError = MiniAppError;
120
174
  exports.SDK_VERSION = SDK_VERSION;
121
175
  exports.closeApp = closeApp;
122
176
  exports.getAccessToken = getAccessToken;
123
- exports.getPageInfo = getPageInfo;
124
- exports.getSystemInfo = getSystemInfo;
125
- exports.getUserInfo = getUserInfo;
126
- exports.insertText = insertText;
127
- exports.openWebview = openWebview;
128
- exports.sendMessage = sendMessage;
129
- exports.setMockHandler = setMockHandler;
130
- exports.showToast = showToast;
177
+ exports.getRouteParams = getRouteParams;
178
+ exports.isInHost = isInHost;
131
179
  //# sourceMappingURL=index.cjs.map
132
180
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/invoke.ts","../src/version.ts","../src/apis/app.ts","../src/apis/user.ts","../src/apis/page.ts","../src/apis/chat.ts","../src/apis/dev.ts","../src/index.ts"],"names":["call","MiniAppError","ErrorCode","isInHost","notify","getState"],"mappings":"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","file":"index.cjs","sourcesContent":["import { call } from './bridge';\nimport { MiniAppError } from './errors';\nimport type { CallbackOptions } from './types';\n\n/**\n * Gọi API host, hỗ trợ cả 2 kiểu: `await api()` và `api({ success, fail })`.\n * Có callback `fail` → promise vẫn reject (người dùng await vẫn bắt được), nhưng không gây\n * \"unhandled rejection\" khi họ chỉ dùng callback.\n */\nexport const invoke = <T>(method: string, params?: unknown, options?: CallbackOptions<T>): Promise<T> => {\n const promise = call<T>(method, params);\n if (options?.success || options?.fail) {\n promise.then(options.success, (error: unknown) => options.fail?.(MiniAppError.from(error, method)));\n }\n return promise;\n};\n\n/** Tách callback ra khỏi object tham số: `{ url, success, fail }` → `[{ url }, { success, fail }]`. */\nexport const splitOptions = <P extends object, T>(\n options: P & CallbackOptions<T>,\n): [Omit<P, keyof CallbackOptions<T>>, CallbackOptions<T>] => {\n const { success, fail, ...params } = options;\n return [params, { success, fail }];\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 { isInHost, notify } from '../bridge';\nimport { ErrorCode, MiniAppError } from '../errors';\nimport { invoke, splitOptions } from '../invoke';\nimport type { CallbackOptions, SystemInfo } from '../types';\nimport { SDK_VERSION } from '../version';\n\n/** Đóng mini app. */\nexport const closeApp = (options?: CallbackOptions<void>): Promise<void> => invoke('closeApp', undefined, options);\n\n/** Mở link ngoài bằng trình duyệt trong app. Chỉ chấp nhận http(s). */\nexport const openWebview = (options: { url: string } & CallbackOptions<void>): Promise<void> => {\n const [params, callbacks] = splitOptions(options);\n if (!/^https?:\\/\\//i.test(params.url ?? '')) {\n const error = new MiniAppError(ErrorCode.INVALID_PARAMS, 'url phải bắt đầu bằng http:// hoặc https://', 'openWebview');\n callbacks.fail?.(error);\n return Promise.reject(error);\n }\n return invoke('openWebview', params, callbacks);\n};\n\n/** Hiện toast native. */\nexport const showToast = (options: { message: string } & CallbackOptions<void>): Promise<void> => {\n const [params, callbacks] = splitOptions(options);\n return invoke('showToast', params, callbacks);\n};\n\n/** Thông tin thiết bị / host. Ngoài app trả giá trị mặc định thay vì lỗi. */\nexport const getSystemInfo = async (options?: CallbackOptions<SystemInfo>): Promise<SystemInfo> => {\n const base: SystemInfo = { platform: 'web', sdkVersion: SDK_VERSION, inHost: isInHost() };\n if (!isInHost()) {\n options?.success?.(base);\n return base;\n }\n try {\n const host = await invoke<Partial<SystemInfo>>('getSystemInfo');\n const result: SystemInfo = { ...base, ...host, sdkVersion: SDK_VERSION, inHost: true };\n options?.success?.(result);\n return result;\n } catch (error) {\n const normalized = MiniAppError.from(error, 'getSystemInfo');\n options?.fail?.(normalized);\n throw normalized;\n }\n};\n\n/** @internal SDK tự gọi khi trang sẵn sàng — host đẩy context mới nhất nếu đổi trong lúc load. */\nexport const notifyReady = (): void => notify('ready', { sdkVersion: SDK_VERSION });\n","import { invoke } from '../invoke';\nimport type { CallbackOptions, UserInfo } from '../types';\n\n/**\n * Access token của mini app cho PAGE hiện tại (mỗi page 1 token). Host tự refresh →\n * gọi lại mỗi khi cần thay vì lưu lâu phía web.\n */\nexport const getAccessToken = (options?: CallbackOptions<string>): Promise<string> =>\n invoke('getAccessToken', undefined, options);\n\n/** Nhân viên đang đăng nhập app. */\nexport const getUserInfo = (options?: CallbackOptions<UserInfo>): Promise<UserInfo> =>\n invoke('getUserInfo', undefined, options);\n","import { invoke } from '../invoke';\nimport type { CallbackOptions, PageInfo } from '../types';\n\n/**\n * Page (kênh) đang mở mini app. Mini app được cài theo page — mỗi page có token, quyền,\n * dữ liệu riêng; host chạy 1 instance mini app cho mỗi page (đổi page = instance khác).\n */\nexport const getPageInfo = (options?: CallbackOptions<PageInfo>): Promise<PageInfo> =>\n invoke('getPageInfo', undefined, options);\n","import { ErrorCode, MiniAppError } from '../errors';\nimport { invoke, splitOptions } from '../invoke';\nimport type { CallbackOptions } from '../types';\n\ntype TextOptions = { text: string } & CallbackOptions<void>;\n\nconst withText = (method: string, options: TextOptions): Promise<void> => {\n const [params, callbacks] = splitOptions(options);\n if (typeof params.text !== 'string' || !params.text.trim()) {\n const error = new MiniAppError(ErrorCode.INVALID_PARAMS, 'text không được rỗng', method);\n callbacks.fail?.(error);\n return Promise.reject(error);\n }\n return invoke(method, params, callbacks);\n};\n\n/** Gửi tin nhắn vào conversation đang mở mini app. */\nexport const sendMessage = (options: TextOptions): Promise<void> => withText('sendMessage', options);\n\n/** Chèn text vào ô nhập chat (không gửi) — nhân viên tự sửa/gửi. */\nexport const insertText = (options: TextOptions): Promise<void> => withText('insertText', options);\n","import { getState } from '../bridge';\nimport type { MockHandler } from '../types';\n\n/**\n * Giả lập host khi dev trên trình duyệt. Không có tác dụng khi chạy trong app.\n * @example\n * if (import.meta.env.DEV) {\n * setMockHandler((method) => (method === 'getAccessToken' ? 'dev-token' : undefined));\n * }\n */\nexport const setMockHandler = (handler: MockHandler | null): void => {\n getState().mock = handler;\n};\n","import { getState } from './bridge';\nimport { notifyReady } from './apis/app';\n\nexport { getRouteParams, onContextChange } from './apis/route';\nexport { closeApp, openWebview, showToast, getSystemInfo } from './apis/app';\nexport { getAccessToken, getUserInfo } from './apis/user';\nexport { getPageInfo } from './apis/page';\nexport { sendMessage, insertText } from './apis/chat';\nexport { setMockHandler } from './apis/dev';\nexport { isInHost, PROTOCOL_VERSION } from './bridge';\nexport { MiniAppError, ErrorCode } from './errors';\nexport { SDK_VERSION } from './version';\nexport type { RouteParams, CallbackOptions, UserInfo, SystemInfo, PageInfo, MockHandler } from './types';\nexport type { HostInfo } from './bridge';\n\n// Khởi tạo bridge ngay khi import (host có thể đẩy event trước khi app gọi API 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 /** 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 state.context = { ...prev, ...(message.data as RouteParams) };\n emit(state, CONTEXT_EVENT, { next: { ...state.context }, 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 } 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","import { getState } 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","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","import { getState } from './bridge';\nimport { notifyReady } from './apis/app';\n\nexport { getRouteParams } from './apis/route';\nexport { getAccessToken } from './apis/user';\nexport { closeApp } 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"]}