@microi.net/cli 5.8.5 → 5.8.6

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.
Files changed (211) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.mcp.json +1 -1
  5. package/.workbuddy-plugin/marketplace.json +2 -2
  6. package/.workbuddy-plugin/plugin.json +1 -1
  7. package/LICENSE +21 -21
  8. package/README.md +71 -71
  9. package/assets/build-meta.json +6 -6
  10. package/assets/feature-matrix.json +138 -138
  11. package/assets/logo.svg +4 -4
  12. package/cordis.patch.yml +1 -1
  13. package/package.json +1 -1
  14. package/scripts/codex-marketplace.json +20 -20
  15. package/scripts/mcp-codex-stdio-adapter.js +189 -189
  16. package/scripts/mcp-trae-windows-launcher.cmd +21 -21
  17. package/scripts/microi-cli-mcp.js +7 -7
  18. package/scripts/microi-cli.js +66 -65
  19. package/scripts/microi-codex-broker.js +450 -450
  20. package/scripts/microi-codex-router.js +618 -618
  21. package/scripts/microi-skills.meta.json +384 -384
  22. package/skills/.microi-skills-version.json +2 -2
  23. package/skills/.progressive-disclosure-manifest.json +3566 -3566
  24. package/skills/README.md +287 -287
  25. package/skills/ai-engine/SKILL.md +269 -265
  26. package/skills/ai-engine/agents/openai.yaml +4 -4
  27. package/skills/ai-engine/references/ai-employees.md +48 -48
  28. package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
  29. package/skills/ai-platform-governance/SKILL.md +177 -177
  30. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  31. package/skills/app-store/SKILL.md +525 -525
  32. package/skills/app-store/agents/openai.yaml +4 -4
  33. package/skills/business-blueprint/SKILL.md +193 -193
  34. package/skills/datasource-engine/SKILL.md +93 -93
  35. package/skills/datasource-engine/agents/openai.yaml +4 -4
  36. package/skills/dos-orm/SKILL.md +97 -97
  37. package/skills/dos-orm/references/api-reference.md +229 -229
  38. package/skills/email-engine/SKILL.md +81 -81
  39. package/skills/email-engine/references/v8-email.md +34 -34
  40. package/skills/job-engine/SKILL.md +176 -176
  41. package/skills/job-engine/agents/openai.yaml +4 -4
  42. package/skills/message-notification/SKILL.md +156 -156
  43. package/skills/message-notification/agents/openai.yaml +5 -5
  44. package/skills/message-notification/references/contracts.md +102 -102
  45. package/skills/microi/SKILL.md +14 -14
  46. package/skills/microi-ai-app-auth.js +652 -652
  47. package/skills/microi-ai-application/SKILL.md +115 -115
  48. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  49. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  50. package/skills/microi-client-frontend/SKILL.md +244 -244
  51. package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +144 -144
  52. package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +196 -196
  53. package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +167 -167
  54. package/skills/microi-codex/SKILL.md +102 -102
  55. package/skills/microi-codex-installer/SKILL.md +231 -231
  56. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  57. package/skills/microi-datasource-mapping/SKILL.md +122 -122
  58. package/skills/microi-db-schema/SKILL.md +175 -175
  59. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  60. package/skills/microi-db-schema/references/core-tables.md +695 -695
  61. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  62. package/skills/microi-db-schema/references/schema-overview.md +202 -202
  63. package/skills/microi-db-schema/references/schema.md +646 -646
  64. package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
  65. package/skills/microi-deployment/SKILL.md +221 -221
  66. package/skills/microi-deployment/references/deployment-matrix.md +109 -109
  67. package/skills/microi-docs-coverage/SKILL.md +133 -133
  68. package/skills/microi-docs-coverage/references/capability-map.md +91 -91
  69. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  70. package/skills/microi-form-engine/SKILL.md +333 -333
  71. package/skills/microi-form-engine/references/component-catalog.md +218 -218
  72. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  73. package/skills/microi-form-layout/SKILL.md +205 -205
  74. package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -235
  75. package/skills/microi-frontend-sdk/SKILL.md +194 -194
  76. package/skills/microi-frontend-sdk/references/progressive-01-token-/345/275/223/345/211/215/347/231/273/345/275/225/347/224/250/346/210/267/344/270/216/345/275/223/345/211/215/347/273/210/347/253/257/347/231/273/345/275/225/345/215/217/350/256/256.md +176 -176
  77. package/skills/microi-left-right-layout/SKILL.md +141 -141
  78. package/skills/microi-microservice/SKILL.md +326 -324
  79. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  80. package/skills/microi-mobile-app-quality/SKILL.md +185 -185
  81. package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +213 -213
  82. package/skills/microi-mobile-app-quality/references/progressive-02-9-/344/270/273/351/242/230/345/210/207/346/215/242/345/277/205/351/241/273/347/234/237/345/256/236/344/270/224/345/205/250/345/261/200/347/224/237/346/225/210.md +128 -128
  83. package/skills/microi-solution-quotation/SKILL.md +78 -78
  84. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  85. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  86. package/skills/microi-sso/SKILL.md +92 -92
  87. package/skills/microi-sso/references/acceptance.md +49 -49
  88. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  89. package/skills/microi-sso/references/inbound.md +53 -53
  90. package/skills/microi-sso/references/outbound.md +39 -39
  91. package/skills/microi-system-delivery/SKILL.md +137 -137
  92. package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +193 -193
  93. package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +217 -217
  94. package/skills/microi-ui/SKILL.md +192 -192
  95. package/skills/microi-ui/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/234/272/346/231/257/350/223/235/345/233/276.md +183 -183
  96. package/skills/microi-uniapp-frontend/SKILL.md +193 -193
  97. package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +225 -225
  98. package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +154 -154
  99. package/skills/microi.v8.js +1921 -1921
  100. package/skills/module-engine/SKILL.md +255 -255
  101. package/skills/module-engine/references/module-config.md +204 -204
  102. package/skills/ocr-engine/SKILL.md +113 -113
  103. package/skills/ocr-engine/agents/openai.yaml +4 -4
  104. package/skills/page-engine/SKILL.md +206 -206
  105. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  106. package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +234 -234
  107. package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +60 -60
  108. package/skills/performance-testing/SKILL.md +221 -221
  109. package/skills/playwright-e2e/SKILL.md +196 -196
  110. package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +173 -173
  111. package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +183 -183
  112. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  113. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  114. package/skills/print-engine/SKILL.md +259 -259
  115. package/skills/production-readonly-audit/SKILL.md +41 -41
  116. package/skills/report-engine/SKILL.md +71 -71
  117. package/skills/report-engine/agents/openai.yaml +4 -4
  118. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  119. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  120. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  121. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  122. package/skills/search-engine/SKILL.md +75 -75
  123. package/skills/search-engine/agents/openai.yaml +4 -4
  124. package/skills/spider-engine/SKILL.md +190 -190
  125. package/skills/system-observability/SKILL.md +248 -248
  126. package/skills/system-observability/references/memory-incident-triage.md +77 -77
  127. package/skills/translate-engine/SKILL.md +140 -140
  128. package/skills/translate-engine/agents/openai.yaml +4 -4
  129. package/skills/ui-design/SKILL.md +223 -223
  130. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  131. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  132. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  133. package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
  134. package/skills/ui-design/references/design-pattern-library.md +184 -184
  135. package/skills/ui-design/references/mci-design-contract.md +163 -163
  136. package/skills/ui-design/references/motion-and-media.md +78 -78
  137. package/skills/ui-design/references/product-flow-recipes.md +94 -94
  138. package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +218 -218
  139. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
  140. package/skills/ui-design/references/progressive-03-/345/212/250/346/225/210/350/247/204/350/214/203-/344/270/260/345/257/214/344/275/206/344/270/215/345/215/241.md +235 -235
  141. package/skills/ui-design/references/progressive-04-/347/273/204/344/273/266/351/243/216/346/240/274/351/200/237/346/237/245.md +152 -152
  142. package/skills/ui-design/references/progressive-05-/347/247/273/345/212/250/347/253/257/344/270/223/347/224/250/350/247/204/350/214/203.md +238 -238
  143. package/skills/ui-design/references/progressive-06-/344/270/273/351/242/230/345/210/207/346/215/242/345/256/236/347/216/260.md +194 -194
  144. package/skills/ui-design/references/progressive-07-/351/200/237/346/237/245-/344/273/216/345/244/264/346/220/255/345/273/272/344/270/200/344/270/252/347/247/273/345/212/250/347/253/257/351/241/265/351/235/242.md +207 -207
  145. package/skills/ui-design/references/progressive-08-/350/241/250/345/215/225/345/210/206/347/273/204/350/247/204/350/214/203-tabs-vs-collapsegroup-/345/274/272/345/210/266.md +142 -142
  146. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  147. package/skills/unity-integration/SKILL.md +171 -171
  148. package/skills/unity-integration/agents/openai.yaml +4 -4
  149. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  150. package/skills/unity-integration/references/sdk-api.md +82 -82
  151. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  152. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  153. package/skills/v8-api-config/SKILL.md +388 -388
  154. package/skills/v8-cache-pattern/SKILL.md +312 -312
  155. package/skills/v8-crud-api/SKILL.md +178 -178
  156. package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +226 -226
  157. package/skills/v8-crud-api/references/progressive-02-where-/346/235/241/344/273/266/350/257/255/346/263/225/351/200/237/346/237/245.md +49 -49
  158. package/skills/v8-debugging/SKILL.md +284 -284
  159. package/skills/v8-explorer-tree/SKILL.md +228 -228
  160. package/skills/v8-export-import/SKILL.md +219 -219
  161. package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +211 -211
  162. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  163. package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +42 -42
  164. package/skills/v8-file-upload/SKILL.md +284 -284
  165. package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +263 -263
  166. package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +161 -161
  167. package/skills/v8-formengine-http/SKILL.md +238 -238
  168. package/skills/v8-frontend-events/SKILL.md +180 -180
  169. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  170. package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
  171. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  172. package/skills/v8-http-integration/SKILL.md +182 -182
  173. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  174. package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +44 -44
  175. package/skills/v8-image-processing/SKILL.md +190 -190
  176. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  177. package/skills/v8-image-processing/references/api-reference.md +623 -623
  178. package/skills/v8-menu-buttons/SKILL.md +186 -186
  179. package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +222 -222
  180. package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +228 -228
  181. package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +104 -104
  182. package/skills/v8-mongodb/SKILL.md +200 -200
  183. package/skills/v8-mq-mqtt/SKILL.md +176 -176
  184. package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
  185. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  186. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  187. package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
  188. package/skills/v8-security/SKILL.md +210 -210
  189. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
  190. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
  191. package/skills/v8-sql-query/SKILL.md +302 -302
  192. package/skills/v8-table-event/SKILL.md +176 -176
  193. package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +216 -216
  194. package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +46 -46
  195. package/skills/v8-tcp-integration/SKILL.md +147 -147
  196. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  197. package/skills/v8-template-engine/SKILL.md +167 -167
  198. package/skills/v8-utilities/SKILL.md +104 -104
  199. package/skills/v8-utilities/references/client-api-index.md +143 -143
  200. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  201. package/skills/v8-utilities/references/server-api-index.md +188 -188
  202. package/skills/v8-workflow/SKILL.md +252 -252
  203. package/skills/v8-workflow/references/progressive-01-/350/212/202/347/202/271/345/274/200/345/247/213-v8-/344/272/213/344/273/266.md +180 -180
  204. package/skills/v8-workflow/references/workflow-configuration.md +49 -49
  205. package/skills/vision-engine/SKILL.md +160 -160
  206. package/skills/vision-engine/agents/openai.yaml +4 -4
  207. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  208. package/skills/workspace-conventions/SKILL.md +273 -273
  209. package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +209 -209
  210. package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +217 -217
  211. package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +27 -27
