@thanhphi/create-360-miniapp 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -1,80 +1,329 @@
1
1
  # @thanhphi/create-360-miniapp
2
2
 
3
- Khởi tạo mini app theo cấu trúc Event và Time-Management: Expo 55, React Native 0.83.6, TypeScript, React Navigation, Redux Toolkit, TanStack Query và Re.Pack 5. Template có sẵn module auth, module nghiệp vụ mẫu, kết nối token host qua `@thanhphi/miniapp-sdk`, env staging/beta/prod và pipeline Azure.
3
+ Tạo mini app React Native chạy bên trong MySGR, có thể chạy độc lập để phát triển và kiểm thử.
4
4
 
5
- ## Dùng ngay từ source
5
+ Project được tạo sẵn gồm TypeScript, navigation, đăng nhập qua host, HTTP client có refresh token, màn hồ sơ mẫu, theme 360, cấu hình môi trường và pipeline Azure DevOps.
6
6
 
7
- Từ `mysgr-shared`:
7
+ ## Mục lục
8
+
9
+ - [Bắt đầu nhanh](#bắt-đầu-nhanh)
10
+ - [Yêu cầu](#yêu-cầu)
11
+ - [Các lựa chọn khi tạo app](#các-lựa-chọn-khi-tạo-app)
12
+ - [Cấu trúc project](#cấu-trúc-project)
13
+ - [Cấu hình môi trường và API](#cấu-hình-môi-trường-và-api)
14
+ - [Chạy trong MySGR](#chạy-trong-mysgr)
15
+ - [Chạy độc lập](#chạy-độc-lập)
16
+ - [Thêm màn hình và gọi API](#thêm-màn-hình-và-gọi-api)
17
+ - [Màu sắc và kích thước UI](#màu-sắc-và-kích-thước-ui)
18
+ - [Đóng gói và triển khai](#đóng-gói-và-triển-khai)
19
+ - [Xử lý vấn đề thường gặp](#xử-lý-vấn-đề-thường-gặp)
20
+
21
+ ## Bắt đầu nhanh
22
+
23
+ Ví dụ tạo mini app quản lý nghỉ phép:
8
24
 
9
25
  ```sh
10
- node packages/create-360-miniapp/bin/create-360-miniapp.cjs leave-management --module leave --app-code LVE --port 8085
26
+ npx @thanhphi/create-360-miniapp@latest leave-management --module leave --app-code LVE --port 8085
11
27
  cd leave-management
12
28
  npm install
13
29
  npm run env:staging
14
30
  npm run start:mf
15
31
  ```
16
32
 
17
- Lệnh chỉ tạo thư mục mới, không ghi đè app hiện có. Mặc định không install hoặc tạo Git repository. `--install` cài dependencies; `--native` cài rồi chạy Expo prebuild để tạo project `ios/` và `android/`. Native project dùng ID riêng của app mới. Chi tiết auth, cấu trúc và pipeline nằm trong README của app được sinh.
33
+ Lệnh cuối mở Re.Pack dev server. Để xem giao diện, mở app đã đăng ký từ **host MySGR có cấu hình trỏ tới dev server này**. Đây không phải lệnh khởi chạy một ứng dụng độc lập trên điện thoại.
34
+
35
+ Có thể tạo project bằng npm create thay cho npx:
36
+
37
+ ```sh
38
+ npm create @thanhphi/360-miniapp@latest leave-management -- --module leave --app-code LVE --port 8085
39
+ ```
40
+
41
+ Generator chỉ tạo thư mục mới, không ghi đè project có sẵn và không tự tạo Git repository. App vừa sinh có README riêng với các giá trị cấu hình đã được điền.
42
+
43
+ ## Yêu cầu
44
+
45
+ Bộ phiên bản của template:
46
+
47
+ | Thành phần | Phiên bản |
48
+ | ------------------------- | ----------------------------------- |
49
+ | Node.js | Từ 20.19.4; khuyến nghị Node 22 LTS |
50
+ | Expo | SDK 55 |
51
+ | React / React Native | 19.2.0 / 0.83.6 |
52
+ | UI360 / miniapp-sdk | 0.2.0 / 0.1.0 |
53
+ | Unistyles / Nitro Modules | 3.2.5 / 0.36.5 |
54
+ | Re.Pack | 5.2.x |
55
+
56
+ - Chạy trong MySGR: cần host cung cấp các native dependencies tương thích, cấu hình tải remote và `hostCapabilities` theo SDK.
57
+ - Chạy độc lập trên iOS: cần macOS, Xcode và CocoaPods.
58
+ - Chạy độc lập trên Android: cần Android SDK, JDK và emulator hoặc thiết bị thật.
59
+ - Không dùng Expo Go cho template này; các native dependencies yêu cầu development build.
60
+ - UI360 được đóng gói trong mini app, không bắt buộc host dùng cùng phiên bản UI360. React, Unistyles và các native dependencies vẫn dùng chung với host.
61
+
62
+ ## Các lựa chọn khi tạo app
63
+
64
+ ```sh
65
+ npx @thanhphi/create-360-miniapp@latest leave-management --module leave --container LeaveManagement --app-code LVE --display-name "Quản lý nghỉ phép" --bundle-id com.sungroup.leave --port 8085 --env staging
66
+ ```
67
+
68
+ | Lựa chọn | Ý nghĩa | Mặc định |
69
+ | ---------------------- | --------------------------------------------------- | -------------------------------------------------- |
70
+ | Tên project | Tên thư mục và npm package, dạng `leave-management` | Bắt buộc |
71
+ | `--module` | Thư mục nghiệp vụ trong `src/modules/` | Tên project |
72
+ | `--container` | Tên container Re.Pack, dạng PascalCase | Tên project chuyển thành PascalCase |
73
+ | `--app-code` | Mã nghiệp vụ của app trên backend | Tên project bỏ dấu gạch, viết hoa, tối đa 12 ký tự |
74
+ | `--display-name` | Tên hiển thị khi chạy độc lập | Tên container |
75
+ | `--bundle-id` | ID ứng dụng native, phải riêng cho mỗi app | `com.sungroup.<tên-project-bỏ-dấu-gạch>` |
76
+ | `--port` | Port Re.Pack, từ 1024 đến 65535 | `8085` |
77
+ | `--env` | Môi trường ban đầu: `staging`, `beta`, `prod` | `staging` |
78
+ | `--install` | Tạo project rồi chạy npm install | Không tự cài |
79
+ | `--native` | Cài dependencies và chạy Expo prebuild | Không tạo ios/android |
80
+ | `--help` / `--version` | Xem trợ giúp / phiên bản generator | — |
81
+
82
+ App ID trong catalog mặc định bằng tên project, ví dụ `leave-management`. App code, container và app ID là ba giá trị khác nhau; kiểm tra bảng cấu hình trong README của project được sinh trước khi tích hợp host.
83
+
84
+ Không đặt module là `auth`, `common`, `library`, `redux`, `navigation` hoặc `themes`; đây là các thư mục của base.
85
+
86
+ ## Cấu trúc project
87
+
88
+ ```text
89
+ env/ staging.json, beta.json, prod.json
90
+ pipelines/ Pipeline build và deployment
91
+ scripts/ Chọn env, dọn output bundle
92
+ src/
93
+ common/mini-app/ Tích hợp host, provider và runtime
94
+ library/networking/ HTTP client dùng miniapp-sdk
95
+ modules/
96
+ auth/ Trạng thái phiên và đăng nhập standalone
97
+ <module>/ Màn hình, hooks, services, Redux nghiệp vụ
98
+ navigation/ Root navigator
99
+ redux/ Store và typed hooks
100
+ services/application.ts Khởi tạo store, QueryClient và HTTP client
101
+ themes/ Palette, geometry và cấu hình standalone
102
+ App.tsx Entry được host tải
103
+ StandaloneApp.tsx Entry chạy độc lập
104
+ ```
105
+
106
+ Phần lớn code tính năng mới đặt trong `src/modules/<module>/`. Không đặt endpoint nghiệp vụ hoặc logic màn hình trong provider/runtime.
107
+
108
+ Commit lockfile được sinh sau khi cài dependencies. Các lần cài tiếp theo và CI dùng `npm ci` để giữ phiên bản nhất quán.
109
+
110
+ ## Cấu hình môi trường và API
111
+
112
+ Sửa `env/staging.json`, `env/beta.json` và `env/prod.json`. Những URL, issuer và client ID mẫu phải được thay bằng cấu hình của app.
113
+
114
+ ```json
115
+ {
116
+ "ENV": "staging",
117
+ "BASE_URL": "https://api.example.com",
118
+ "API_TIMEOUT_MS": 15000,
119
+ "AUTH": {
120
+ "issuer": "https://identity.example.com",
121
+ "clientId": "native-client-id",
122
+ "redirectUrl": "com.sungroup.leave.auth://oauth/redirect",
123
+ "scopes": ["openid", "profile", "offline_access"]
124
+ }
125
+ }
126
+ ```
127
+
128
+ Chọn môi trường trước khi chạy Re.Pack hoặc bundle:
129
+
130
+ ```sh
131
+ npm run env:staging
132
+ # Hoặc npm run env:beta / npm run env:prod
133
+ ```
134
+
135
+ Script sinh `env/env-config.ts`; không sửa file này bằng tay. Đổi env cần restart dev server.
136
+
137
+ **Các giá trị env được đưa vào bundle. Không đặt mật khẩu, client secret, private key hoặc token vào đây.** Lưu secret deployment trong cấu hình secret của CI.
138
+
139
+ Màn mẫu chỉ gọi API khi người dùng nhấn **Tải hồ sơ**. Endpoint mặc định là `/me`; sửa `PROFILE_PATH` và mapping response trong `src/modules/<module>/services/profile.service.ts` theo backend thật.
140
+
141
+ ## Chạy trong MySGR
142
+
143
+ ### Cấu hình phía host
144
+
145
+ Đăng ký các thông tin sau trong registry/resolver của host và catalog tương ứng:
146
+
147
+ - App ID và app code.
148
+ - Tên container, ví dụ `LeaveManagement`.
149
+ - Module được expose: `LeaveManagement/MiniApp`.
150
+ - Dev server URL/port khi phát triển, hoặc URL artifact khi triển khai.
151
+
152
+ Các giá trị này có trong README được tạo cùng project. CLI không tự sửa repository host hay đăng ký dữ liệu backend.
153
+
154
+ ### Chạy local
18
155
 
19
- ## Chạy bằng npx từ tarball local
156
+ ```sh
157
+ npm run env:staging
158
+ npm run start:mf
159
+ ```
20
160
 
21
- Từ `mysgr-shared`, sau `npm run pack:check`:
161
+ Dùng thiết bị thật:
22
162
 
23
163
  ```sh
24
- npx --offline --yes --cache ./artifacts/.npm-cache --package ./artifacts/thanhphi-create-360-miniapp-0.2.0.tgz create-360-miniapp leave-management --module leave --app-code LVE
164
+ npm run start:mf:device
25
165
  ```
26
166
 
27
- Lệnh này dùng package đã đóng gói, không cần publish registry hoặc source Event/Time-Management. Cache nằm trong `artifacts`, cùng workspace.
167
+ Host phải trỏ tới IP LAN của máy phát triển và port đã chọn. Kiểm tra thiết bị truy cập được dev server; `localhost` trên điện thoại không phải máy tính.
168
+
169
+ ### Phiên đăng nhập
170
+
171
+ Host truyền `hostCapabilities` vào mini app. Contract có `auth.getSnapshot()`, `auth.subscribe()`, `auth.refreshIfNeeded()` và `closeMiniApp()`.
28
172
 
29
- ## Qua npm sau khi publish package
173
+ Base đã nối sẵn các chức năng này. HTTP client tự gắn access token, nhờ host refresh và retry tối đa một lần sau HTTP 401. Code màn hình/service không cần tự lưu token hoặc đăng nhập lại.
174
+
175
+ - Host sở hữu refresh token và persistence.
176
+ - Không persist token do host cấp trong mini app.
177
+ - Session đổi khi người dùng đổi tài khoản; refresh access token trong cùng session không reset dữ liệu nghiệp vụ.
178
+ - `param_supper_app` chứa dữ liệu mở app/thông báo, không dùng thay cho auth.
179
+ - Nếu thiếu `hostCapabilities`, kiểm tra cấu hình tích hợp; hosted entry không tự chuyển sang đăng nhập standalone.
180
+
181
+ ## Chạy độc lập
182
+
183
+ Cấu hình `AUTH` trong env trước, đăng ký OAuth native client và redirect URL với nhà cung cấp đăng nhập, sau đó:
30
184
 
31
185
  ```sh
32
- npx @thanhphi/create-360-miniapp@0.2.0 leave-management --module leave --app-code LVE
33
- # npm create là alias npm init và ánh xạ tới @thanhphi/create-360-miniapp:
34
- npm create @thanhphi/360-miniapp@0.2.0 leave-management -- --module leave --app-code LVE
186
+ npm run env:staging
187
+ npx expo prebuild
188
+ npm run ios:staging
189
+ # Hoặc npm run android:staging
190
+ ```
191
+
192
+ Sau khi có development build, có thể dùng `npm run start:staging` để chạy Metro.
193
+
194
+ - Đăng nhập OIDC sử dụng `react-native-app-auth`.
195
+ - Token standalone mặc định chỉ giữ trong bộ nhớ; đóng app/reset runtime sẽ mất phiên.
196
+ - Scopes mẫu không tự cấp quyền API. Cấu hình audience/scopes đúng với backend.
197
+ - Nếu backend yêu cầu đổi token OIDC sang token riêng, bổ sung flow exchange trong service auth.
198
+ - Đổi redirect URL hoặc thêm native dependency cần cập nhật native build; đổi JavaScript không nhất thiết cần prebuild lại.
199
+
200
+ Standalone là entry riêng; không xác định chế độ chạy chỉ bằng việc thiếu props host.
201
+
202
+ ## Thêm màn hình và gọi API
203
+
204
+ 1. Thêm screen/hook/service trong `src/modules/<module>/`.
205
+ 2. Đăng ký route trong navigator của module.
206
+ 3. Lấy HTTP client đã được tạo sẵn từ `useApplicationServices()`.
207
+ 4. Dùng Redux cho state nghiệp vụ và TanStack Query cho server data.
208
+
209
+ Ví dụ service:
210
+
211
+ ```ts
212
+ import type { ApiClient } from "../../../library/networking/client";
213
+
214
+ export function getItems(api: ApiClient, signal?: AbortSignal) {
215
+ return api.get<{ id: string; name: string }[]>("/items", { signal });
216
+ }
217
+ ```
218
+
219
+ Ví dụ trong component/hook; điều chỉnh relative imports theo vị trí file:
220
+
221
+ ```tsx
222
+ import { useQuery } from "@tanstack/react-query";
223
+ import { useApplicationServices } from "./common/mini-app/provider";
224
+
225
+ const { api } = useApplicationServices();
226
+ const query = useQuery({
227
+ queryKey: ["items"],
228
+ queryFn: ({ signal }) => getItems(api, signal),
229
+ });
230
+ ```
231
+
232
+ Ví dụ trên giả định API trả trực tiếp một array. Nếu backend trả envelope, unwrap trong service module. Nếu API báo hết hạn token trong HTTP 200, cấu hình thêm `isAuthExpired` trong HTTP client theo contract backend.
233
+
234
+ Khi query chứa dữ liệu cá nhân, đưa session ID vào query key như `useHome()` mẫu. Không lưu token trong query key.
235
+
236
+ ## Màu sắc và kích thước UI
237
+
238
+ - `StyleSheet.configure()` cấp theme cho Unistyles.
239
+ - `UIThemeProvider` cấp theme cho component UI360.
240
+ - `createUITheme()` tạo object dữ liệu; không đăng ký hoặc thay đổi theme global.
241
+ - `create360Theme()` tạo cùng dữ liệu và thêm `type/color` để dùng với Unistyles.
242
+
243
+ Khi hosted, base giữ nguyên mode/màu của Unistyles đang hoạt động. Không map tên màu legacy, không trộn palette khác và không ghi đè runtime host.
244
+
245
+ Geometry được tạo sẵn trong `src/themes/ui-geometry.ts` từ factory UI360, scale theo chiều rộng thiết kế 375. Provider và style mẫu cùng dùng geometry này; không cần host cung cấp spacing/radius/typography. Không nhân scale lại tại màn hình.
246
+
247
+ ```tsx
248
+ import { StyleSheet } from "react-native-unistyles";
249
+ import { uiGeometry } from "./themes/ui-geometry";
250
+
251
+ const { spacing, radius } = uiGeometry;
252
+ const styles = StyleSheet.create(({ color }) => ({
253
+ card: {
254
+ padding: spacing.xxl,
255
+ borderRadius: radius.lg,
256
+ backgroundColor: color.background,
257
+ },
258
+ }));
35
259
  ```
36
260
 
37
- Theo [quy tắc npm initializer](https://docs.npmjs.com/cli/v11/commands/npm-init/), `npm create @thanhphi/360-miniapp` chạy package `@thanhphi/create-360-miniapp`. Package này cần được publish trước khi tên registry hoạt động.
261
+ Màu guideline: `content1` là chữ nội dung, `textPrimary` là chữ trên primary, `background` là nền card và `base3` là nền màn.
38
262
 
39
- Tên package có scope `@thanhphi/create-360-miniapp` là tên đầy đủ trên npm; `create-360-miniapp` là tên executable trong `bin`. Package `create-360-miniapp` không có scope và package cùng tên dưới scope khác có thể do người khác sở hữu. Để chọn đúng generator, dùng tên có scope như ví dụ trên hoặc chỉ định tarball bằng `--package`. Scope cho phép cùng tên package dưới các namespace khác nhau; publish cần quyền trong scope tương ứng. Xem [npm scopes](https://docs.npmjs.com/about-scopes/).
263
+ Palette local nằm trong `src/themes/colors/light.ts` và `dark.ts`, được dùng để khởi tạo khi standalone. Cấu hình standalone nằm trong `src/themes/standalone.ts`.
40
264
 
41
- ## Options
265
+ **Tách phiên bản UI360 không tự tách theme Unistyles.** Host vẫn cần cung cấp các tên màu mà app sử dụng; factory không tự đổi `bg_1` thành `background`. Nếu cần palette riêng khi hosted, phải thiết kế phạm vi theme riêng; template không tự bật cơ chế đó.
42
266
 
43
- | Option | Mặc định |
44
- | --- | --- |
45
- | `--module` | Tên app, dùng cho `src/modules/<module>` |
46
- | `--container` | PascalCase tên app, dùng cho federation container |
47
- | `--app-code` | Tối đa 12 ký tự đầu tên app, viết hoa |
48
- | `--bundle-id` | `com.sungroup.<tên-app-bỏ-dấu-gạch>` |
49
- | `--display-name` | Tên container |
50
- | `--port` | `8085`; chọn port khác nếu có mini app đang chạy |
51
- | `--env` | `staging`; hỗ trợ `beta`, `prod` |
52
- | `--install` | Không cài dependencies nếu bỏ option |
53
- | `--native` | Không prebuild nếu bỏ option |
267
+ ## Đóng gói và triển khai
54
268
 
55
- ## Kiểm tra và đóng gói
269
+ Chọn env rồi tạo remote bundles:
56
270
 
57
271
  ```sh
58
- npm run test:create
59
- npm run pack:check
60
- # Hoặc chỉ package generator:
61
- npm pack --workspace @thanhphi/create-360-miniapp --pack-destination artifacts
272
+ npm run env:staging
273
+ npm run bundle:mf:android
274
+ npm run bundle:mf:ios
62
275
  ```
63
276
 
64
- Generator không có runtime dependency: tarball chứa CLI và toàn bộ template, không cần truy cập thư mục Event/Time-Management khi người dùng chạy CLI. Bộ kiểm tra source/generator có guard cho entry hosted–standalone, capability auth, token không persist, provider chuyển tiếp token và cách dùng shared UI. Kết quả kiểm thử app sinh của bản trước là snapshot lịch sử; đợt chuẩn hóa hiện tại chưa chạy lại build/test/typecheck hoặc tạo tarball mới.
277
+ Artifact cần triển khai nằm ở:
278
+
279
+ ```text
280
+ build/outputs/android/remotes/
281
+ build/outputs/ios/remotes/
282
+ ```
283
+
284
+ ZIP **toàn bộ nội dung** thư mục `remotes/` của từng platform, gồm container và các chunk; loại bỏ `*.map`. Không chỉ upload file container. Không dùng `build/generated/<platform>/index.bundle` làm remote artifact.
285
+
286
+ Các lệnh trên chỉ tạo JavaScript/assets, không tạo IPA/APK. Thêm native module mới vẫn cần binary host hỗ trợ trước khi deploy remote.
287
+
288
+ ### Azure DevOps
289
+
290
+ Chọn pipeline `pipelines/staging.yml`, `beta.yml` hoặc `production.yml`.
291
+
292
+ Mặc định `deploy: false`: pipeline cài dependencies, kiểm tra, bundle hai platform, đóng ZIP và lưu artifacts. Chỉ bật deployment sau khi cấu hình:
293
+
294
+ | Biến | Giá trị cần cung cấp |
295
+ | ------------------------------------ | -------------------------- |
296
+ | `MINIAPP_AZURE_SUBSCRIPTION` | Azure service connection |
297
+ | `MINIAPP_STORAGE_ACCOUNT` | Storage Account |
298
+ | `MINIAPP_STORAGE_CONTAINER` | Blob container |
299
+ | `DB_HOST`, `DB_LOGIN`, `DB_DATABASE` | Kết nối PostgreSQL catalog |
300
+ | `DB_PASSWORD` | Mật khẩu, đánh dấu secret |
301
+
302
+ Kiểm tra `appName`, `appId`, `appCode`, `envBuild`, `envFolder` và `miniAppVersion` trong YAML. Agent deployment cần Azure CLI, psql và quyền truy cập hạ tầng. Pipeline không tự tạo Storage Account, database/schema hay đăng ký registry trong code host.
65
303
 
66
- Để publish sau khi kiểm tra tarball và đăng nhập đúng tài khoản:
304
+ ### Trước khi phát hành
67
305
 
68
306
  ```sh
69
- npm publish --workspace @thanhphi/create-360-miniapp --access public
307
+ npm run typecheck
308
+ npm run lint
309
+ npm test
70
310
  ```
71
311
 
72
- Không publish root workspace. Đây là hướng dẫn phát hành; việc tạo source/tarball không tự publish lên npm.
312
+ Sau đó kiểm tra trên host: mở/đóng app, refresh token, đổi tài khoản, lỗi mạng, hiển thị theme, dialog/loading và remote bundle đúng platform. Xác nhận các dependencies native tương thích trước khi cập nhật catalog.
73
313
 
74
- Template 0.2.0 dùng contract tên màu của UI360 0.2.0, Unistyles 3.2.5/Nitro 0.36.5. Generator tạo đầy đủ mã hex trong src/themes/colors/light.ts và dark.ts để app tự quản lý. Một factory native tạo đủ màu/mode/spacing/radius/typography; ApplicationProvider chỉ chuyển tiếp token đang hoạt động, không tạo lại default hoặc scale. Màn hình/navigation dùng Unistyles color; shared UI giữ useUITheme bên trong package.
314
+ ## Xử lý vấn đề thường gặp
75
315
 
76
- Hosted không khởi tạo global, nhận toàn bộ native theme của host. Standalone cấu hình tại src/themes/standalone.ts; factory mặc định dùng DEVICE_WIDTH / 375 như host 360 và typography UI360 body 14/title 18 trước scale, không giữ body 16/title 22/scale 1 của template cũ. Scale áp dụng một lần tại factory, override số đã là dp; không qua props.uiTheme. Màn mẫu dùng Button/UIText trực tiếp và style lấy token theme, không có ActionButton chỉ để bọc shared Button.
316
+ | Hiện tượng | Kiểm tra |
317
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------ |
318
+ | Không tải được mini app | Container/module name, port hoặc URL resolver; artifact phải đủ chunk |
319
+ | Thiếu hostCapabilities | Cách host mở app và object capabilities truyền vào |
320
+ | Lỗi đọc spacing.xxl | Dùng provider/geometry của base mới; không lấy geometry từ theme host chưa có token này |
321
+ | Thiếu màu hoặc màu không đúng | Tên token Unistyles mà host cung cấp; không thêm mapping màu chỉ để sửa geometry |
322
+ | Standalone không đăng nhập được | Issuer, client ID, redirect URL, scopes và native build |
323
+ | API trả 401 sau đăng nhập | Audience/scopes hoặc flow token exchange của backend |
324
+ | Thêm native package nhưng remote vẫn lỗi | Binary host phải có module native tương ứng |
325
+ | Thay template nhưng app cũ không đổi | Generator chỉ tạo project; cập nhật app đã sinh theo từng thay đổi, không chạy đè lên thư mục cũ |
77
326
 
78
- App mới bắt buộc nhận HostCapabilities khi hosted: identity/token từ auth.getSnapshot/subscribe/refreshIfNeeded và đóng app qua closeMiniApp. Không có adapter props token/callback/sessionId cũ, không suy diễn identity từ access token. Exposed App thiếu capability báo host không tương thích, không tự chuyển sang OIDC; chỉ entry standalone chọn adapter đăng nhập độc lập. Host hiện tại đã cấp capability/session ID, nhưng cần cập nhật hoặc gate catalog cho các bản host cũ trước rollout. param_supper_app vẫn phục vụ dữ liệu mở app/thông báo.
327
+ ## License
79
328
 
80
- Chưa tách runtime riêng. Card trắng dùng background, nền màn dùng base3; textPrimary không còn là chữ nội dung. Cần đóng gói/phát hành generator mới để npx từ registry có template này; thay source chưa tự cập nhật tarball hoặc app đã sinh. Lần thay đổi source này không chạy install, build/test, typecheck/lint hoặc publish.
329
+ MIT.
package/package.json CHANGED
@@ -1,11 +1,26 @@
1
1
  {
2
2
  "name": "@thanhphi/create-360-miniapp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Create a MySGR Expo and Re.Pack mini app with modules, host auth and Azure pipelines",
5
5
  "license": "MIT",
6
- "bin": { "create-360-miniapp": "bin/create-360-miniapp.cjs" },
7
- "files": ["bin", "lib", "template", "README.md", "LICENSE"],
8
- "scripts": { "test": "node --test tests/*.test.cjs" },
9
- "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
10
- "engines": { "node": ">=20" }
6
+ "bin": {
7
+ "create-360-miniapp": "bin/create-360-miniapp.cjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib",
12
+ "template",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "test": "node --test tests/*.test.cjs"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public",
21
+ "registry": "https://registry.npmjs.org/"
22
+ },
23
+ "engines": {
24
+ "node": ">=20"
25
+ }
11
26
  }
@@ -1,234 +1,215 @@
1
1
  # __DISPLAY_NAME__
2
2
 
3
- Base TypeScript lấy Event làm nền, giữ thêm `constants`, `hooks`, `services` quen thuộc của Time Management; expose `__APP_NAME__/MiniApp` để MySGR tải qua Re.Pack, chạy độc lập bằng Expo là tùy chọn phục vụ dev. Đây là chuẩn cho app mới, không mang adapter host cũ hoặc quy tắc giao diện legacy của các app nguồn.
3
+ Mini app TypeScript chạy trong MySGR qua Re.Pack. Chạy độc lập bằng Expo là lựa chọn phục vụ phát triển và kiểm thử.
4
4
 
5
- ## Theme 360 / UI360 0.2.0
6
-
7
- - Mã hex nằm ngay trong src/themes/colors/light.ts và dark.ts của app. @thanhphi/ui360 chỉ chia sẻ contract tên màu/component/factory, không sở hữu palette thương hiệu.
8
- - src/themes/index.ts có một factory tạo đủ màu/mode, spacing, radius và typography; chỉ khai báo giá trị/hàm khi import, không tự cấu hình runtime. Hosted src/App.tsx không import setup global.
9
- - src/themes/standalone.ts chọn mode/override phục vụ dev và gọi configureStandaloneTheme từ factory đó. StandaloneApp và bootstrap standalone import file này sau ranh giới federation bootstrap.
10
- - ApplicationProvider đọc đầy đủ token đang hoạt động từ Unistyles rồi chuyển tiếp type → mode, color → colors cùng spacing/radius/typography vào UIThemeProvider. Không tạo bộ default thứ hai hoặc scale lại ở provider; không dùng props.uiTheme để override riêng shared UI.
11
- - Navigation và màn hình thuộc app dùng useUnistyles để lấy color/spacing/typography; style riêng tiếp tục StyleSheet.create(({ color }) => (...)), xem ProfileCard. useUITheme nằm bên trong thư viện shared.
12
- - Factory mặc định dùng scale DEVICE_WIDTH / 375 như host 360, với typography chuẩn UI360 (body 14/title 18 trước scale). Scale chỉ áp dụng một lần lúc dựng default geometry; override số trong standaloneThemeOptions đã là dp. Hosted nhận đầy đủ token của host, không ép default standalone lên host hoặc scale lại ở provider/màn hình.
13
- - Màn mẫu dùng Button/UIText từ shared, spacing/radius/typography từ theme. Không tạo ActionButton chỉ để bọc Button hoặc ép một chuẩn chiều cao khác.
14
- - Chưa tách runtime riêng. Palette local phục vụ khởi tạo mặc định/standalone, không tự cô lập global Unistyles. Muốn đổi màu riêng toàn mini app khi hosted cần áp dụng theme đồng bộ cho native và shared theo phạm vi app.
15
- - content1 là chữ nội dung; textPrimary là chữ trên nút primary. base3 là nền màn, base2 là nền hồng có chủ ý; card trắng dùng background.
16
- - Native host cần Unistyles 3.2.5/Nitro 0.36.5 và UI360 0.2.0. Base khai báo expo-image vì đây là peer của UI360. Cài đặt lại dependencies sau khi publisher phát hành package tương ứng.
5
+ ## Thông tin project
17
6
 
18
7
  | Cấu hình | Giá trị |
19
8
  | --- | --- |
20
- | Package | `__PACKAGE_NAME__` |
21
- | Federation container | `__APP_NAME__` |
22
- | App ID / code trong catalog | `__APP_ID__` / `__APP_CODE__` |
23
- | Module nghiệp vụ | `__MODULE__` |
24
- | Re.Pack port | `__PORT__` |
25
- | Native bundle ID | `__BUNDLE_ID__` |
9
+ | npm package / thư mục | `__PACKAGE_NAME__` |
10
+ | Container Re.Pack | `__APP_NAME__` |
11
+ | Module được expose | `__APP_NAME__/MiniApp` |
12
+ | App ID trong catalog | `__APP_ID__` |
13
+ | App code | `__APP_CODE__` |
14
+ | Module nghiệp vụ | `src/modules/__MODULE__` |
15
+ | Port local | `__PORT__` |
16
+ | Bundle ID standalone | `__BUNDLE_ID__` |
26
17
 
27
- ## Tạo project khác từ base
18
+ Yêu cầu Node.js >=20.19.4, khuyến nghị Node 22 LTS. Bộ phiên bản được khai báo trong package.json: Expo 55, React Native 0.83.6, React 19.2.0, UI360 0.2.0, Unistyles 3.2.5 và Nitro 0.36.5. Không tự nâng riêng React/native dependencies nếu host chưa hỗ trợ phiên bản đó.
28
19
 
29
- Sau khi `@thanhphi/create-360-miniapp` được publish lên registry:
20
+ ## 1. Cài dependencies và chọn môi trường
30
21
 
31
22
  ```sh
32
- npx @thanhphi/create-360-miniapp@latest demo --module sample --port 8085 --bundle-id com.sungroup.demo
33
- npm create @thanhphi/360-miniapp@latest demo -- --module sample --port 8085 --bundle-id com.sungroup.demo
23
+ npm install
24
+ npm run env:staging
34
25
  ```
35
26
 
36
- `npm create @thanhphi/360-miniapp` ánh xạ tới `@thanhphi/create-360-miniapp` theo [npm init/create](https://docs.npmjs.com/cli/v11/commands/npm-init/). Khi chưa publish, chạy từ thư mục `mysgr-shared`:
27
+ Commit package-lock.json sau lần cài đầu. Các lần sau dùng npm ci.
37
28
 
38
- ```sh
39
- node packages/create-360-miniapp/bin/create-360-miniapp.cjs demo --module sample --port 8085 --bundle-id com.sungroup.demo
40
- ```
29
+ Sửa env/staging.json, env/beta.json và env/prod.json:
30
+
31
+ - BASE_URL: URL gốc của backend.
32
+ - API_TIMEOUT_MS: timeout của request.
33
+ - AUTH: cấu hình OIDC khi chạy độc lập.
34
+
35
+ Script env sinh env/env-config.ts; không sửa file sinh bằng tay. Đổi môi trường cần restart dev server.
41
36
 
42
- CLI mặc định chỉ sinh file. Thêm `--install` để chạy npm install, thêm `--native` để cài dependencies rồi prebuild native. CLI không khởi tạo git repository.
37
+ Tất cả giá trị env đi vào bundle. Không lưu client secret, mật khẩu hoặc token trong JSON. Secret deployment phải đặt trong CI.
43
38
 
44
- ## Chạy lần đầu
39
+ ## 2. Chạy trong MySGR
45
40
 
46
- Dùng Node/npm theo `package.json`, Xcode/CocoaPods để chạy iOS, Android SDK/JDK để chạy Android. Cấu hình env trước khi tạo native build.
41
+ **Hosted** nghĩa là mở mini app bên trong MySGR. Host cung cấp phiên đăng nhập và các native modules.
47
42
 
48
43
  ```sh
49
- npm install
50
44
  npm run env:staging
51
- npx expo prebuild
52
- npm run ios:staging
53
- # hoặc
54
- npm run android:staging
45
+ npm run start:mf
55
46
  ```
56
47
 
57
- `npx expo prebuild` tạo project `ios/` và `android/` theo `__BUNDLE_ID__`; source/env/pipeline giữ nguyên. AppAuth và các native dependencies cần development build. Xem [Expo SDK 55](https://docs.expo.dev/versions/v55.0.0/) và [Expo prebuild](https://docs.expo.dev/workflow/continuous-native-generation/).
48
+ Đăng ký app phía host/catalog theo bảng đầu tài liệu, trỏ resolver tới dev server port __PORT__, sau đó mở app từ MySGR. CLI không tự sửa repository host hoặc dữ liệu backend.
58
49
 
59
- Commit lockfile được sinh sau `npm install` để CI có thể dùng `npm ci`. Lần sau dùng:
50
+ Thiết bị thật cần IP LAN của máy phát triển:
60
51
 
61
52
  ```sh
62
- npm run start:staging
63
- npm run start:beta
64
- npm run start:prod
65
- npm run typecheck
66
- npm run lint
67
- npm test
53
+ npm run start:mf:device
68
54
  ```
69
55
 
70
- ## Thư mục và nơi thêm code
56
+ Nếu cần reset cache dev server:
71
57
 
72
- ```text
73
- assets/ # static assets cấp app
74
- declare/ # ambient declarations cho assets
75
- env/ # JSON các flavor và env-config.ts sinh ra
76
- pipelines/templates/ # Azure build/ZIP, upload/register
77
- scripts/ # sync env và build remote
78
- src/
79
- assets/ # assets nhập từ source
80
- common/ # mini-app runtime và host adapter
81
- config/ # cấu hình dùng trong app
82
- constants/ # hằng số cấp app
83
- hooks/ # hook dùng chung
84
- library/
85
- components/ # UI dùng lại trong app
86
- networking/ # SDK HTTP client
87
- utils/ # helper không thuộc nghiệp vụ
88
- modules/
89
- auth/ # màn/login service/redux/navigation auth
90
- __MODULE__/ # screens/navigation/redux/services/types nghiệp vụ
91
- navigation/ # root navigator
92
- redux/ # store factory, reducers và typed hooks
93
- services/ # application composition, runtime/store/cache/client
94
- themes/ # theme của app
95
- types/ # contract app, assets và kiểu dùng chung
96
- App.tsx
97
- bootstrap.tsx
58
+ ```sh
59
+ npm run start:mf:reset
98
60
  ```
99
61
 
100
- Thêm màn/service/state trong `src/modules/__MODULE__`; endpoint thuộc service của module. `src/services/application.ts` chỉ nối các thành phần cấp app. `ios/`/`android/` được Expo prebuild sinh khi chạy độc lập. Không cần native build riêng để chỉ tạo remote bundle.
62
+ Host phải truyền hostCapabilities theo miniapp-sdk. Base đã nối sẵn auth.getSnapshot/subscribe/refreshIfNeeded và closeMiniApp; màn hình/service không cần tự lưu token hoặc đăng nhập lại.
101
63
 
102
- ## Env
64
+ - Host quản lý refresh token và persistence.
65
+ - Mini app không persist token do host cấp.
66
+ - Đổi tài khoản làm đổi session và xóa dữ liệu phiên cũ.
67
+ - Refresh access token trong cùng session không reset dữ liệu nghiệp vụ.
68
+ - param_supper_app dùng cho dữ liệu mở app/thông báo, không thay cho auth.
69
+ - Thiếu hostCapabilities là lỗi cấu hình tích hợp; không tự chuyển sang standalone login.
103
70
 
104
- Sửa `env/staging.json`, `env/beta.json`, `env/prod.json`. Script sinh `env/env-config.ts`; không sửa file sinh trực tiếp. Các lệnh có hậu tố môi trường sync JSON trước khi start/build.
71
+ ## 3. Chạy độc lập để phát triển
105
72
 
106
- ```json
107
- {
108
- "ENV": "staging",
109
- "BASE_URL": "https://api.example.com",
110
- "API_TIMEOUT_MS": 15000,
111
- "AUTH": {
112
- "issuer": "https://identity.example.com",
113
- "clientId": "your-native-client-id",
114
- "redirectUrl": "__BUNDLE_ID__.auth://oauth/redirect",
115
- "scopes": ["openid", "profile", "offline_access"]
116
- }
117
- }
118
- ```
73
+ **Standalone** nghĩa là chạy app riêng, không nằm trong MySGR. Cần Xcode/CocoaPods cho iOS hoặc Android SDK/JDK cho Android; template không chạy bằng Expo Go.
119
74
 
120
- Issuer, client ID, scope và URL là placeholder; thay bằng cấu hình public của provider/API. Mọi giá trị env này được đưa vào bundle. Không đặt client secret, private key, mật khẩu hay token trong JSON. Secret CI dùng secret variables/service connections.
75
+ Đăng ký OAuth native client với provider, cấu hình issuer/clientId/scopes và redirect URL trong env. Redirect mặc định có dạng __BUNDLE_ID__.auth://oauth/redirect.
121
76
 
122
- Các script gọi trực tiếp Expo/Re.Pack như Event và Time Management. Chọn môi trường bằng `env:staging`, `env:beta` hoặc `env:prod` trước khi chạy Re.Pack hoặc bundle. Các lệnh `start:<flavor>`, `ios:<flavor>` và `android:<flavor>` chọn env rồi gọi Expo trực tiếp. Đổi env cần restart Metro/Re.Pack.
77
+ ```sh
78
+ npm run env:staging
79
+ npx expo prebuild
80
+ npm run ios:staging
81
+ # Hoặc npm run android:staging
82
+ ```
123
83
 
124
- Khi đổi redirect URL, cập nhật cấu hình provider và chạy lại prebuild/build để native redirect được áp dụng. App config đọc redirect cho AppAuth plugin. Xem [AppAuth Expo config plugin](https://github.com/FormidableLabs/react-native-app-auth/blob/main/docs/docs/usage/expo-setup.md).
84
+ Đã có development build thì dùng npm run start:staging để chạy Metro. Có các lệnh tương ứng với beta và prod.
125
85
 
126
- ## Auth và token khi chạy trong MySGR
86
+ Token standalone mặc định chỉ ở bộ nhớ; khởi động lại sẽ mất phiên. Scopes OIDC mẫu không tự cấp quyền API. Nếu backend yêu cầu token exchange, bổ sung flow đó trong service auth. Không dùng ID token thay API access token.
127
87
 
128
- Hosted bắt buộc dùng `HostCapabilities`. Host hiện tại đã cấp capability/session ID chung; template không nhận các prop token/callback/session ID legacy và không có adapter tự suy diễn identity từ access token.
88
+ Đổi redirect URL hoặc thêm native dependency cần cập nhật native build.
129
89
 
130
- ```tsx
131
- import MiniApp from './src/App';
132
- import type { HostCapabilities } from '@thanhphi/miniapp-sdk';
133
-
134
- const capabilities: HostCapabilities = {
135
- auth: {
136
- getSnapshot: () => hostAuth.getSnapshot(),
137
- refreshIfNeeded: input => hostAuth.refreshIfNeeded(input),
138
- subscribe: listener => hostAuth.subscribe(listener),
139
- },
140
- closeMiniApp: () => navigation.goBack(),
141
- };
142
-
143
- // Phía host: `hostAuth` và `navigation` là service/navigation thật.
144
- // Giữ object capabilities ổn định (ví dụ useMemo trong component host).
145
- <MiniApp
146
- hostCapabilities={capabilities}
147
- param_supper_app={{ notifications: notification }}
148
- />;
149
- ```
90
+ ## 4. Nơi thêm code
150
91
 
151
- Host cấp session ID mới khi login/logout/đổi tài khoản, giữ session ID khi refresh token; cập nhật snapshot trước khi phát subscription. Host sở hữu refresh token, persistence và coordinator refresh chung. Không đưa refresh token vào props mini app.
92
+ ```text
93
+ env/ Cấu hình staging, beta, prod
94
+ pipelines/ CI và deployment Azure
95
+ src/
96
+ common/mini-app/ Provider, runtime, host integration
97
+ library/networking/ HTTP client
98
+ modules/auth/ Phiên đăng nhập
99
+ modules/__MODULE__/
100
+ screens/ Màn hình và hooks
101
+ navigation/ Routes của module
102
+ services/ Endpoints và mapping response
103
+ redux/ State nghiệp vụ
104
+ navigation/ Root navigator
105
+ services/application.ts Khởi tạo client/store/QueryClient
106
+ themes/ Palette, geometry, standalone setup
107
+ ```
152
108
 
153
- Mini app đọc identity/token bằng `auth.getSnapshot()`, theo dõi thay đổi bằng `auth.subscribe()` và nhờ host refresh qua `auth.refreshIfNeeded()`; đóng app qua `closeMiniApp()`. `param_supper_app` vẫn là dữ liệu mở app/thông báo, không dùng để cấp token hay nhận diện phiên. Một lần refresh chỉ đổi access token không được reset dữ liệu nghiệp vụ nếu session ID không đổi.
109
+ Đặt tính năng mới trong src/modules/__MODULE__, đăng ký route trong navigator của module. Không đặt endpoints trong root provider.
154
110
 
155
- Exposed `App` luôn là hosted: thiếu capability thì báo cấu hình host không tương thích, không tự chuyển sang màn OIDC standalone. Chỉ entry standalone chủ động chọn adapter OIDC. Host đã phát hành nhưng chưa có capability không tương thích template mới; phải cập nhật binary/host hoặc chặn phiên bản qua catalog trước khi deploy remote này. Base không thay host registry; thêm app ID `__APP_ID__`, container `__APP_NAME__`, import `__APP_NAME__/MiniApp` và URL/port `__PORT__` trong registry/resolver của MySGR theo môi trường triển khai.
111
+ ### API và dữ liệu mẫu
156
112
 
157
- HTTP dùng `@thanhphi/miniapp-sdk`: tự gắn `Authorization: Bearer <accessToken>`, refresh và retry tối đa một lần sau HTTP 401. Client nhận route tương đối theo `BASE_URL`; service module tự unwrap response envelope. Demo gọi đường dẫn `/me`; chỉnh `PROFILE_PATH` trong service của module cho backend thật. Nếu backend báo token hết hạn trong HTTP 200 envelope, cấu hình `isAuthExpired` trong `src/library/networking/client.ts` theo response contract của API.
113
+ Màn Home chỉ gọi API sau khi nhấn **Tải hồ sơ**. Chỉnh PROFILE_PATH và mapping response trong src/modules/__MODULE__/services/profile.service.ts theo backend thật; endpoint mẫu là /me.
158
114
 
159
- Service dùng client được tạo theo runtime, chẳng hạn:
115
+ Lấy client qua useApplicationServices() trong component/hook, rồi truyền client vào service:
160
116
 
161
117
  ```ts
162
- import type { ApiClient } from './src/library/networking/client';
118
+ import type { ApiClient } from '../../../library/networking/client';
163
119
 
164
- export async function loadItem(api: ApiClient, itemId: string) {
165
- return api.get<{ id: string; name: string }>(
166
- `/items/${encodeURIComponent(itemId)}`,
167
- );
120
+ export function getItems(api: ApiClient, signal?: AbortSignal) {
121
+ return api.get<{ id: string; name: string }[]>('/items', { signal });
168
122
  }
169
123
  ```
170
124
 
171
- Runtime/store/client/QueryClient có vòng đời theo mount. Identity đổi reset auth/state và cancel/clear queries của phiên cũ; unmount hủy subscriptions, cancel/clear cache và dispose client/runtime. `AuthError.kind` phân biệt `expired`, `session-changed`, `unavailable`; lỗi mạng tạm thời không được coi như logout.
125
+ Ví dụ này giả định response là array trực tiếp; unwrap envelope trong service nếu backend trả cấu trúc khác. HTTP client tự gắn access token và refresh/retry tối đa một lần sau HTTP 401.
172
126
 
173
- ## Auth standalone
127
+ Mẫu useHome() có QueryClient, request cancellation và session trong query key; dùng làm mẫu cho dữ liệu cá nhân. Nếu backend báo hết hạn token trong HTTP 200, cấu hình isAuthExpired tại src/library/networking/client.ts.
174
128
 
175
- Chỉ khi chạy entry standalone, app dùng adapter riêng và có màn đăng nhập OIDC qua `react-native-app-auth`. Không phát hiện standalone bằng việc thiếu props host. Adapter standalone đóng vai host, giữ access/refresh token trong bộ nhớ, dùng coordinator để chia sẻ refresh và kiểm tra identity trước khi lưu kết quả. Đóng app hoặc reset runtime sẽ mất phiên này; muốn lưu phiên cần thêm secure native storage. Không persist token do host cấp vào Redux/storage của mini app.
129
+ ## 5. Màu và geometry
176
130
 
177
- Đăng ký native OAuth client và redirect `__BUNDLE_ID__.auth://oauth/redirect` với provider; chọn scopes cho API audience, rồi thay env của từng flavor. `openid/profile/offline_access` không tự cấp quyền gọi API của bạn. Backend MySGR có thể yêu cầu đổi Azure token sang token riêng: thêm flow exchange trong service auth, với endpoint/body đúng backend. Base không giả định endpoint exchange, không dùng ID token làm API access token. Login và profile demo chỉ có dữ liệu thật sau khi provider/API được cấu hình.
131
+ Unistyles phục vụ style của app; UIThemeProvider phục vụ component từ UI360.
178
132
 
179
- ## Re.Pack và build remote
133
+ - Hosted giữ nguyên mode và color từ runtime host.
134
+ - UI360 được bundle trong mini app, không share implementation với host.
135
+ - Geometry mặc định được tạo bằng factory UI360 tại src/themes/ui-geometry.ts. Màn mẫu và provider dùng cùng spacing/radius/typography, không yêu cầu host có những trường này.
136
+ - Palette local tại src/themes/colors/light.ts và dark.ts dùng để cấu hình standalone.
137
+ - Không mapping key màu legacy, không trộn palette và không ghi đè theme global khi hosted.
138
+ - Tách phiên bản UI360 không tự tách runtime Unistyles. Host vẫn cần có tên token màu mà app sử dụng.
180
139
 
181
- ```sh
182
- npm run env:staging
183
- # hoặc env:beta / env:prod
184
- npm run start:mf
140
+ Ví dụ style:
185
141
 
186
- # Thiết bị thật hoặc reset cache
187
- npm run start:mf:device
188
- npm run start:mf:reset
142
+ ```tsx
143
+ import { StyleSheet } from 'react-native-unistyles';
144
+ import { uiGeometry } from './themes/ui-geometry';
145
+
146
+ const { spacing, radius } = uiGeometry;
147
+ const styles = StyleSheet.create(({ color }) => ({
148
+ card: {
149
+ backgroundColor: color.background,
150
+ padding: spacing.xxl,
151
+ borderRadius: radius.lg,
152
+ },
153
+ }));
154
+ ```
155
+
156
+ Điều chỉnh import theo vị trí file. content1 là chữ nội dung, textPrimary là chữ trên primary; background là nền card, base3 là nền màn. Geometry đã scale theo thiết kế rộng 375, không nhân scale lại.
189
157
 
190
- # Bundle dùng env đã chọn
158
+ Để đổi geometry, cấu hình tại ui-geometry.ts. Override số truyền vào factory là dp và không bị scale lại. createUITheme/create360Theme chỉ tạo object; chỉ entry standalone mới gọi StyleSheet.configure qua src/themes/standalone.ts.
159
+
160
+ ## 6. Đóng gói remote
161
+
162
+ ```sh
163
+ npm run env:staging
191
164
  npm run bundle:mf:android
192
165
  npm run bundle:mf:ios
193
166
  ```
194
167
 
195
- Dev server chạy port `__PORT__`; resolver của host phải trỏ đúng server đó. Với thiết bị thật, dùng IP LAN của máy và bảo đảm thiết bị truy cập được dev server. Host phải có native modules và singleton versions tương thích với dependencies remote; thêm native package vào mini app cần cập nhật binary host trước khi sử dụng.
196
-
197
- Output deploy:
168
+ Deploy các thư mục:
198
169
 
199
170
  ```text
200
- build/outputs/android/remotes/__APP_NAME__.container.js.bundle
201
- build/outputs/ios/remotes/__APP_NAME__.container.js.bundle
202
- build/outputs/<platform>/remotes/*.chunk.bundle
171
+ build/outputs/android/remotes/
172
+ build/outputs/ios/remotes/
203
173
  ```
204
174
 
205
- ZIP toàn bộ nội dung `remotes`, bỏ `*.map`; container cần các chunk đi kèm. `build/generated/<platform>/index.bundle` là entry standalone và không upload cho host. Script clean output từng platform trước bundle để không đưa chunk cũ vào archive. Pipeline giữ SHA-1 vì catalog/host hiện dùng checksum này.
175
+ ZIP toàn bộ nội dung remotes của mỗi platform, gồm __APP_NAME__.container.js.bundle và các chunk, bỏ các file .map. Không chỉ upload container. Không dùng build/generated/<platform>/index.bundle để triển khai mini app.
176
+
177
+ Các lệnh này tạo JavaScript/assets, không tạo IPA/APK. Các native dependencies vẫn phải có trong binary host.
206
178
 
207
- ## Pipeline Azure DevOps
179
+ ## 7. CI và deployment
208
180
 
209
- Các YAML trong `pipelines/` chọn flavor/branch và dùng chung `pipelines/templates`. Mặc định chỉ build, đóng ZIP và publish artifacts. Bật deployment sau khi cấu hình Azure/DB thật; xem các parameters/variables trong YAML, không đưa secret vào git.
181
+ Azure DevOps có pipelines/staging.yml, beta.yml và production.yml. Mặc định deploy=false: cài dependencies, kiểm tra, build remote, ZIP và lưu artifacts.
210
182
 
211
- Pipeline có `parameters.deploy: false`, `parameters.agentPool` mặc định Ubuntu và `parameters.nodeVersion`. Các flavor là `pipelines/staging.yml`, `beta.yml`, `production.yml`. Khi `deploy: true`, cấu hình các biến sau trong Azure Pipeline/variable group:
183
+ Muốn bật deploy, cấu hình service connection/variable group và các biến:
212
184
 
213
185
  | Biến | Mục đích |
214
186
  | --- | --- |
215
- | `MINIAPP_AZURE_SUBSCRIPTION` | Azure service connection có quyền upload Blob |
216
- | `MINIAPP_STORAGE_ACCOUNT` | Tên Storage Account |
217
- | `MINIAPP_STORAGE_CONTAINER` | Blob container chứa ZIP |
218
- | `DB_HOST`, `DB_LOGIN`, `DB_DATABASE` | PostgreSQL host/user/database của mini app catalog |
219
- | `DB_PASSWORD` | Mật khẩu PostgreSQL, đánh dấu secret |
220
- | `appName`, `appId`, `appCode` | Container `__APP_NAME__`, catalog ID `__APP_ID__`, code `__APP_CODE__` |
221
- | `envBuild`, `envFolder` | Flavor env và folder Blob của môi trường |
222
- | `miniAppVersion` | Version artifact, mặc định `$(Build.BuildNumber)` |
223
-
224
- Pipeline cần agent có Node/npm, `zip`, `sha1sum`; deployment cần Azure CLI và `psql`, truy cập mạng đến PostgreSQL. DB kết nối SSL (`PGSSLMODE=require`); schema cần bảng `mini_app` và `mini_app_version` phù hợp host. ZIP nằm ở `appId/envFolder/platform/version.zip` trong Blob. Metadata được ghi sau upload thành công. Version nên duy nhất vì upload không ghi đè archive đã có. Cài lần đầu có thể dùng npm install khi chưa có lockfile; các lần sau dùng npm ci từ lockfile đã commit.
187
+ | MINIAPP_AZURE_SUBSCRIPTION | Azure service connection |
188
+ | MINIAPP_STORAGE_ACCOUNT | Storage Account |
189
+ | MINIAPP_STORAGE_CONTAINER | Blob container |
190
+ | DB_HOST, DB_LOGIN, DB_DATABASE | PostgreSQL catalog |
191
+ | DB_PASSWORD | Mật khẩu, đánh dấu secret |
225
192
 
226
- Build cả Android/iOS remote chỉ tạo JavaScript artifacts, không tạo IPA/APK. Có thể chạy build-only CI trước khi cấu hình deployment.
193
+ Kiểm tra appName/appId/appCode, envBuild/envFolder và miniAppVersion trong YAML. Agent deploy cần Azure CLI, psql và quyền mạng tới hạ tầng. Pipeline không tự tạo hạ tầng, schema database hoặc đăng ký app trong source host.
227
194
 
228
- Base chưa tự đăng ký app trên hạ tầng và không tự publish npm package. Provider login/native redirect, native binary, tích hợp host và deployment vào Azure thật cần được kiểm chứng trong môi trường của app.
195
+ ## 8. Kiểm tra trước khi phát hành
229
196
 
230
- ## Kiểm tra trước khi phát hành
197
+ ```sh
198
+ npm run typecheck
199
+ npm run lint
200
+ npm test
201
+ ```
231
202
 
232
- Source generator có guard kiểm tra entry hosted không kéo standalone setup, chỉ nhận capability auth, không persist token hosted, provider không tạo bộ token thứ hai và màn mẫu dùng shared UI/theme token thay vì wrapper/alias legacy. Chạy `npm run test:create` từ workspace để kiểm tra source generator; các guard này không thay thế thử runtime/native trên host.
203
+ Sau đó thử trên host: mở/đóng app, refresh token, đổi tài khoản, lỗi mạng, dialog/loading, theme và artifact hai platform.
233
204
 
234
- Đợt chuẩn hóa source này không chạy build/test, install, typecheck/lint, đóng gói hoặc publish. App đã sinh, tarball cũ và package trên registry không tự cập nhật theo template nguồn.
205
+ | Vấn đề | Kiểm tra |
206
+ | --- | --- |
207
+ | Không tải remote | Container name, module name, URL/port và đủ chunk |
208
+ | Thiếu hostCapabilities | Props/cách host mở mini app |
209
+ | Không có spacing.xxl | Dùng uiGeometry/UIThemeProvider, không đọc geometry thiếu từ host |
210
+ | Màu thiếu hoặc không đúng | Token màu trong theme Unistyles đang hoạt động |
211
+ | Standalone không đăng nhập | OAuth client, redirect URL, scopes, native build |
212
+ | API vẫn trả 401 | API audience/scopes hoặc token exchange |
213
+ | Lỗi native module | Binary host có module/phiên bản cần dùng chưa |
214
+
215
+ Hướng dẫn tạo project mới và các CLI options: [@thanhphi/create-360-miniapp trên npm](https://www.npmjs.com/package/@thanhphi/create-360-miniapp).
@@ -8,7 +8,8 @@ const aliases = Object.fromEntries(Object.entries(require('./aliases.cjs')).map(
8
8
  const sharedNames = [
9
9
  'react', 'react-native', 'expo', 'expo-image',
10
10
  'react-native-unistyles', 'react-native-nitro-modules',
11
- '@thanhphi/miniapp-sdk', '@thanhphi/ui360',
11
+ // UI360 stays in this remote bundle, independent of the host's UI version.
12
+ '@thanhphi/miniapp-sdk',
12
13
  '@react-navigation/native', '@react-navigation/native-stack',
13
14
  'react-native-gesture-handler', 'react-native-safe-area-context',
14
15
  'react-native-screens', 'react-native-svg', 'react-native-calendars',
@@ -16,7 +17,6 @@ const sharedNames = [
16
17
  ];
17
18
  const shared = Object.fromEntries(sharedNames.map(name => [name, {
18
19
  singleton: true,
19
- strictVersion: name === '@thanhphi/ui360',
20
20
  eager: false,
21
21
  packageName: name,
22
22
  requiredVersion: pkg.dependencies[name],
@@ -3,8 +3,9 @@ import { Provider } from 'react-redux';
3
3
  import { QueryClientProvider } from '@tanstack/react-query';
4
4
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
5
5
  import { SafeAreaProvider } from 'react-native-safe-area-context';
6
- import { UIThemeProvider } from '@thanhphi/ui360';
6
+ import { createUITheme, UIThemeProvider } from '@thanhphi/ui360';
7
7
  import { useUnistyles } from 'react-native-unistyles';
8
+ import { uiGeometry } from '../../themes/ui-geometry';
8
9
  import type {} from '../../themes';
9
10
  import type { ApplicationServices } from '../../services/application';
10
11
 
@@ -21,12 +22,12 @@ export function ApplicationProvider({
21
22
  children,
22
23
  }: React.PropsWithChildren<{ services: ApplicationServices }>) {
23
24
  const {
24
- theme: { type, color, spacing, radius, typography },
25
+ theme: { type, color },
25
26
  } = useUnistyles();
26
- // Read active tokens here so Unistyles tracks changes; never rebuild defaults.
27
+ // Host supplies colors/mode; local UI360 supplies geometry without global setup.
27
28
  const uiTheme = React.useMemo(
28
- () => ({ mode: type, colors: color, spacing, radius, typography }),
29
- [type, color, spacing, radius, typography],
29
+ () => createUITheme({ mode: type, colors: color, ...uiGeometry }),
30
+ [type, color],
30
31
  );
31
32
  return (
32
33
  <GestureHandlerRootView style={{ flex: 1 }}>
@@ -3,6 +3,7 @@ import { UIText } from '@thanhphi/ui360';
3
3
  import { View } from 'react-native';
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
5
  import type {} from '../../../themes';
6
+ import { uiGeometry } from '../../../themes/ui-geometry';
6
7
  import type { Profile } from '../services/profile.service';
7
8
 
8
9
  export function ProfileCard({ profile }: { profile: Profile }) {
@@ -25,8 +26,8 @@ export function ProfileCard({ profile }: { profile: Profile }) {
25
26
  );
26
27
  }
27
28
 
28
- // Same guideline names as shared components; no per-component style hook.
29
- const styles = StyleSheet.create(({ color, spacing, radius }) => ({
29
+ const { spacing, radius } = uiGeometry;
30
+ const styles = StyleSheet.create(({ color }) => ({
30
31
  card: {
31
32
  padding: spacing.xxl,
32
33
  gap: spacing.sm,
@@ -5,14 +5,16 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
5
  import { StyleSheet, useUnistyles } from 'react-native-unistyles';
6
6
  import { errorMessage } from '../../../../library/networking/errors';
7
7
  import type {} from '../../../../themes';
8
+ import { uiGeometry } from '../../../../themes/ui-geometry';
8
9
  import { useAuth } from '../../../auth/hooks/use-auth';
9
10
  import { ProfileCard } from '../../components/ProfileCard';
10
11
  import { useHome } from './hooks/use-home';
11
12
 
12
13
  export default function HomeScreen() {
13
14
  const {
14
- theme: { color, spacing },
15
+ theme: { color },
15
16
  } = useUnistyles();
17
+ const { spacing } = uiGeometry;
16
18
  const query = useHome();
17
19
  const auth = useAuth();
18
20
  const insets = useSafeAreaInsets();
@@ -86,7 +88,8 @@ export default function HomeScreen() {
86
88
  );
87
89
  }
88
90
 
89
- const styles = StyleSheet.create(({ color, spacing }) => ({
91
+ const { spacing } = uiGeometry;
92
+ const styles = StyleSheet.create(({ color }) => ({
90
93
  content: {
91
94
  padding: spacing.xxl,
92
95
  gap: spacing.xxl,
@@ -4,12 +4,14 @@ import { ActivityIndicator, ScrollView, View } from 'react-native';
4
4
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
5
  import { StyleSheet, useUnistyles } from 'react-native-unistyles';
6
6
  import type {} from '../../../themes';
7
+ import { uiGeometry } from '../../../themes/ui-geometry';
7
8
  import { useAuth } from '../hooks/use-auth';
8
9
 
9
10
  export default function LoginScreen() {
10
11
  const {
11
- theme: { color, spacing },
12
+ theme: { color },
12
13
  } = useUnistyles();
14
+ const { spacing } = uiGeometry;
13
15
  const auth = useAuth();
14
16
  const insets = useSafeAreaInsets();
15
17
  return (
@@ -64,7 +66,8 @@ export default function LoginScreen() {
64
66
  );
65
67
  }
66
68
 
67
- const styles = StyleSheet.create(({ color, spacing }) => ({
69
+ const { spacing } = uiGeometry;
70
+ const styles = StyleSheet.create(({ color }) => ({
68
71
  content: {
69
72
  padding: spacing.xxl,
70
73
  gap: spacing.xxl,
@@ -0,0 +1,12 @@
1
+ import { createUITheme } from '@thanhphi/ui360';
2
+ import { ratioW } from '../library/utils/responsive';
3
+ import { lightColors } from './colors/light';
4
+
5
+ // Only export geometry. The palette here never replaces the active host colors.
6
+ // Pure factory: no Unistyles registration, no dependency on host geometry.
7
+ const { spacing, radius, typography } = createUITheme({
8
+ colors: lightColors,
9
+ scale: ratioW(1),
10
+ });
11
+
12
+ export const uiGeometry = { spacing, radius, typography };