@retion-miniapp/sdk 0.2.0 → 0.3.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,12 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.3.0 — 2026-09-30
8
+
9
+ ### Added
10
+ - `getToken('access' | 'partner' | 'chatbox')` — lấy `partner_token` / `chatbox_token` khi admin đã bật quyền cho mini app trên Console.
11
+ - `ErrorCode.PERMISSION_DENIED`.
12
+
7
13
  ## 0.2.0 — 2026-09-30
8
14
 
9
15
  ### Breaking
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Kết nối web mini app (Vue, React, ...) với app Retion.
4
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 chỉ làm 3 việc:
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
6
 
7
7
  1. Cho biết mini app đang mở ở **page nào, khách nào** → `getRouteParams()`
8
8
  2. Đưa **token** để gọi API backend của bạn → `getAccessToken()`
@@ -127,6 +127,7 @@ Lên Console → version vừa deploy → **→ production**. Người dùng nh
127
127
  |---|---|---|---|
128
128
  | `getRouteParams()` | `RouteParams` | Thông tin app truyền vào | Đọc query URL |
129
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=` |
130
131
  | `closeApp()` | `Promise<void>` | Đóng mini app | Lỗi `NOT_IN_HOST` |
131
132
  | `isInHost()` | `boolean` | `true` | `false` |
132
133
 
@@ -139,6 +140,7 @@ Mọi lỗi là `MiniAppError` có `code`:
139
140
  | `code` | Khi nào |
140
141
  |---|---|
141
142
  | `NOT_IN_HOST` | Chạy ngoài app mà gọi hàm cần app |
143
+ | `PERMISSION_DENIED` | Mini app chưa được cấp quyền lấy token đó |
142
144
  | `TIMEOUT` | App không phản hồi sau 15s |
143
145
  | `NOT_SUPPORTED` | Phiên bản app hiện tại chưa hỗ trợ |
144
146
  | `HOST_ERROR` | Lỗi khác phía app (vd page chưa có token) |
@@ -153,6 +155,20 @@ try {
153
155
  }
154
156
  ```
155
157
 
158
+ ### Token khác ngoài `access_token`
159
+
160
+ | Token | Là gì | Cần quyền |
161
+ |---|---|---|
162
+ | `access` | Token của mini app cho page hiện tại | Không — luôn được |
163
+ | `partner` | Token đối tác của page | `partner_token` |
164
+ | `chatbox` | Token đăng nhập của người dùng app (**toàn quyền** như người dùng) | `chatbox_token` |
165
+
166
+ 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**.
167
+
168
+ ```ts
169
+ const partnerToken = await getToken('partner');
170
+ ```
171
+
156
172
  ## Hỏi đáp
157
173
 
158
174
  **Web đang chạy (iframe / trình duyệt) có bị ảnh hưởng khi cài SDK?**
package/dist/index.cjs CHANGED
@@ -6,6 +6,8 @@ var ErrorCode = {
6
6
  NOT_IN_HOST: "NOT_IN_HOST",
7
7
  /** Host không phản hồi trong thời gian chờ. */
8
8
  TIMEOUT: "TIMEOUT",
9
+ /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */
10
+ PERMISSION_DENIED: "PERMISSION_DENIED",
9
11
  /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
10
12
  NOT_SUPPORTED: "NOT_SUPPORTED",
11
13
  /** Lỗi khác phía host. */
@@ -131,7 +133,7 @@ var notify = (method, params) => {
131
133
  };
132
134
 
133
135
  // src/version.ts
134
- var SDK_VERSION = "0.2.0" ;
136
+ var SDK_VERSION = "0.3.0" ;
135
137
 
136
138
  // src/apis/app.ts
137
139
  var closeApp = () => call("closeApp");
@@ -158,6 +160,24 @@ var getAccessToken = async () => {
158
160
  }
159
161
  return token;
160
162
  };