@@ -1,652 +1,652 @@
1
- /*
2
- * Microi AI Application authentication bridge.
3
- *
4
- * Anonymous users may browse an application's UI and use explicitly anonymous,
5
- * server-scoped app_* operations. Requests are sent to ApiEngine first; only an
6
- * authoritative authentication response opens the shared login dialog and
7
- * retries once. Server-side ApiEngine remains the authority for persistence and
8
- * UserId isolation.
9
- */
10
- import V8 from './microi.v8.js';
11
- import JSEncrypt from '../Microi.Client/node_modules/jsencrypt/bin/jsencrypt.min.js';
12
-
13
- const nativeFetch = typeof window !== 'undefined' && window.fetch
14
- ? window.fetch.bind(window)
15
- : null;
16
- const NativeXHR = typeof window !== 'undefined' ? window.XMLHttpRequest : null;
17
- const AUTH_CODES = new Set([401, -1, 1001, 1002]);
18
- // Stop before Microi's `--OsClient--tenant--` suffix. Interface-engine keys
19
- // created for AI applications use the shared app_ prefix and underscore words.
20
- const APP_ENGINE_PATH = /\/apiengine\/(app_[a-z0-9_]+)/i;
21
- // This is intentionally a positive write list. A naming heuristic must never
22
- // block an anonymous first screen merely because a read-only engine uses a
23
- // domain verb such as `calculate`, `convert`, `trend` or `status`. Unknown
24
- // operations are sent anonymously first; the server-side user guard remains
25
- // authoritative and returns Code=1001 for a persistent write, at which point
26
- // the bridge opens the login dialog and retries once.
27
- const WRITE_ENGINE_KEY = /(?:^|_)(accept|ack|activate|add|adjust|allocate|append|apply|approve|archive|arrive|assign|attach|bind|book|cancel|certify|change|checkin|checkout|claim|clear|clock|close|collect|complete|confirm|consume|create|decide|decline|delete|depart|detach|dispatch|drop|end|event|execute|expire|finalize|finish|handover|hide|hold|import|ingest|initialize|insert|invite|issue|join|leave|lock|mark|move|occupy|open|order|pack|pass|pause|pay|pick|post|prepare|process|propose|publish|quarantine|raise|rebuild|receive|recall|reconcile|record|redeem|refund|register|reject|release|remove|reopen|reorder|replace|request|reschedule|reset|resolve|restore|resume|retire|retry|return|reverse|review|revoke|rollback|rotate|run|save|schedule|scrap|seal|seat|send|serve|set|settle|ship|simulate|skip|snapshot|start|submit|suspend|take|toggle|transfer|transition|unassign|update|upt|vacate|verify|vote|watched|withdraw)(?:_|$)/i;
28
- const OSCLIENT_SUFFIX = /--OsClient--[^/]*--$/i;
29
- const DEFAULT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
30
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7q21EG3HiSFNO9XFUJoMeyz2R
31
- XaFX8UgCFE4d4pvK6IvQsWunm+WfYqgrSzBMS1LH1fstmZB0wnVUX1uGROaZTKGZ
32
- 1rS/MVn4i6CsPgP9Q7nFV6dZvbxro1byH/E3CV/Q1CgCDeue9FzQUlWQ+UZld8Jg
33
- 1DsI9VJ7gTHGL3R7sQIDAQAB
34
- -----END PUBLIC KEY-----`;
35
-
36
- let loginPromise = null;
37
- let modal = null;
38
- let sysConfig = {};
39
- let captchaId = '';
40
- let captchaObjectUrl = '';
41
- let lastUserIntentAt = 0;
42
- let validatedSessionToken = '';
43
- const USER_INTENT_WINDOW_MS = 30000;
44
-
45
- function installUserIntentTracking() {
46
- if (document.documentElement.dataset.microiAiUserIntentTracking === 'true') return;
47
- document.documentElement.dataset.microiAiUserIntentTracking = 'true';
48
- const mark = event => {
49
- if (event && event.isTrusted === false) return;
50
- lastUserIntentAt = Date.now();
51
- };
52
- ['pointerdown', 'keydown', 'input', 'change', 'submit'].forEach(type => {
53
- document.addEventListener(type, mark, true);
54
- });
55
- }
56
-
57
- function hasRecentUserIntent() {
58
- const active = typeof navigator !== 'undefined' && navigator.userActivation
59
- ? navigator.userActivation.isActive
60
- : false;
61
- return active || Date.now() - lastUserIntentAt <= USER_INTENT_WINDOW_MS;
62
- }
63
-
64
- function flag(value) {
65
- if (value === true || value === 1) return true;
66
- const text = String(value == null ? '' : value).trim().toLowerCase();
67
- return text === '1' || text === 'true' || text === 'yes' || text === 'on';
68
- }
69
-
70
- function normalizeBase(value) {
71
- return String(value || '').trim().replace(/\/+$/, '');
72
- }
73
-
74
- function contextValue() {
75
- let microData = {};
76
- try {
77
- microData = window.microApp && typeof window.microApp.getData === 'function'
78
- ? (window.microApp.getData() || {})
79
- : {};
80
- } catch (error) {}
81
- return { ...window.__MICROI_APP_CONTEXT__, ...microData };
82
- }
83
-
84
- function runtime() {
85
- const query = new URLSearchParams(window.location.search);
86
- const context = contextValue();
87
- let apiBase = context.apiBase || context.ApiBase || window.MicroiApiBase
88
- || query.get('apiBase') || query.get('ApiBase') || '';
89
- let osClient = context.osClient || context.OsClient || window.MicroiOsClient
90
- || query.get('OsClient') || query.get('osClient') || '';
91
- if (!apiBase && /(^|\.)static\.itdos\.com$/i.test(window.location.hostname)) {
92
- apiBase = 'https://api.itdos.com';
93
- }
94
- if (!osClient && /(^|\.)static\.itdos\.com$/i.test(window.location.hostname)) {
95
- osClient = 'iTdos';
96
- }
97
- if (!apiBase) apiBase = window.location.origin;
98
- if (!osClient) osClient = localStorage.getItem('OsClient') || 'iTdos';
99
- apiBase = normalizeBase(apiBase);
100
- window.__MICROI_APP_CONTEXT__ = {
101
- ...context,
102
- apiBase,
103
- ApiBase: apiBase,
104
- osClient: String(osClient),
105
- OsClient: String(osClient)
106
- };
107
- window.MicroiApiBase = apiBase;
108
- window.MicroiOsClient = String(osClient);
109
- document.documentElement.dataset.microiAiAuthReady = 'true';
110
- document.documentElement.dataset.microiOsClient = String(osClient);
111
- try {
112
- // Many generated applications read `window.microApp.getData()` first and
113
- // use `{}` as the fallback. In a standalone HDFS page that empty object is
114
- // truthy and masks `__MICROI_APP_CONTEXT__`, so expose the same context
115
- // through a tiny compatible microApp facade. A real host-provided getData
116
- // function is preserved and merged with the portable runtime defaults.
117
- const microApp = window.microApp || (window.microApp = {});
118
- if (typeof microApp.getData !== 'function') {
119
- const getData = function () { return { ...(window.__MICROI_APP_CONTEXT__ || {}) }; };
120
- getData.__microiRuntimeFallback = true;
121
- microApp.getData = getData;
122
- } else if (!microApp.getData.__microiRuntimeFallback) {
123
- const originalGetData = microApp.getData.bind(microApp);
124
- const getData = function () {
125
- let current = {};
126
- try { current = originalGetData() || {}; } catch (error) {}
127
- return { ...(window.__MICROI_APP_CONTEXT__ || {}), ...current };
128
- };
129
- getData.__microiRuntimeFallback = true;
130
- microApp.getData = getData;
131
- }
132
- } catch (error) {}
133
- V8.configure({
134
- apiBase,
135
- osClient: String(osClient),
136
- clientType: /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent) ? 'Mobile' : 'PC'
137
- });
138
- return { apiBase, osClient: String(osClient) };
139
- }
140
-
141
- function normalizeToken(value) {
142
- return String(value || '').replace(/^Bearer\s+/i, '').trim();
143
- }
144
-
145
- function isAppEngineUrl(value) {
146
- return APP_ENGINE_PATH.test(String(value || ''));
147
- }
148
-
149
- function appEngineKey(value) {
150
- const match = String(value || '').match(APP_ENGINE_PATH);
151
- return match ? match[1] : '';
152
- }
153
-
154
- function isLikelyWrite(value) {
155
- const key = appEngineKey(value);
156
- return Boolean(key) && WRITE_ENGINE_KEY.test(key);
157
- }
158
-
159
- function rewriteApiUrl(value) {
160
- const raw = String(value || '');
161
- if (!isAppEngineUrl(raw)) return raw;
162
- try {
163
- const target = new URL(raw, window.location.href);
164
- const current = new URL(window.location.href);
165
- const resolved = runtime();
166
- target.pathname = target.pathname.replace(OSCLIENT_SUFFIX, '');
167
- if (target.origin === current.origin && resolved.apiBase !== current.origin) {
168
- return resolved.apiBase + target.pathname + target.search + target.hash;
169
- }
170
- return target.toString();
171
- } catch (error) {}
172
- return raw;
173
- }
174
-
175
- function setRequestHeaders(headers) {
176
- const resolved = runtime();
177
- const next = new Headers(headers || {});
178
- next.delete('OsClient');
179
- next.set('osclient', resolved.osClient);
180
- next.set('did', V8.getDid());
181
- const token = normalizeToken(V8.getToken());
182
- if (token) {
183
- next.set('Token', token);
184
- next.set('Authorization', `Bearer ${token}`);
185
- } else {
186
- next.delete('Token');
187
- next.delete('Authorization');
188
- }
189
- return next;
190
- }
191
-
192
- function authenticatedIdentity() {
193
- const user = V8.getUser() || {};
194
- const id = String(user.Id || user.id || user.UserId || '').trim();
195
- const name = String(user.Name || user.name || user.Account || user.account || '').trim();
196
- return id ? { id, name } : null;
197
- }
198
-
199
- function requestIdentity() {
200
- const authenticated = authenticatedIdentity();
201
- if (authenticated) return authenticated;
202
- const deviceId = String(V8.getDid() || 'browser')
203
- .replace(/[^a-z0-9_-]/gi, '')
204
- .slice(0, 80) || 'browser';
205
- return { id: `anon_${deviceId}`, name: '访客' };
206
- }
207
-
208
- function applyIdentityToObject(value) {
209
- const identity = requestIdentity();
210
- if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
211
- return {
212
- ...value,
213
- ClientKey: identity.id,
214
- ActorKey: identity.id,
215
- UserId: identity.id,
216
- UserName: identity.name
217
- };
218
- }
219
-
220
- function applyIdentityToBody(body, headers) {
221
- if (!body) return body;
222
- if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
223
- const identity = requestIdentity();
224
- const next = new URLSearchParams(body);
225
- next.set('ClientKey', identity.id);
226
- next.set('ActorKey', identity.id);
227
- next.set('UserId', identity.id);
228
- next.set('UserName', identity.name);
229
- return next;
230
- }
231
- if (typeof FormData !== 'undefined' && body instanceof FormData) {
232
- const identity = requestIdentity();
233
- const next = new FormData();
234
- for (const pair of body.entries()) next.append(pair[0], pair[1]);
235
- next.set('ClientKey', identity.id);
236
- next.set('ActorKey', identity.id);
237
- next.set('UserId', identity.id);
238
- next.set('UserName', identity.name);
239
- return next;
240
- }
241
- if (typeof body !== 'string') return body;
242
- const contentType = String(new Headers(headers || {}).get('content-type') || '').toLowerCase();
243
- if (contentType.includes('application/x-www-form-urlencoded')) {
244
- return applyIdentityToBody(new URLSearchParams(body), headers).toString();
245
- }
246
- if (contentType.includes('application/json') || /^[\s]*[\[{]/.test(body)) {
247
- try { return JSON.stringify(applyIdentityToObject(JSON.parse(body))); } catch (error) {}
248
- }
249
- return body;
250
- }
251
-
252
- function bodyAuthExpired(body, status) {
253
- if (Number(status) === 401 || AUTH_CODES.has(Number(body && body.Code))) return true;
254
- const message = String(body && (body.Msg || body.Message || body.message) || '');
255
- if (/(?:请先|需要|尚未|未)登录|登录后|Token.*(?:失效|缺失|过期)/i.test(message)) return true;
256
- if (V8.getToken()) return false;
257
- return /没有权限|无权限|身份验证/i.test(message);
258
- }
259
-
260
- function bodyAction(body, headers) {
261
- let value = body;
262
- if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
263
- value = Object.fromEntries(body.entries());
264
- } else if (typeof FormData !== 'undefined' && body instanceof FormData) {
265
- value = Object.fromEntries(body.entries());
266
- } else if (typeof body === 'string') {
267
- const contentType = String(new Headers(headers || {}).get('content-type') || '').toLowerCase();
268
- try {
269
- value = contentType.includes('application/x-www-form-urlencoded')
270
- ? Object.fromEntries(new URLSearchParams(body).entries())
271
- : JSON.parse(body);
272
- } catch (error) { value = null; }
273
- }
274
- if (!value || typeof value !== 'object' || Array.isArray(value)) return '';
275
- return String(value.Action || value.action || value.Command || value.command || value.Operation || value.operation || '');
276
- }
277
-
278
- function bodyLikelyWrites(body, headers) {
279
- const action = bodyAction(body, headers);
280
- return Boolean(action) && WRITE_ENGINE_KEY.test(`_${action}_`);
281
- }
282
-
283
- function registerUrl(path) {
284
- const local = /^(localhost|127\.0\.0\.1)$/i.test(window.location.hostname);
285
- return `${local ? 'http://localhost:2015' : 'https://microi.net'}/${path}`;
286
- }
287
-
288
- function injectStyles() {
289
- if (document.getElementById('microi-ai-auth-style')) return;
290
- const style = document.createElement('style');
291
- style.id = 'microi-ai-auth-style';
292
- style.textContent = `
293
- .mci-auth-mask{position:fixed;inset:0;z-index:2147483646;background:rgba(15,23,42,.52);backdrop-filter:blur(7px);display:flex;align-items:center;justify-content:center;padding:20px;font-family:Inter,"PingFang SC","Microsoft YaHei",sans-serif}
294
- .mci-auth-mask[hidden]{display:none!important}
295
- .mci-auth-card{width:min(420px,100%);background:#fff;border:1px solid rgba(148,163,184,.24);border-radius:20px;box-shadow:0 28px 80px rgba(15,23,42,.28);padding:30px;color:#172033;position:relative}
296
- .mci-auth-close{position:absolute;right:18px;top:16px;border:0;background:transparent;color:#64748b;font-size:24px;cursor:pointer}
297
- .mci-auth-brand{display:flex;align-items:center;gap:10px;color:#135ee8;font-weight:800;font-size:15px;letter-spacing:.04em}.mci-auth-logo{width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,#1769ff,#05a8bf);display:grid;place-items:center;color:#fff;font-size:18px}
298
- .mci-auth-title{font-size:25px;line-height:1.25;margin:22px 0 6px;font-weight:760}.mci-auth-sub{font-size:14px;color:#64748b;margin:0 0 22px}
299
- .mci-auth-field{display:block;margin:0 0 14px}.mci-auth-field span{display:block;font-size:13px;font-weight:650;margin:0 0 7px}.mci-auth-field input{box-sizing:border-box;width:100%;height:46px;border:1px solid #dbe3ef;border-radius:11px;padding:0 13px;font-size:15px;outline:0;background:#f8fafc;color:#172033}.mci-auth-field input:focus{border-color:#1769ff;box-shadow:0 0 0 3px rgba(23,105,255,.12);background:#fff}
300
- .mci-auth-captcha{display:grid;grid-template-columns:1fr 120px;gap:10px}.mci-auth-captcha img{width:120px;height:46px;object-fit:cover;border-radius:10px;border:1px solid #dbe3ef;cursor:pointer}
301
- .mci-auth-captcha-field[hidden],.mci-auth-privacy[hidden]{display:none!important}
302
- .mci-auth-privacy{display:flex;gap:8px;align-items:flex-start;color:#64748b;font-size:13px;margin:2px 0 14px}.mci-auth-privacy input{margin-top:2px}.mci-auth-message{min-height:20px;margin:0 0 8px;color:#dc2626;font-size:13px}.mci-auth-submit{width:100%;height:46px;border:0;border-radius:11px;background:linear-gradient(135deg,#1769ff,#0e88d8);color:#fff;font-size:15px;font-weight:700;cursor:pointer;box-shadow:0 10px 26px rgba(23,105,255,.25)}.mci-auth-submit:disabled{opacity:.65;cursor:wait}
303
- .mci-auth-links{display:flex;justify-content:space-between;margin-top:16px;font-size:13px}.mci-auth-links a{color:#1769ff;text-decoration:none}.mci-auth-note{margin:18px 0 0;padding:12px 14px;border-radius:11px;background:#eff6ff;color:#475569;font-size:12px;line-height:1.65}
304
- @media(prefers-color-scheme:dark){.mci-auth-card{background:#111827;color:#f8fafc;border-color:#334155}.mci-auth-field input{background:#0f172a;border-color:#334155;color:#f8fafc}.mci-auth-sub,.mci-auth-privacy,.mci-auth-note{color:#aab6c8}.mci-auth-note{background:#172554}}
305
- `;
306
- document.head.appendChild(style);
307
- }
308
-
309
- function ensureModal() {
310
- if (modal) return modal;
311
- injectStyles();
312
- const mask = document.createElement('div');
313
- mask.className = 'mci-auth-mask';
314
- mask.hidden = true;
315
- mask.innerHTML = `
316
- <form class="mci-auth-card" autocomplete="on">
317
- <button class="mci-auth-close" type="button" aria-label="关闭">×</button>
318
- <div class="mci-auth-brand"><span class="mci-auth-logo">M</span><span>Microi 吾码</span></div>
319
- <h2 class="mci-auth-title">登录后继续操作</h2>
320
- <p class="mci-auth-sub">应用可以匿名浏览,保存的业务数据将安全归属到您的账号。</p>
321
- <label class="mci-auth-field"><span>账号</span><input name="account" autocomplete="username" required placeholder="手机号或账号"></label>
322
- <label class="mci-auth-field"><span>密码</span><input name="password" type="password" autocomplete="current-password" required placeholder="请输入登录密码"></label>
323
- <label class="mci-auth-field mci-auth-captcha-field" hidden><span>验证码</span><div class="mci-auth-captcha"><input name="captcha" autocomplete="off" placeholder="图形验证码"><img alt="点击刷新验证码"></div></label>
324
- <label class="mci-auth-privacy" hidden><input name="privacy" type="checkbox"><span></span></label>
325
- <div class="mci-auth-message" role="alert"></div>
326
- <button class="mci-auth-submit" type="submit">登录并继续</button>
327
- <div class="mci-auth-links"><a data-link="register" target="_blank">注册账号</a><a data-link="forgot" target="_blank">忘记密码</a></div>
328
- <p class="mci-auth-note">登录只在您保存、提交或修改数据时需要;继续浏览不会创建个人数据。</p>
329
- </form>`;
330
- document.body.appendChild(mask);
331
- const form = mask.querySelector('form');
332
- mask.querySelector('[data-link="register"]').href = registerUrl('register.html');
333
- mask.querySelector('[data-link="forgot"]').href = registerUrl('login.html#forgot');
334
- modal = {
335
- mask,
336
- form,
337
- account: form.elements.account,
338
- password: form.elements.password,
339
- captcha: form.elements.captcha,
340
- captchaField: mask.querySelector('.mci-auth-captcha-field'),
341
- captchaImage: mask.querySelector('.mci-auth-captcha img'),
342
- privacy: form.elements.privacy,
343
- privacyLabel: mask.querySelector('.mci-auth-privacy'),
344
- privacyText: mask.querySelector('.mci-auth-privacy span'),
345
- message: mask.querySelector('.mci-auth-message'),
346
- submit: mask.querySelector('.mci-auth-submit'),
347
- close: mask.querySelector('.mci-auth-close')
348
- };
349
- modal.account.value = localStorage.getItem('microi_last_login_account') || '';
350
- modal.captchaImage.addEventListener('click', () => void loadCaptcha());
351
- return modal;
352
- }
353
-
354
- async function jsonRequest(path, options = {}) {
355
- if (!nativeFetch) throw new Error('当前浏览器不支持网络请求');
356
- const resolved = runtime();
357
- const response = await nativeFetch(resolved.apiBase + path, options);
358
- const text = await response.text();
359
- let body;
360
- try { body = JSON.parse(text); } catch (error) { body = { Code: 0, Msg: text || `HTTP ${response.status}` }; }
361
- return { response, body };
362
- }
363
-
364
- async function loadSysConfig() {
365
- const resolved = runtime();
366
- const result = await jsonRequest(`/apiengine/platform-sys-config?OsClient=${encodeURIComponent(resolved.osClient)}`, {
367
- method: 'POST',
368
- headers: { 'Content-Type': 'application/json', osclient: resolved.osClient, did: V8.getDid(), apiengine: '1' },
369
- body: JSON.stringify({ OsClient: resolved.osClient, _SearchEqual: { IsEnable: 1 } })
370
- });
371
- if (!result.body || Number(result.body.Code) !== 1) {
372
- throw new Error((result.body && result.body.Msg) || '系统登录配置加载失败');
373
- }
374
- sysConfig = result.body.Data || {};
375
- return sysConfig;
376
- }
377
-
378
- async function loadCaptcha() {
379
- const view = ensureModal();
380
- view.captchaField.hidden = true;
381
- view.captchaImage.removeAttribute('src');
382
- if (!flag(sysConfig.EnableCaptcha)) {
383
- captchaId = '';
384
- return;
385
- }
386
- const resolved = runtime();
387
- const response = await nativeFetch(`${resolved.apiBase}/api/Captcha/GetCaptcha?OsClient=${encodeURIComponent(resolved.osClient)}`, {
388
- headers: { osclient: resolved.osClient, did: V8.getDid() }
389
- });
390
- if (!response.ok) throw new Error('验证码加载失败');
391
- captchaId = response.headers.get('captchaid') || '';
392
- if (!captchaId) throw new Error('验证码标识缺失,请刷新后重试');
393
- const captchaBlob = await response.blob();
394
- if (!captchaBlob.size) throw new Error('验证码图片为空,请刷新后重试');
395
- if (captchaObjectUrl) URL.revokeObjectURL(captchaObjectUrl);
396
- captchaObjectUrl = URL.createObjectURL(captchaBlob);
397
- await new Promise((resolve, reject) => {
398
- const done = (callback) => {
399
- window.clearTimeout(timer);
400
- view.captchaImage.onload = null;
401
- view.captchaImage.onerror = null;
402
- callback();
403
- };
404
- const timer = window.setTimeout(() => done(() => reject(new Error('验证码图片加载超时'))), 8000);
405
- view.captchaImage.onload = () => done(resolve);
406
- view.captchaImage.onerror = () => done(() => reject(new Error('验证码图片解析失败')));
407
- view.captchaImage.src = captchaObjectUrl;
408
- });
409
- view.captchaField.hidden = false;
410
- }
411
-
412
- function encryptPassword(password) {
413
- const publicKey = window.MicroiLoginPublicKey
414
- || String(sysConfig.LoginRsaPublicKey || '').replace(/\\n/g, '\n').trim()
415
- || DEFAULT_PUBLIC_KEY;
416
- if (!publicKey) return password;
417
- const encryptor = new JSEncrypt();
418
- encryptor.setPublicKey(publicKey);
419
- const encrypted = encryptor.encrypt(password);
420
- if (!encrypted) throw new Error('密码加密失败,请刷新后重试');
421
- return encrypted;
422
- }
423
-
424
- async function performLogin() {
425
- const view = ensureModal();
426
- const account = String(view.account.value || '').trim();
427
- const password = String(view.password.value || '');
428
- if (!account) throw new Error('请输入账号');
429
- if (!password) throw new Error('请输入密码');
430
- if (flag(sysConfig.EnablePrivacyPolicy) && !view.privacy.checked) {
431
- throw new Error(`请先勾选${sysConfig.PrivacyPolicyName || '同意隐私协议'}`);
432
- }
433
- const resolved = runtime();
434
- const data = new URLSearchParams();
435
- data.set('Account', account);
436
- data.set('Pwd', encryptPassword(password));
437
- data.set('OsClient', resolved.osClient);
438
- data.set('_ClientType', V8.config.clientType || 'PC');
439
- if (flag(sysConfig.EnableCaptcha)) {
440
- data.set('_CaptchaId', captchaId);
441
- data.set('_CaptchaValue', String(view.captcha.value || '').trim());
442
- }
443
- const result = await jsonRequest('/api/SysUser/Login', {
444
- method: 'POST',
445
- headers: {
446
- 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
447
- osclient: resolved.osClient,
448
- did: V8.getDid()
449
- },
450
- body: data.toString()
451
- });
452
- if (!result.body || Number(result.body.Code) !== 1) {
453
- if (flag(sysConfig.EnableCaptcha)) await loadCaptcha();
454
- throw new Error((result.body && result.body.Msg) || '登录失败');
455
- }
456
- const token = normalizeToken(
457
- result.response.headers.get('authorization')
458
- || result.response.headers.get('token')
459
- || result.body.Token
460
- || (result.body.Data && (result.body.Data.Token || result.body.Data.Authorization))
461
- );
462
- if (!token) throw new Error('登录成功但未收到 Token,请检查服务端登录响应');
463
- V8.setToken(token);
464
- V8.setUser(result.body.Data || {});
465
- validatedSessionToken = token;
466
- localStorage.setItem('microi_last_login_account', account);
467
- window.dispatchEvent(new CustomEvent('microi:ai-app-login', { detail: result.body.Data || {} }));
468
- return result.body.Data || {};
469
- }
470
-
471
- async function validateCachedLogin() {
472
- const token = normalizeToken(V8.getToken());
473
- if (!token) return null;
474
- const cachedUser = V8.getUser() || {};
475
- if (validatedSessionToken === token && authenticatedIdentity()) return cachedUser;
476
-
477
- const resolved = runtime();
478
- const result = await jsonRequest(`/apiengine/platform-current-user?OsClient=${encodeURIComponent(resolved.osClient)}`, {
479
- method: 'POST',
480
- headers: {
481
- 'Content-Type': 'application/json',
482
- osclient: resolved.osClient,
483
- did: V8.getDid(),
484
- apiengine: '1',
485
- Token: token,
486
- Authorization: `Bearer ${token}`
487
- },
488
- body: JSON.stringify({ OsClient: resolved.osClient, _ClientType: V8.config.clientType || 'PC' })
489
- });
490
- const refreshedToken = normalizeToken(
491
- result.response.headers.get('authorization') || result.response.headers.get('token') || token
492
- );
493
- const user = result.body && Number(result.body.Code) === 1 ? (result.body.Data || {}) : {};
494
- const userId = String(user.Id || user.id || user.UserId || '').trim();
495
- if (result.response.ok && userId) {
496
- if (refreshedToken) V8.setToken(refreshedToken);
497
- V8.setUser(user);
498
- validatedSessionToken = refreshedToken || token;
499
- return user;
500
- }
501
-
502
- V8.clearToken();
503
- validatedSessionToken = '';
504
- return null;
505
- }
506
-
507
- async function showLogin() {
508
- const currentUser = await validateCachedLogin();
509
- if (currentUser) return currentUser;
510
- if (loginPromise) return loginPromise;
511
- const view = ensureModal();
512
- document.documentElement.dataset.microiAiAuthState = 'prompt';
513
- view.mask.hidden = false;
514
- view.message.textContent = '';
515
- view.submit.disabled = false;
516
- view.account.focus();
517
- loginPromise = new Promise(async (resolve, reject) => {
518
- let finished = false;
519
- const cleanup = () => {
520
- view.form.removeEventListener('submit', submit);
521
- view.close.removeEventListener('click', cancel);
522
- view.mask.removeEventListener('click', outside);
523
- };
524
- const cancel = () => {
525
- if (finished) return;
526
- finished = true;
527
- cleanup();
528
- view.mask.hidden = true;
529
- reject(new Error('已取消登录'));
530
- };
531
- const outside = (event) => { if (event.target === view.mask) cancel(); };
532
- const submit = async (event) => {
533
- event.preventDefault();
534
- view.submit.disabled = true;
535
- view.message.textContent = '';
536
- try {
537
- const user = await performLogin();
538
- finished = true;
539
- cleanup();
540
- view.mask.hidden = true;
541
- resolve(user);
542
- } catch (error) {
543
- view.message.textContent = error && error.message ? error.message : String(error);
544
- } finally {
545
- view.submit.disabled = false;
546
- }
547
- };
548
- view.form.addEventListener('submit', submit);
549
- view.close.addEventListener('click', cancel);
550
- view.mask.addEventListener('click', outside);
551
- try {
552
- await loadSysConfig();
553
- view.privacyLabel.hidden = !flag(sysConfig.EnablePrivacyPolicy);
554
- view.privacyText.textContent = sysConfig.PrivacyPolicyName || '同意隐私协议';
555
- await loadCaptcha();
556
- } catch (error) {
557
- view.message.textContent = error && error.message ? error.message : String(error);
558
- }
559
- }).finally(() => { loginPromise = null; });
560
- return loginPromise;
561
- }
562
-
563
- async function fetchWithAuth(input, init = {}, retried = false) {
564
- const rawUrl = typeof input === 'string' || input instanceof URL ? String(input) : input.url;
565
- const appRequest = isAppEngineUrl(rawUrl);
566
- if (!appRequest) return nativeFetch(input, init);
567
- const writeRequest = isLikelyWrite(rawUrl) || bodyLikelyWrites(init.body, init.headers);
568
- const promptAllowed = hasRecentUserIntent();
569
- document.documentElement.dataset.microiLastEngine = appEngineKey(rawUrl) || '';
570
- document.documentElement.dataset.microiWriteDetected = writeRequest ? 'true' : 'false';
571
- document.documentElement.dataset.microiHasToken = V8.getToken() ? 'true' : 'false';
572
- const headers = setRequestHeaders(init.headers || (input && input.headers));
573
- const target = typeof input === 'string' || input instanceof URL ? rewriteApiUrl(rawUrl) : input;
574
- const requestBody = applyIdentityToBody(init.body, headers);
575
- const response = await nativeFetch(target, { ...init, headers, body: requestBody });
576
- let responseBody = null;
577
- try { responseBody = await response.clone().json(); } catch (error) {}
578
- if (!retried && bodyAuthExpired(responseBody, response.status) && promptAllowed) {
579
- V8.clearToken();
580
- await showLogin();
581
- return fetchWithAuth(input, init, true);
582
- }
583
- const returnedToken = normalizeToken(response.headers.get('authorization') || response.headers.get('token'));
584
- if (returnedToken) V8.setToken(returnedToken);
585
- return response;
586
- }
587
-
588
- function installFetchBridge() {
589
- if (!nativeFetch || window.fetch.__microiAiAuthBridge) return;
590
- const bridged = function (input, init) { return fetchWithAuth(input, init || {}); };
591
- bridged.__microiAiAuthBridge = true;
592
- window.fetch = bridged;
593
- }
594
-
595
- function installXhrBridge() {
596
- if (!NativeXHR || NativeXHR.prototype.__microiAiAuthBridge) return;
597
- const open = NativeXHR.prototype.open;
598
- const send = NativeXHR.prototype.send;
599
- const setRequestHeader = NativeXHR.prototype.setRequestHeader;
600
- NativeXHR.prototype.open = function (method, url, async, user, password) {
601
- this.__microiMethod = method;
602
- this.__microiUrl = String(url || '');
603
- return open.call(this, method, rewriteApiUrl(url), async === undefined ? true : async, user, password);
604
- };
605
- NativeXHR.prototype.send = function (body) {
606
- if (!isAppEngineUrl(this.__microiUrl)) return send.call(this, body);
607
- const promptAllowed = hasRecentUserIntent();
608
- const execute = () => {
609
- const resolved = runtime();
610
- setRequestHeader.call(this, 'osclient', resolved.osClient);
611
- setRequestHeader.call(this, 'did', V8.getDid());
612
- const token = normalizeToken(V8.getToken());
613
- if (token) {
614
- setRequestHeader.call(this, 'Token', token);
615
- setRequestHeader.call(this, 'Authorization', `Bearer ${token}`);
616
- }
617
- this.addEventListener('load', () => {
618
- try {
619
- const result = JSON.parse(this.responseText || '{}');
620
- if (bodyAuthExpired(result, this.status) && promptAllowed) {
621
- V8.clearToken();
622
- void showLogin();
623
- }
624
- } catch (error) {}
625
- }, { once: true });
626
- const requestBody = applyIdentityToBody(body, { 'content-type': this.__microiContentType || '' });
627
- send.call(this, requestBody);
628
- };
629
- execute();
630
- };
631
- NativeXHR.prototype.setRequestHeader = function (name, value) {
632
- const normalized = String(name || '').toLowerCase();
633
- if (normalized === 'content-type') this.__microiContentType = String(value || '');
634
- if (normalized === 'osclient') return;
635
- return setRequestHeader.call(this, name, value);
636
- };
637
- NativeXHR.prototype.__microiAiAuthBridge = true;
638
- }
639
-
640
- runtime();
641
- installUserIntentTracking();
642
- installFetchBridge();
643
- installXhrBridge();
644
- window.MicroiV8 = window.MicroiV8 || V8;
645
- window.MicroiAiAppAuth = {
646
- login: showLogin,
647
- logout() { validatedSessionToken = ''; V8.clearToken(); window.dispatchEvent(new Event('microi:ai-app-logout')); },
648
- getUser: () => V8.getUser(),
649
- getToken: () => V8.getToken(),
650
- getDid: () => V8.getDid(),
651
- runtime
652
- };
1
+ /*
2
+ * Microi AI Application authentication bridge.
3
+ *
4
+ * Anonymous users may browse an application's UI and use explicitly anonymous,
5
+ * server-scoped app_* operations. Requests are sent to ApiEngine first; only an
6
+ * authoritative authentication response opens the shared login dialog and
7
+ * retries once. Server-side ApiEngine remains the authority for persistence and
8
+ * UserId isolation.
9
+ */
10
+ import V8 from './microi.v8.js';
11
+ import JSEncrypt from '../Microi.Client/node_modules/jsencrypt/bin/jsencrypt.min.js';
12
+
13
+ const nativeFetch = typeof window !== 'undefined' && window.fetch
14
+ ? window.fetch.bind(window)
15
+ : null;
16
+ const NativeXHR = typeof window !== 'undefined' ? window.XMLHttpRequest : null;
17
+ const AUTH_CODES = new Set([401, -1, 1001, 1002]);
18
+ // Stop before Microi's `--OsClient--tenant--` suffix. Interface-engine keys
19
+ // created for AI applications use the shared app_ prefix and underscore words.
20
+ const APP_ENGINE_PATH = /\/apiengine\/(app_[a-z0-9_]+)/i;
21
+ // This is intentionally a positive write list. A naming heuristic must never
22
+ // block an anonymous first screen merely because a read-only engine uses a
23
+ // domain verb such as `calculate`, `convert`, `trend` or `status`. Unknown
24
+ // operations are sent anonymously first; the server-side user guard remains
25
+ // authoritative and returns Code=1001 for a persistent write, at which point
26
+ // the bridge opens the login dialog and retries once.
27
+ const WRITE_ENGINE_KEY = /(?:^|_)(accept|ack|activate|add|adjust|allocate|append|apply|approve|archive|arrive|assign|attach|bind|book|cancel|certify|change|checkin|checkout|claim|clear|clock|close|collect|complete|confirm|consume|create|decide|decline|delete|depart|detach|dispatch|drop|end|event|execute|expire|finalize|finish|handover|hide|hold|import|ingest|initialize|insert|invite|issue|join|leave|lock|mark|move|occupy|open|order|pack|pass|pause|pay|pick|post|prepare|process|propose|publish|quarantine|raise|rebuild|receive|recall|reconcile|record|redeem|refund|register|reject|release|remove|reopen|reorder|replace|request|reschedule|reset|resolve|restore|resume|retire|retry|return|reverse|review|revoke|rollback|rotate|run|save|schedule|scrap|seal|seat|send|serve|set|settle|ship|simulate|skip|snapshot|start|submit|suspend|take|toggle|transfer|transition|unassign|update|upt|vacate|verify|vote|watched|withdraw)(?:_|$)/i;
28
+ const OSCLIENT_SUFFIX = /--OsClient--[^/]*--$/i;
29
+ const DEFAULT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
30
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7q21EG3HiSFNO9XFUJoMeyz2R
31
+ XaFX8UgCFE4d4pvK6IvQsWunm+WfYqgrSzBMS1LH1fstmZB0wnVUX1uGROaZTKGZ
32
+ 1rS/MVn4i6CsPgP9Q7nFV6dZvbxro1byH/E3CV/Q1CgCDeue9FzQUlWQ+UZld8Jg
33
+ 1DsI9VJ7gTHGL3R7sQIDAQAB
34
+ -----END PUBLIC KEY-----`;
35
+
36
+ let loginPromise = null;
37
+ let modal = null;
38
+ let sysConfig = {};
39
+ let captchaId = '';
40
+ let captchaObjectUrl = '';
41
+ let lastUserIntentAt = 0;
42
+ let validatedSessionToken = '';
43
+ const USER_INTENT_WINDOW_MS = 30000;
44
+
45
+ function installUserIntentTracking() {
46
+ if (document.documentElement.dataset.microiAiUserIntentTracking === 'true') return;
47
+ document.documentElement.dataset.microiAiUserIntentTracking = 'true';
48
+ const mark = event => {
49
+ if (event && event.isTrusted === false) return;
50
+ lastUserIntentAt = Date.now();
51
+ };
52
+ ['pointerdown', 'keydown', 'input', 'change', 'submit'].forEach(type => {
53
+ document.addEventListener(type, mark, true);
54
+ });
55
+ }
56
+
57
+ function hasRecentUserIntent() {
58
+ const active = typeof navigator !== 'undefined' && navigator.userActivation
59
+ ? navigator.userActivation.isActive
60
+ : false;
61
+ return active || Date.now() - lastUserIntentAt <= USER_INTENT_WINDOW_MS;
62
+ }
63
+
64
+ function flag(value) {
65
+ if (value === true || value === 1) return true;
66
+ const text = String(value == null ? '' : value).trim().toLowerCase();
67
+ return text === '1' || text === 'true' || text === 'yes' || text === 'on';
68
+ }
69
+
70
+ function normalizeBase(value) {
71
+ return String(value || '').trim().replace(/\/+$/, '');
72
+ }
73
+
74
+ function contextValue() {
75
+ let microData = {};
76
+ try {
77
+ microData = window.microApp && typeof window.microApp.getData === 'function'
78
+ ? (window.microApp.getData() || {})
79
+ : {};
80
+ } catch (error) {}
81
+ return { ...window.__MICROI_APP_CONTEXT__, ...microData };
82
+ }
83
+
84
+ function runtime() {
85
+ const query = new URLSearchParams(window.location.search);
86
+ const context = contextValue();
87
+ let apiBase = context.apiBase || context.ApiBase || window.MicroiApiBase
88
+ || query.get('apiBase') || query.get('ApiBase') || '';
89
+ let osClient = context.osClient || context.OsClient || window.MicroiOsClient
90
+ || query.get('OsClient') || query.get('osClient') || '';
91
+ if (!apiBase && /(^|\.)static\.itdos\.com$/i.test(window.location.hostname)) {
92
+ apiBase = 'https://api.itdos.com';
93
+ }
94
+ if (!osClient && /(^|\.)static\.itdos\.com$/i.test(window.location.hostname)) {
95
+ osClient = 'iTdos';
96
+ }
97
+ if (!apiBase) apiBase = window.location.origin;
98
+ if (!osClient) osClient = localStorage.getItem('OsClient') || 'iTdos';
99
+ apiBase = normalizeBase(apiBase);
100
+ window.__MICROI_APP_CONTEXT__ = {
101
+ ...context,
102
+ apiBase,
103
+ ApiBase: apiBase,
104
+ osClient: String(osClient),
105
+ OsClient: String(osClient)
106
+ };
107
+ window.MicroiApiBase = apiBase;
108
+ window.MicroiOsClient = String(osClient);
109
+ document.documentElement.dataset.microiAiAuthReady = 'true';
110
+ document.documentElement.dataset.microiOsClient = String(osClient);
111
+ try {
112
+ // Many generated applications read `window.microApp.getData()` first and
113
+ // use `{}` as the fallback. In a standalone HDFS page that empty object is
114
+ // truthy and masks `__MICROI_APP_CONTEXT__`, so expose the same context
115
+ // through a tiny compatible microApp facade. A real host-provided getData
116
+ // function is preserved and merged with the portable runtime defaults.
117
+ const microApp = window.microApp || (window.microApp = {});
118
+ if (typeof microApp.getData !== 'function') {
119
+ const getData = function () { return { ...(window.__MICROI_APP_CONTEXT__ || {}) }; };
120
+ getData.__microiRuntimeFallback = true;
121
+ microApp.getData = getData;
122
+ } else if (!microApp.getData.__microiRuntimeFallback) {
123
+ const originalGetData = microApp.getData.bind(microApp);
124
+ const getData = function () {
125
+ let current = {};
126
+ try { current = originalGetData() || {}; } catch (error) {}
127
+ return { ...(window.__MICROI_APP_CONTEXT__ || {}), ...current };
128
+ };
129
+ getData.__microiRuntimeFallback = true;
130
+ microApp.getData = getData;
131
+ }
132
+ } catch (error) {}
133
+ V8.configure({
134
+ apiBase,
135
+ osClient: String(osClient),
136
+ clientType: /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent) ? 'Mobile' : 'PC'
137
+ });
138
+ return { apiBase, osClient: String(osClient) };
139
+ }
140
+
141
+ function normalizeToken(value) {
142
+ return String(value || '').replace(/^Bearer\s+/i, '').trim();
143
+ }
144
+
145
+ function isAppEngineUrl(value) {
146
+ return APP_ENGINE_PATH.test(String(value || ''));
147
+ }
148
+
149
+ function appEngineKey(value) {
150
+ const match = String(value || '').match(APP_ENGINE_PATH);
151
+ return match ? match[1] : '';
152
+ }
153
+
154
+ function isLikelyWrite(value) {
155
+ const key = appEngineKey(value);
156
+ return Boolean(key) && WRITE_ENGINE_KEY.test(key);
157
+ }
158
+
159
+ function rewriteApiUrl(value) {
160
+ const raw = String(value || '');
161
+ if (!isAppEngineUrl(raw)) return raw;
162
+ try {
163
+ const target = new URL(raw, window.location.href);
164
+ const current = new URL(window.location.href);
165
+ const resolved = runtime();
166
+ target.pathname = target.pathname.replace(OSCLIENT_SUFFIX, '');
167
+ if (target.origin === current.origin && resolved.apiBase !== current.origin) {
168
+ return resolved.apiBase + target.pathname + target.search + target.hash;
169
+ }
170
+ return target.toString();
171
+ } catch (error) {}
172
+ return raw;
173
+ }
174
+
175
+ function setRequestHeaders(headers) {
176
+ const resolved = runtime();
177
+ const next = new Headers(headers || {});
178
+ next.delete('OsClient');
179
+ next.set('osclient', resolved.osClient);
180
+ next.set('did', V8.getDid());
181
+ const token = normalizeToken(V8.getToken());
182
+ if (token) {
183
+ next.set('Token', token);
184
+ next.set('Authorization', `Bearer ${token}`);
185
+ } else {
186
+ next.delete('Token');
187
+ next.delete('Authorization');
188
+ }
189
+ return next;
190
+ }
191
+
192
+ function authenticatedIdentity() {
193
+ const user = V8.getUser() || {};
194
+ const id = String(user.Id || user.id || user.UserId || '').trim();
195
+ const name = String(user.Name || user.name || user.Account || user.account || '').trim();
196
+ return id ? { id, name } : null;
197
+ }
198
+
199
+ function requestIdentity() {
200
+ const authenticated = authenticatedIdentity();
201
+ if (authenticated) return authenticated;
202
+ const deviceId = String(V8.getDid() || 'browser')
203
+ .replace(/[^a-z0-9_-]/gi, '')
204
+ .slice(0, 80) || 'browser';
205
+ return { id: `anon_${deviceId}`, name: '访客' };
206
+ }
207
+
208
+ function applyIdentityToObject(value) {
209
+ const identity = requestIdentity();
210
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
211
+ return {
212
+ ...value,
213
+ ClientKey: identity.id,
214
+ ActorKey: identity.id,
215
+ UserId: identity.id,
216
+ UserName: identity.name
217
+ };
218
+ }
219
+
220
+ function applyIdentityToBody(body, headers) {
221
+ if (!body) return body;
222
+ if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
223
+ const identity = requestIdentity();
224
+ const next = new URLSearchParams(body);
225
+ next.set('ClientKey', identity.id);
226
+ next.set('ActorKey', identity.id);
227
+ next.set('UserId', identity.id);
228
+ next.set('UserName', identity.name);
229
+ return next;
230
+ }
231
+ if (typeof FormData !== 'undefined' && body instanceof FormData) {
232
+ const identity = requestIdentity();
233
+ const next = new FormData();
234
+ for (const pair of body.entries()) next.append(pair[0], pair[1]);
235
+ next.set('ClientKey', identity.id);
236
+ next.set('ActorKey', identity.id);
237
+ next.set('UserId', identity.id);
238
+ next.set('UserName', identity.name);
239
+ return next;
240
+ }
241
+ if (typeof body !== 'string') return body;
242
+ const contentType = String(new Headers(headers || {}).get('content-type') || '').toLowerCase();
243
+ if (contentType.includes('application/x-www-form-urlencoded')) {
244
+ return applyIdentityToBody(new URLSearchParams(body), headers).toString();
245
+ }
246
+ if (contentType.includes('application/json') || /^[\s]*[\[{]/.test(body)) {
247
+ try { return JSON.stringify(applyIdentityToObject(JSON.parse(body))); } catch (error) {}
248
+ }
249
+ return body;
250
+ }
251
+
252
+ function bodyAuthExpired(body, status) {
253
+ if (Number(status) === 401 || AUTH_CODES.has(Number(body && body.Code))) return true;
254
+ const message = String(body && (body.Msg || body.Message || body.message) || '');
255
+ if (/(?:请先|需要|尚未|未)登录|登录后|Token.*(?:失效|缺失|过期)/i.test(message)) return true;
256
+ if (V8.getToken()) return false;
257
+ return /没有权限|无权限|身份验证/i.test(message);
258
+ }
259
+
260
+ function bodyAction(body, headers) {
261
+ let value = body;
262
+ if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
263
+ value = Object.fromEntries(body.entries());
264
+ } else if (typeof FormData !== 'undefined' && body instanceof FormData) {
265
+ value = Object.fromEntries(body.entries());
266
+ } else if (typeof body === 'string') {
267
+ const contentType = String(new Headers(headers || {}).get('content-type') || '').toLowerCase();
268
+ try {
269
+ value = contentType.includes('application/x-www-form-urlencoded')
270
+ ? Object.fromEntries(new URLSearchParams(body).entries())
271
+ : JSON.parse(body);
272
+ } catch (error) { value = null; }
273
+ }
274
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return '';
275
+ return String(value.Action || value.action || value.Command || value.command || value.Operation || value.operation || '');
276
+ }
277
+
278
+ function bodyLikelyWrites(body, headers) {
279
+ const action = bodyAction(body, headers);
280
+ return Boolean(action) && WRITE_ENGINE_KEY.test(`_${action}_`);
281
+ }
282
+
283
+ function registerUrl(path) {
284
+ const local = /^(localhost|127\.0\.0\.1)$/i.test(window.location.hostname);
285
+ return `${local ? 'http://localhost:2015' : 'https://microi.net'}/${path}`;
286
+ }
287
+
288
+ function injectStyles() {
289
+ if (document.getElementById('microi-ai-auth-style')) return;
290
+ const style = document.createElement('style');
291
+ style.id = 'microi-ai-auth-style';
292
+ style.textContent = `
293
+ .mci-auth-mask{position:fixed;inset:0;z-index:2147483646;background:rgba(15,23,42,.52);backdrop-filter:blur(7px);display:flex;align-items:center;justify-content:center;padding:20px;font-family:Inter,"PingFang SC","Microsoft YaHei",sans-serif}
294
+ .mci-auth-mask[hidden]{display:none!important}
295
+ .mci-auth-card{width:min(420px,100%);background:#fff;border:1px solid rgba(148,163,184,.24);border-radius:20px;box-shadow:0 28px 80px rgba(15,23,42,.28);padding:30px;color:#172033;position:relative}
296
+ .mci-auth-close{position:absolute;right:18px;top:16px;border:0;background:transparent;color:#64748b;font-size:24px;cursor:pointer}
297
+ .mci-auth-brand{display:flex;align-items:center;gap:10px;color:#135ee8;font-weight:800;font-size:15px;letter-spacing:.04em}.mci-auth-logo{width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,#1769ff,#05a8bf);display:grid;place-items:center;color:#fff;font-size:18px}
298
+ .mci-auth-title{font-size:25px;line-height:1.25;margin:22px 0 6px;font-weight:760}.mci-auth-sub{font-size:14px;color:#64748b;margin:0 0 22px}
299
+ .mci-auth-field{display:block;margin:0 0 14px}.mci-auth-field span{display:block;font-size:13px;font-weight:650;margin:0 0 7px}.mci-auth-field input{box-sizing:border-box;width:100%;height:46px;border:1px solid #dbe3ef;border-radius:11px;padding:0 13px;font-size:15px;outline:0;background:#f8fafc;color:#172033}.mci-auth-field input:focus{border-color:#1769ff;box-shadow:0 0 0 3px rgba(23,105,255,.12);background:#fff}
300
+ .mci-auth-captcha{display:grid;grid-template-columns:1fr 120px;gap:10px}.mci-auth-captcha img{width:120px;height:46px;object-fit:cover;border-radius:10px;border:1px solid #dbe3ef;cursor:pointer}
301
+ .mci-auth-captcha-field[hidden],.mci-auth-privacy[hidden]{display:none!important}
302
+ .mci-auth-privacy{display:flex;gap:8px;align-items:flex-start;color:#64748b;font-size:13px;margin:2px 0 14px}.mci-auth-privacy input{margin-top:2px}.mci-auth-message{min-height:20px;margin:0 0 8px;color:#dc2626;font-size:13px}.mci-auth-submit{width:100%;height:46px;border:0;border-radius:11px;background:linear-gradient(135deg,#1769ff,#0e88d8);color:#fff;font-size:15px;font-weight:700;cursor:pointer;box-shadow:0 10px 26px rgba(23,105,255,.25)}.mci-auth-submit:disabled{opacity:.65;cursor:wait}
303
+ .mci-auth-links{display:flex;justify-content:space-between;margin-top:16px;font-size:13px}.mci-auth-links a{color:#1769ff;text-decoration:none}.mci-auth-note{margin:18px 0 0;padding:12px 14px;border-radius:11px;background:#eff6ff;color:#475569;font-size:12px;line-height:1.65}
304
+ @media(prefers-color-scheme:dark){.mci-auth-card{background:#111827;color:#f8fafc;border-color:#334155}.mci-auth-field input{background:#0f172a;border-color:#334155;color:#f8fafc}.mci-auth-sub,.mci-auth-privacy,.mci-auth-note{color:#aab6c8}.mci-auth-note{background:#172554}}
305
+ `;
306
+ document.head.appendChild(style);
307
+ }
308
+
309
+ function ensureModal() {
310
+ if (modal) return modal;
311
+ injectStyles();
312
+ const mask = document.createElement('div');
313
+ mask.className = 'mci-auth-mask';
314
+ mask.hidden = true;
315
+ mask.innerHTML = `
316
+ <form class="mci-auth-card" autocomplete="on">
317
+ <button class="mci-auth-close" type="button" aria-label="关闭">×</button>
318
+ <div class="mci-auth-brand"><span class="mci-auth-logo">M</span><span>Microi 吾码</span></div>
319
+ <h2 class="mci-auth-title">登录后继续操作</h2>
320
+ <p class="mci-auth-sub">应用可以匿名浏览,保存的业务数据将安全归属到您的账号。</p>
321
+ <label class="mci-auth-field"><span>账号</span><input name="account" autocomplete="username" required placeholder="手机号或账号"></label>
322
+ <label class="mci-auth-field"><span>密码</span><input name="password" type="password" autocomplete="current-password" required placeholder="请输入登录密码"></label>
323
+ <label class="mci-auth-field mci-auth-captcha-field" hidden><span>验证码</span><div class="mci-auth-captcha"><input name="captcha" autocomplete="off" placeholder="图形验证码"><img alt="点击刷新验证码"></div></label>
324
+ <label class="mci-auth-privacy" hidden><input name="privacy" type="checkbox"><span></span></label>
325
+ <div class="mci-auth-message" role="alert"></div>
326
+ <button class="mci-auth-submit" type="submit">登录并继续</button>
327
+ <div class="mci-auth-links"><a data-link="register" target="_blank">注册账号</a><a data-link="forgot" target="_blank">忘记密码</a></div>
328
+ <p class="mci-auth-note">登录只在您保存、提交或修改数据时需要;继续浏览不会创建个人数据。</p>
329
+ </form>`;
330
+ document.body.appendChild(mask);
331
+ const form = mask.querySelector('form');
332
+ mask.querySelector('[data-link="register"]').href = registerUrl('register.html');
333
+ mask.querySelector('[data-link="forgot"]').href = registerUrl('login.html#forgot');
334
+ modal = {
335
+ mask,
336
+ form,
337
+ account: form.elements.account,
338
+ password: form.elements.password,
339
+ captcha: form.elements.captcha,
340
+ captchaField: mask.querySelector('.mci-auth-captcha-field'),
341
+ captchaImage: mask.querySelector('.mci-auth-captcha img'),
342
+ privacy: form.elements.privacy,
343
+ privacyLabel: mask.querySelector('.mci-auth-privacy'),
344
+ privacyText: mask.querySelector('.mci-auth-privacy span'),
345
+ message: mask.querySelector('.mci-auth-message'),
346
+ submit: mask.querySelector('.mci-auth-submit'),
347
+ close: mask.querySelector('.mci-auth-close')
348
+ };
349
+ modal.account.value = localStorage.getItem('microi_last_login_account') || '';
350
+ modal.captchaImage.addEventListener('click', () => void loadCaptcha());
351
+ return modal;
352
+ }
353
+
354
+ async function jsonRequest(path, options = {}) {
355
+ if (!nativeFetch) throw new Error('当前浏览器不支持网络请求');
356
+ const resolved = runtime();
357
+ const response = await nativeFetch(resolved.apiBase + path, options);
358
+ const text = await response.text();
359
+ let body;
360
+ try { body = JSON.parse(text); } catch (error) { body = { Code: 0, Msg: text || `HTTP ${response.status}` }; }
361
+ return { response, body };
362
+ }
363
+
364
+ async function loadSysConfig() {
365
+ const resolved = runtime();
366
+ const result = await jsonRequest(`/apiengine/platform-sys-config?OsClient=${encodeURIComponent(resolved.osClient)}`, {
367
+ method: 'POST',
368
+ headers: { 'Content-Type': 'application/json', osclient: resolved.osClient, did: V8.getDid(), apiengine: '1' },
369
+ body: JSON.stringify({ OsClient: resolved.osClient, _SearchEqual: { IsEnable: 1 } })
370
+ });
371
+ if (!result.body || Number(result.body.Code) !== 1) {
372
+ throw new Error((result.body && result.body.Msg) || '系统登录配置加载失败');
373
+ }
374
+ sysConfig = result.body.Data || {};
375
+ return sysConfig;
376
+ }
377
+
378
+ async function loadCaptcha() {
379
+ const view = ensureModal();
380
+ view.captchaField.hidden = true;
381
+ view.captchaImage.removeAttribute('src');
382
+ if (!flag(sysConfig.EnableCaptcha)) {
383
+ captchaId = '';
384
+ return;
385
+ }
386
+ const resolved = runtime();
387
+ const response = await nativeFetch(`${resolved.apiBase}/api/Captcha/GetCaptcha?OsClient=${encodeURIComponent(resolved.osClient)}`, {
388
+ headers: { osclient: resolved.osClient, did: V8.getDid() }
389
+ });
390
+ if (!response.ok) throw new Error('验证码加载失败');
391
+ captchaId = response.headers.get('captchaid') || '';
392
+ if (!captchaId) throw new Error('验证码标识缺失,请刷新后重试');
393
+ const captchaBlob = await response.blob();
394
+ if (!captchaBlob.size) throw new Error('验证码图片为空,请刷新后重试');
395
+ if (captchaObjectUrl) URL.revokeObjectURL(captchaObjectUrl);
396
+ captchaObjectUrl = URL.createObjectURL(captchaBlob);
397
+ await new Promise((resolve, reject) => {
398
+ const done = (callback) => {
399
+ window.clearTimeout(timer);
400
+ view.captchaImage.onload = null;
401
+ view.captchaImage.onerror = null;
402
+ callback();
403
+ };
404
+ const timer = window.setTimeout(() => done(() => reject(new Error('验证码图片加载超时'))), 8000);
405
+ view.captchaImage.onload = () => done(resolve);
406
+ view.captchaImage.onerror = () => done(() => reject(new Error('验证码图片解析失败')));
407
+ view.captchaImage.src = captchaObjectUrl;
408
+ });
409
+ view.captchaField.hidden = false;
410
+ }
411
+
412
+ function encryptPassword(password) {
413
+ const publicKey = window.MicroiLoginPublicKey
414
+ || String(sysConfig.LoginRsaPublicKey || '').replace(/\\n/g, '\n').trim()
415
+ || DEFAULT_PUBLIC_KEY;
416
+ if (!publicKey) return password;
417
+ const encryptor = new JSEncrypt();
418
+ encryptor.setPublicKey(publicKey);
419
+ const encrypted = encryptor.encrypt(password);
420
+ if (!encrypted) throw new Error('密码加密失败,请刷新后重试');
421
+ return encrypted;
422
+ }
423
+
424
+ async function performLogin() {
425
+ const view = ensureModal();
426
+ const account = String(view.account.value || '').trim();
427
+ const password = String(view.password.value || '');
428
+ if (!account) throw new Error('请输入账号');
429
+ if (!password) throw new Error('请输入密码');
430
+ if (flag(sysConfig.EnablePrivacyPolicy) && !view.privacy.checked) {
431
+ throw new Error(`请先勾选${sysConfig.PrivacyPolicyName || '同意隐私协议'}`);
432
+ }
433
+ const resolved = runtime();
434
+ const data = new URLSearchParams();
435
+ data.set('Account', account);
436
+ data.set('Pwd', encryptPassword(password));
437
+ data.set('OsClient', resolved.osClient);
438
+ data.set('_ClientType', V8.config.clientType || 'PC');
439
+ if (flag(sysConfig.EnableCaptcha)) {
440
+ data.set('_CaptchaId', captchaId);
441
+ data.set('_CaptchaValue', String(view.captcha.value || '').trim());
442
+ }
443
+ const result = await jsonRequest('/api/SysUser/Login', {
444
+ method: 'POST',
445
+ headers: {
446
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
447
+ osclient: resolved.osClient,
448
+ did: V8.getDid()
449
+ },
450
+ body: data.toString()
451
+ });
452
+ if (!result.body || Number(result.body.Code) !== 1) {
453
+ if (flag(sysConfig.EnableCaptcha)) await loadCaptcha();
454
+ throw new Error((result.body && result.body.Msg) || '登录失败');
455
+ }
456
+ const token = normalizeToken(
457
+ result.response.headers.get('authorization')
458
+ || result.response.headers.get('token')
459
+ || result.body.Token
460
+ || (result.body.Data && (result.body.Data.Token || result.body.Data.Authorization))
461
+ );
462
+ if (!token) throw new Error('登录成功但未收到 Token,请检查服务端登录响应');
463
+ V8.setToken(token);
464
+ V8.setUser(result.body.Data || {});
465
+ validatedSessionToken = token;
466
+ localStorage.setItem('microi_last_login_account', account);
467
+ window.dispatchEvent(new CustomEvent('microi:ai-app-login', { detail: result.body.Data || {} }));
468
+ return result.body.Data || {};
469
+ }
470
+
471
+ async function validateCachedLogin() {
472
+ const token = normalizeToken(V8.getToken());
473
+ if (!token) return null;
474
+ const cachedUser = V8.getUser() || {};
475
+ if (validatedSessionToken === token && authenticatedIdentity()) return cachedUser;
476
+
477
+ const resolved = runtime();
478
+ const result = await jsonRequest(`/apiengine/platform-current-user?OsClient=${encodeURIComponent(resolved.osClient)}`, {
479
+ method: 'POST',
480
+ headers: {
481
+ 'Content-Type': 'application/json',
482
+ osclient: resolved.osClient,
483
+ did: V8.getDid(),
484
+ apiengine: '1',
485
+ Token: token,
486
+ Authorization: `Bearer ${token}`
487
+ },
488
+ body: JSON.stringify({ OsClient: resolved.osClient, _ClientType: V8.config.clientType || 'PC' })
489
+ });
490
+ const refreshedToken = normalizeToken(
491
+ result.response.headers.get('authorization') || result.response.headers.get('token') || token
492
+ );
493
+ const user = result.body && Number(result.body.Code) === 1 ? (result.body.Data || {}) : {};
494
+ const userId = String(user.Id || user.id || user.UserId || '').trim();
495
+ if (result.response.ok && userId) {
496
+ if (refreshedToken) V8.setToken(refreshedToken);
497
+ V8.setUser(user);
498
+ validatedSessionToken = refreshedToken || token;
499
+ return user;
500
+ }
501
+
502
+ V8.clearToken();
503
+ validatedSessionToken = '';
504
+ return null;
505
+ }
506
+
507
+ async function showLogin() {
508
+ const currentUser = await validateCachedLogin();
509
+ if (currentUser) return currentUser;
510
+ if (loginPromise) return loginPromise;
511
+ const view = ensureModal();
512
+ document.documentElement.dataset.microiAiAuthState = 'prompt';
513
+ view.mask.hidden = false;
514
+ view.message.textContent = '';
515
+ view.submit.disabled = false;
516
+ view.account.focus();
517
+ loginPromise = new Promise(async (resolve, reject) => {
518
+ let finished = false;
519
+ const cleanup = () => {
520
+ view.form.removeEventListener('submit', submit);
521
+ view.close.removeEventListener('click', cancel);
522
+ view.mask.removeEventListener('click', outside);
523
+ };
524
+ const cancel = () => {
525
+ if (finished) return;
526
+ finished = true;
527
+ cleanup();
528
+ view.mask.hidden = true;
529
+ reject(new Error('已取消登录'));
530
+ };
531
+ const outside = (event) => { if (event.target === view.mask) cancel(); };
532
+ const submit = async (event) => {
533
+ event.preventDefault();
534
+ view.submit.disabled = true;
535
+ view.message.textContent = '';
536
+ try {
537
+ const user = await performLogin();
538
+ finished = true;
539
+ cleanup();
540
+ view.mask.hidden = true;
541
+ resolve(user);
542
+ } catch (error) {
543
+ view.message.textContent = error && error.message ? error.message : String(error);
544
+ } finally {
545
+ view.submit.disabled = false;
546
+ }
547
+ };
548
+ view.form.addEventListener('submit', submit);
549
+ view.close.addEventListener('click', cancel);
550
+ view.mask.addEventListener('click', outside);
551
+ try {
552
+ await loadSysConfig();
553
+ view.privacyLabel.hidden = !flag(sysConfig.EnablePrivacyPolicy);
554
+ view.privacyText.textContent = sysConfig.PrivacyPolicyName || '同意隐私协议';
555
+ await loadCaptcha();
556
+ } catch (error) {
557
+ view.message.textContent = error && error.message ? error.message : String(error);
558
+ }
559
+ }).finally(() => { loginPromise = null; });
560
+ return loginPromise;
561
+ }
562
+
563
+ async function fetchWithAuth(input, init = {}, retried = false) {
564
+ const rawUrl = typeof input === 'string' || input instanceof URL ? String(input) : input.url;
565
+ const appRequest = isAppEngineUrl(rawUrl);
566
+ if (!appRequest) return nativeFetch(input, init);
567
+ const writeRequest = isLikelyWrite(rawUrl) || bodyLikelyWrites(init.body, init.headers);
568
+ const promptAllowed = hasRecentUserIntent();
569
+ document.documentElement.dataset.microiLastEngine = appEngineKey(rawUrl) || '';
570
+ document.documentElement.dataset.microiWriteDetected = writeRequest ? 'true' : 'false';
571
+ document.documentElement.dataset.microiHasToken = V8.getToken() ? 'true' : 'false';
572
+ const headers = setRequestHeaders(init.headers || (input && input.headers));
573
+ const target = typeof input === 'string' || input instanceof URL ? rewriteApiUrl(rawUrl) : input;
574
+ const requestBody = applyIdentityToBody(init.body, headers);
575
+ const response = await nativeFetch(target, { ...init, headers, body: requestBody });
576
+ let responseBody = null;
577
+ try { responseBody = await response.clone().json(); } catch (error) {}
578
+ if (!retried && bodyAuthExpired(responseBody, response.status) && promptAllowed) {
579
+ V8.clearToken();
580
+ await showLogin();
581
+ return fetchWithAuth(input, init, true);
582
+ }
583
+ const returnedToken = normalizeToken(response.headers.get('authorization') || response.headers.get('token'));
584
+ if (returnedToken) V8.setToken(returnedToken);
585
+ return response;
586
+ }
587
+
588
+ function installFetchBridge() {
589
+ if (!nativeFetch || window.fetch.__microiAiAuthBridge) return;
590
+ const bridged = function (input, init) { return fetchWithAuth(input, init || {}); };
591
+ bridged.__microiAiAuthBridge = true;
592
+ window.fetch = bridged;
593
+ }
594
+
595
+ function installXhrBridge() {
596
+ if (!NativeXHR || NativeXHR.prototype.__microiAiAuthBridge) return;
597
+ const open = NativeXHR.prototype.open;
598
+ const send = NativeXHR.prototype.send;
599
+ const setRequestHeader = NativeXHR.prototype.setRequestHeader;
600
+ NativeXHR.prototype.open = function (method, url, async, user, password) {
601
+ this.__microiMethod = method;
602
+ this.__microiUrl = String(url || '');
603
+ return open.call(this, method, rewriteApiUrl(url), async === undefined ? true : async, user, password);
604
+ };
605
+ NativeXHR.prototype.send = function (body) {
606
+ if (!isAppEngineUrl(this.__microiUrl)) return send.call(this, body);
607
+ const promptAllowed = hasRecentUserIntent();
608
+ const execute = () => {
609
+ const resolved = runtime();
610
+ setRequestHeader.call(this, 'osclient', resolved.osClient);
611
+ setRequestHeader.call(this, 'did', V8.getDid());
612
+ const token = normalizeToken(V8.getToken());
613
+ if (token) {
614
+ setRequestHeader.call(this, 'Token', token);
615
+ setRequestHeader.call(this, 'Authorization', `Bearer ${token}`);
616
+ }
617
+ this.addEventListener('load', () => {
618
+ try {
619
+ const result = JSON.parse(this.responseText || '{}');
620
+ if (bodyAuthExpired(result, this.status) && promptAllowed) {
621
+ V8.clearToken();
622
+ void showLogin();
623
+ }
624
+ } catch (error) {}
625
+ }, { once: true });
626
+ const requestBody = applyIdentityToBody(body, { 'content-type': this.__microiContentType || '' });
627
+ send.call(this, requestBody);
628
+ };
629
+ execute();
630
+ };
631
+ NativeXHR.prototype.setRequestHeader = function (name, value) {
632
+ const normalized = String(name || '').toLowerCase();
633
+ if (normalized === 'content-type') this.__microiContentType = String(value || '');
634
+ if (normalized === 'osclient') return;
635
+ return setRequestHeader.call(this, name, value);
636
+ };
637
+ NativeXHR.prototype.__microiAiAuthBridge = true;
638
+ }
639
+
640
+ runtime();
641
+ installUserIntentTracking();
642
+ installFetchBridge();
643
+ installXhrBridge();
644
+ window.MicroiV8 = window.MicroiV8 || V8;
645
+ window.MicroiAiAppAuth = {
646
+ login: showLogin,
647
+ logout() { validatedSessionToken = ''; V8.clearToken(); window.dispatchEvent(new Event('microi:ai-app-logout')); },
648
+ getUser: () => V8.getUser(),
649
+ getToken: () => V8.getToken(),
650
+ getDid: () => V8.getDid(),
651
+ runtime
652
+ };