@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 +12 -4
- package/README.md +134 -85
- package/dist/index.cjs +147 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -51
- package/dist/index.d.ts +47 -51
- package/dist/index.js +142 -67
- package/dist/index.js.map +1 -1
- package/package.json +4 -24
- package/dist/chunk-C2AZ33LI.cjs +0 -175
- package/dist/chunk-C2AZ33LI.cjs.map +0 -1
- package/dist/chunk-PVXYU6XK.js +0 -165
- package/dist/chunk-PVXYU6XK.js.map +0 -1
- package/dist/types-8g8OpZsj.d.cts +0 -65
- package/dist/types-8g8OpZsj.d.ts +0 -65
- package/dist/vue/index.cjs +0 -19
- package/dist/vue/index.cjs.map +0 -1
- package/dist/vue/index.d.cts +0 -13
- package/dist/vue/index.d.ts +0 -13
- package/dist/vue/index.js +0 -17
- package/dist/vue/index.js.map +0 -1
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
|
-
-
|
|
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
|
-
|
|
3
|
+
Kết nối web mini app (Vue, React, ...) với app Retion.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
import { getRouteParams, getAccessToken, insertText, closeApp, isInHost } from '@retion-miniapp/sdk';
|
|
25
|
+
## 2. Cấu hình project
|
|
20
26
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
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
|
-
|
|
37
|
+
**b. Router dạng hash** (nếu dùng vue-router):
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
```ts
|
|
40
|
+
createRouter({ history: createWebHashHistory(), routes });
|
|
41
|
+
```
|
|
39
42
|
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
await
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
99
|
+
Chừa khoảng **100px bên phải header** — app phủ nút `••• | ✕` ở góc trên phải.
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
import { setMockHandler } from '@retion-miniapp/sdk';
|
|
101
|
+
## 5. Chạy thử trên trình duyệt
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
+
## 6. Deploy
|
|
109
112
|
|
|
110
113
|
```bash
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
##
|
|
156
|
+
## Hỏi đáp
|
|
116
157
|
|
|
117
|
-
|
|
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
|
-
|
|
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
|
|
124
|
-
npm run 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
|
-
|
|
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
|
-
|
|
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/
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
48
|
+
const query = Object.fromEntries(new URLSearchParams(window.location.search));
|
|
49
|
+
return { ...query, ...(_a = window.__RETION_MINIAPP_CONTEXT__) != null ? _a : {} };
|
|
15
50
|
};
|
|
16
|
-
var
|
|
17
|
-
const
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
103
|
+
return (_a = window.__RETION_MINIAPP__) != null ? _a : window.__RETION_MINIAPP__ = createState();
|
|
35
104
|
};
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
return
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
(
|
|
55
|
-
|
|
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/
|
|
61
|
-
var
|
|
62
|
-
|
|
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/
|
|
65
|
-
var
|
|
140
|
+
// src/apis/route.ts
|
|
141
|
+
var getRouteParams = () => ({ ...getState().context });
|
|
66
142
|
|
|
67
|
-
// src/apis/
|
|
68
|
-
var
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
149
|
+
return new URLSearchParams(window.location.search).get(TOKEN_QUERY_PARAM);
|
|
77
150
|
};
|
|
78
|
-
var
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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.
|
|
124
|
-
exports.
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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"]}
|