163
+ var QUERY_PARAM = {
164
+ access: "access_token",
165
+ partner: "partner_token",
166
+ chatbox: "chatbox_token"
167
+ };
168
+ var getToken = async (type) => {
169
+ if (!(type in QUERY_PARAM)) {
170
+ throw new MiniAppError(ErrorCode.HOST_ERROR, `Lo\u1EA1i token kh\xF4ng h\u1EE3p l\u1EC7: ${String(type)}`, "getToken");
171
+ }
172
+ if (isInHost()) {
173
+ return call("getToken", { type });
174
+ }
175
+ const token = typeof window === "undefined" ? null : new URLSearchParams(window.location.search).get(QUERY_PARAM[type]);
176
+ if (!token) {
177
+ throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngo\xE0i app: thi\u1EBFu ?${QUERY_PARAM[type]}= tr\xEAn URL`, "getToken");
178
+ }
179
+ return token;
180
+ };
161
181
 
162
182
  // src/index.ts
163
183
  if (typeof window !== "undefined") {
@@ -175,6 +195,7 @@ exports.SDK_VERSION = SDK_VERSION;
175
195
  exports.closeApp = closeApp;
176
196
  exports.getAccessToken = getAccessToken;
177
197
  exports.getRouteParams = getRouteParams;
198
+ exports.getToken = getToken;
178
199
  exports.isInHost = isInHost;
179
200
  //# sourceMappingURL=index.cjs.map
180
201
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** 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"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */\n PERMISSION_DENIED: 'PERMISSION_DENIED',\n /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */\n NOT_SUPPORTED: 'NOT_SUPPORTED',\n /** Lỗi khác phía host. */\n HOST_ERROR: 'HOST_ERROR',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode] | (string & {});\n\nexport class MiniAppError extends Error {\n readonly code: ErrorCode;\n readonly method?: string;\n\n constructor(code: ErrorCode, message: string, method?: string) {\n super(message);\n this.name = 'MiniAppError';\n this.code = code;\n this.method = method;\n }\n\n /** Chuẩn hoá lỗi từ host (object thuần, Error, string) về MiniAppError. */\n static from(error: unknown, method?: string): MiniAppError {\n if (error instanceof MiniAppError) {\n return error;\n }\n if (error && typeof error === 'object') {\n const { code, message } = error as { code?: unknown; message?: unknown };\n return new MiniAppError(\n typeof code === 'string' ? code : ErrorCode.HOST_ERROR,\n typeof message === 'string' ? message : 'Unknown error',\n method,\n );\n }\n return new MiniAppError(ErrorCode.HOST_ERROR, String(error), method);\n }\n}\n","/**\n * Bridge web mini app ↔ host (app Retion). Nội bộ SDK — dev mini app không import trực tiếp.\n *\n * Giao thức v1\n * web → host : ReactNativeWebView.postMessage(JSON.stringify(RequestMessage))\n * host → web : window.__RETION_MINIAPP__.receive(ResponseMessage | EventMessage)\n * host inject trước khi trang chạy:\n * window.__RETION_MINIAPP_HOST__ = { protocol } ← đánh dấu host hỗ trợ SDK\n * window.__RETION_MINIAPP_CONTEXT__ = RouteParams ← context ban đầu\n *\n * Chỉ có ReactNativeWebView KHÔNG đủ để coi là host: màn iframe cũ của app cũng là RN WebView\n * nhưng không hiểu giao thức này (gọi API sẽ treo tới timeout). Thiếu marker → NOT_IN_HOST ngay.\n *\n * State đặt trên window (singleton): nhiều bản SDK trong cùng trang (trùng version trong\n * node_modules) vẫn dùng chung 1 bridge thay vì ghi đè hàm receive của nhau.\n */\nimport { ErrorCode, MiniAppError } from './errors';\nimport type { RouteParams } from './types';\n\nexport const PROTOCOL_VERSION = 1;\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const CONTEXT_EVENT = 'context';\n\nexport interface RequestMessage {\n __retion: typeof PROTOCOL_VERSION;\n /** Có id → host phải trả ResponseMessage. Không có → lệnh 1 chiều. */\n id?: string;\n method: string;\n params?: unknown;\n}\n\nexport type ResponseMessage =\n | { id: string; result?: unknown; error?: undefined }\n | { id: string; error: { code: string; message: string } };\n\nexport interface EventMessage {\n event: string;\n data?: unknown;\n}\n\nexport interface HostInfo {\n /** Phiên bản giao thức host hỗ trợ. */\n protocol: number;\n}\n\ntype Listener = (data: unknown) => void;\n\ninterface Pending {\n method: string;\n resolve: (value: unknown) => void;\n reject: (error: MiniAppError) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface BridgeState {\n protocol: number;\n seq: number;\n pending: Map<string, Pending>;\n listeners: Map<string, Set<Listener>>;\n context: RouteParams;\n receive: (message: ResponseMessage | EventMessage) => void;\n}\n\ndeclare global {\n interface Window {\n ReactNativeWebView?: { postMessage: (message: string) => void };\n __RETION_MINIAPP__?: BridgeState;\n __RETION_MINIAPP_CONTEXT__?: RouteParams;\n __RETION_MINIAPP_HOST__?: HostInfo;\n }\n}\n\nconst hasWindow = typeof window !== 'undefined';\n\nconst readInitialContext = (): RouteParams => {\n if (!hasWindow) {\n return {};\n }\n const query = Object.fromEntries(new URLSearchParams(window.location.search));\n return { ...query, ...(window.__RETION_MINIAPP_CONTEXT__ ?? {}) };\n};\n\nconst createState = (): BridgeState => {\n const state: BridgeState = {\n protocol: PROTOCOL_VERSION,\n seq: 0,\n pending: new Map(),\n listeners: new Map(),\n context: readInitialContext(),\n receive: (message) => {\n if (!message || typeof message !== 'object') {\n return;\n }\n if ('event' in message) {\n if (message.event === CONTEXT_EVENT) {\n // Cập nhật context TRƯỚC khi emit → getRouteParams() luôn đúng, kể cả không ai lắng nghe.\n const prev = state.context;\n 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\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 } from './apis/route';\nexport { getAccessToken, getToken, type TokenType } 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"]}
package/dist/index.d.cts CHANGED
@@ -32,6 +32,16 @@ declare const getRouteParams: () => RouteParams;
32
32
  * Gọi lại mỗi khi cần — không lưu token lâu phía web.
33
33
  */
34
34
  declare const getAccessToken: () => Promise<string>;
35
+ /** Loại token: `access` luôn được phép; `partner` / `chatbox` cần admin bật quyền trên Console. */
36
+ type TokenType = 'access' | 'partner' | 'chatbox';
37
+ /**
38
+ * Lấy token theo loại.
39
+ * - `access`: token của mini app cho page hiện tại (= getAccessToken()).
40
+ * - `partner`: token đối tác của page — cần quyền `partner_token`.
41
+ * - `chatbox`: token đăng nhập của người dùng app (toàn quyền) — cần quyền `chatbox_token`.
42
+ * Trong app, chưa được cấp quyền → MiniAppError PERMISSION_DENIED. Ngoài app: đọc query URL.
43
+ */
44
+ declare const getToken: (type: TokenType) => Promise<string>;
35
45
 
36
46
  /**
37
47
  * Đóng mini app, quay về màn chat. Ngoài app → reject NOT_IN_HOST (web tự xử lý, vd router.back()).
@@ -43,6 +53,8 @@ declare const ErrorCode: {
43
53
  readonly NOT_IN_HOST: "NOT_IN_HOST";
44
54
  /** Host không phản hồi trong thời gian chờ. */
45
55
  readonly TIMEOUT: "TIMEOUT";
56
+ /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */
57
+ readonly PERMISSION_DENIED: "PERMISSION_DENIED";
46
58
  /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
47
59
  readonly NOT_SUPPORTED: "NOT_SUPPORTED";
48
60
  /** Lỗi khác phía host. */
@@ -124,4 +136,4 @@ declare const isInHost: () => boolean;
124
136
  /** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */
125
137
  declare const SDK_VERSION: string;
126
138
 
127
- export { ErrorCode, MiniAppError, type RouteParams, SDK_VERSION, closeApp, getAccessToken, getRouteParams, isInHost };
139
+ export { ErrorCode, MiniAppError, type RouteParams, SDK_VERSION, type TokenType, closeApp, getAccessToken, getRouteParams, getToken, isInHost };
package/dist/index.d.ts CHANGED
@@ -32,6 +32,16 @@ declare const getRouteParams: () => RouteParams;
32
32
  * Gọi lại mỗi khi cần — không lưu token lâu phía web.
33
33
  */
34
34
  declare const getAccessToken: () => Promise<string>;
35
+ /** Loại token: `access` luôn được phép; `partner` / `chatbox` cần admin bật quyền trên Console. */
36
+ type TokenType = 'access' | 'partner' | 'chatbox';
37
+ /**
38
+ * Lấy token theo loại.
39
+ * - `access`: token của mini app cho page hiện tại (= getAccessToken()).
40
+ * - `partner`: token đối tác của page — cần quyền `partner_token`.
41
+ * - `chatbox`: token đăng nhập của người dùng app (toàn quyền) — cần quyền `chatbox_token`.
42
+ * Trong app, chưa được cấp quyền → MiniAppError PERMISSION_DENIED. Ngoài app: đọc query URL.
43
+ */
44
+ declare const getToken: (type: TokenType) => Promise<string>;
35
45
 
36
46
  /**
37
47
  * Đóng mini app, quay về màn chat. Ngoài app → reject NOT_IN_HOST (web tự xử lý, vd router.back()).
@@ -43,6 +53,8 @@ declare const ErrorCode: {
43
53
  readonly NOT_IN_HOST: "NOT_IN_HOST";
44
54
  /** Host không phản hồi trong thời gian chờ. */
45
55
  readonly TIMEOUT: "TIMEOUT";
56
+ /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */
57
+ readonly PERMISSION_DENIED: "PERMISSION_DENIED";
46
58
  /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
47
59
  readonly NOT_SUPPORTED: "NOT_SUPPORTED";
48
60
  /** Lỗi khác phía host. */
@@ -124,4 +136,4 @@ declare const isInHost: () => boolean;
124
136
  /** Thay lúc build (tsup define) / test (vitest define) bằng version trong package.json. */
125
137
  declare const SDK_VERSION: string;
126
138
 
127
- export { ErrorCode, MiniAppError, type RouteParams, SDK_VERSION, closeApp, getAccessToken, getRouteParams, isInHost };
139
+ export { ErrorCode, MiniAppError, type RouteParams, SDK_VERSION, type TokenType, closeApp, getAccessToken, getRouteParams, getToken, isInHost };
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ var ErrorCode = {
4
4
  NOT_IN_HOST: "NOT_IN_HOST",
5
5
  /** Host không phản hồi trong thời gian chờ. */
6
6
  TIMEOUT: "TIMEOUT",
7
+ /** Mini app chưa được admin cấp quyền (vd lấy partner_token / chatbox_token). */
8
+ PERMISSION_DENIED: "PERMISSION_DENIED",
7
9
  /** Host (phiên bản app hiện tại) chưa hỗ trợ API này. */
8
10
  NOT_SUPPORTED: "NOT_SUPPORTED",
9
11
  /** Lỗi khác phía host. */
@@ -129,7 +131,7 @@ var notify = (method, params) => {
129
131
  };
130
132
 
131
133
  // src/version.ts
132
- var SDK_VERSION = "0.2.0" ;
134
+ var SDK_VERSION = "0.3.0" ;
133
135
 
134
136
  // src/apis/app.ts
135
137
  var closeApp = () => call("closeApp");
@@ -156,6 +158,24 @@ var getAccessToken = async () => {
156
158
  }
157
159
  return token;
158
160
  };
161
+ var QUERY_PARAM = {
162
+ access: "access_token",
163
+ partner: "partner_token",
164
+ chatbox: "chatbox_token"
165
+ };
166
+ var getToken = async (type) => {
167
+ if (!(type in QUERY_PARAM)) {
168
+ throw new MiniAppError(ErrorCode.HOST_ERROR, `Lo\u1EA1i token kh\xF4ng h\u1EE3p l\u1EC7: ${String(type)}`, "getToken");
169
+ }
170
+ if (isInHost()) {
171
+ return call("getToken", { type });
172
+ }
173
+ const token = typeof window === "undefined" ? null : new URLSearchParams(window.location.search).get(QUERY_PARAM[type]);
174
+ if (!token) {
175
+ throw new MiniAppError(ErrorCode.NOT_IN_HOST, `Ngo\xE0i app: thi\u1EBFu ?${QUERY_PARAM[type]}= tr\xEAn URL`, "getToken");
176
+ }
177
+ return token;
178
+ };
159
179
 
160
180
  // src/index.ts
161
181
  if (typeof window !== "undefined") {
@@ -167,6 +187,6 @@ if (typeof window !== "undefined") {
167
187
  }
168
188
  }
169
189
 
170
- export { ErrorCode, MiniAppError, SDK_VERSION, closeApp, getAccessToken, getRouteParams, isInHost };
190
+ export { ErrorCode, MiniAppError, SDK_VERSION, closeApp, getAccessToken, getRouteParams, getToken, isInHost };
171
191
  //# sourceMappingURL=index.js.map
172
192
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/bridge.ts","../src/version.ts","../src/apis/app.ts","../src/apis/route.ts","../src/apis/user.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const ErrorCode = {\n /** Không chạy trong app Retion (trình duyệt / iframe cũ). */\n NOT_IN_HOST: 'NOT_IN_HOST',\n /** Host không phản hồi trong thời gian chờ. */\n TIMEOUT: 'TIMEOUT',\n /** 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"]}
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 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\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 } from './apis/route';\nexport { getAccessToken, getToken, type TokenType } 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retion-miniapp/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",