openxiangda 1.0.158 → 1.0.160

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
@@ -439,7 +439,7 @@ await ctx.organization.accounts.resetPassword("user-alice", {
439
439
  })
440
440
  ```
441
441
 
442
- 应用登录能力通过 auth resource 和 runtime SDK 提供:登录配置放在 `src/resources/auth/<code>.json`,默认 React SPA 模板已包含 `/view/:appType/login`,自定义页面可使用 `createAuthClient({ appType, servicePrefix })` 或 `LoginPage` / `useAuth` from `openxiangda/runtime/react`。手机号验证码、CAS/SSO 或其他外部登录可以由 App Function provider 校验外部凭证,但 provider 只能返回 `phone` / `email` / `externalId` / `unionId` 等身份声明;平台后端按 auth resource 策略执行账号匹配、绑定、创建或拒绝,并由平台统一签发 token/cookie。默认注册策略是拒绝,开启自动注册或白名单注册前必须确认身份匹配键、默认角色、验证码 TTL/频率/失败次数、审计字段和错误文案策略。
442
+ 应用登录能力通过 auth resource 和 runtime SDK 提供:登录配置放在 `src/resources/auth/<code>.json`,默认 React SPA 模板已包含 `/view/:appType/login`,自定义页面可使用 `createAuthClient({ appType, servicePrefix })` 或 `LoginPage` / `useAuth` from `openxiangda/runtime/react`。钉钉登录默认使用 `dingtalkFlow="auto"`:只有明确位于钉钉容器且 JSAPI 可用时才走免登码,普通浏览器会请求应用级 OAuth 地址并跳转钉钉认证页;自定义页可调用 `getDingTalkOAuthUrl({ returnUrl })` 接入相同能力。手机号验证码、CAS/SSO 或其他外部登录可以由 App Function provider 校验外部凭证,但 provider 只能返回 `phone` / `email` / `externalId` / `unionId` 等身份声明;平台后端按 auth resource 策略执行账号匹配、绑定、创建或拒绝,并由平台统一签发 token/cookie。默认注册策略是拒绝,开启自动注册或白名单注册前必须确认身份匹配键、默认角色、验证码 TTL/频率/失败次数、审计字段和错误文案策略。
443
443
 
444
444
  常用数据视图命令:
445
445
 
package/lib/cli.js CHANGED
@@ -3568,8 +3568,15 @@ async function authConfig(args) {
3568
3568
  const result = {
3569
3569
  methods: [
3570
3570
  { type: 'password', requiredFields: ['username', 'password'] },
3571
+ {
3572
+ type: 'dingtalk',
3573
+ requiredFields: [],
3574
+ defaultFlow: 'auto',
3575
+ supportedFlows: ['auto', 'jsapi', 'oauth'],
3576
+ },
3571
3577
  { type: 'phone_code', requiredFields: ['phone', 'code'], provider: 'functionCode 或 connector' },
3572
3578
  { type: 'sso', requiredFields: ['provider', 'callbackUrl'] },
3579
+ { type: 'guest', requiredFields: [] },
3573
3580
  ],
3574
3581
  defaultRegistration: { mode: 'reject' },
3575
3582
  defaultBinding: { mode: 'auto' },
@@ -5924,6 +5931,8 @@ async function runDirectRequest(config, target, flags, request, options = {}) {
5924
5931
  method: request.method || 'GET',
5925
5932
  body: request.body,
5926
5933
  strictEnvelope: request.strictEnvelope,
5934
+ timeoutMs:
5935
+ request.timeoutMs || flags['timeout-ms'] || flags.timeout || undefined,
5927
5936
  };
5928
5937
  let data;
5929
5938
  if (request.auth === false) {
package/lib/http.js CHANGED
@@ -1,8 +1,15 @@
1
1
  const { formatFetchError, maskText } = require('./utils');
2
2
 
3
- const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
3
+ const DEFAULT_REQUEST_TIMEOUT_MS = normalizeTimeoutMs(
4
+ process.env.OPENXIANGDA_REQUEST_TIMEOUT_MS,
5
+ 120000
6
+ );
7
+ const DEFAULT_CONNECT_TIMEOUT_MS = normalizeTimeoutMs(
8
+ process.env.OPENXIANGDA_CONNECT_TIMEOUT_MS,
9
+ 30000
10
+ );
4
11
 
5
- configureFetchDispatcher(DEFAULT_REQUEST_TIMEOUT_MS);
12
+ configureFetchDispatcher(DEFAULT_CONNECT_TIMEOUT_MS);
6
13
 
7
14
  async function requestJson(baseUrl, apiPath, options = {}) {
8
15
  if (typeof fetch !== 'function') {
@@ -82,17 +89,106 @@ module.exports = {
82
89
 
83
90
  function configureFetchDispatcher(timeoutMs) {
84
91
  try {
85
- // Node fetch uses undici internally. Setting the global dispatcher prevents
86
- // large resource operations from failing on undici's 10s connect default.
87
- const { Agent, setGlobalDispatcher } = require('undici');
88
- setGlobalDispatcher(
89
- new Agent({
90
- connect: {
91
- timeout: timeoutMs,
92
- },
93
- })
92
+ // Node fetch uses undici internally but does not honor proxy environment
93
+ // variables by itself. Keep NO_PROXY-aware routing while extending the
94
+ // connect timeout for private and high-latency deployments.
95
+ const { Agent, ProxyAgent, setGlobalDispatcher } = require('undici');
96
+ const connect = { timeout: timeoutMs };
97
+ const directAgent = new Agent({ connect });
98
+ const httpProxyUrl = readProxyUrl('HTTP_PROXY');
99
+ const httpsProxyUrl = readProxyUrl('HTTPS_PROXY') || httpProxyUrl;
100
+ const allProxyUrl = readProxyUrl('ALL_PROXY');
101
+ const httpProxyAgent = createProxyAgent(
102
+ ProxyAgent,
103
+ httpProxyUrl || allProxyUrl,
104
+ connect
94
105
  );
106
+ const httpsProxyAgent = createProxyAgent(
107
+ ProxyAgent,
108
+ httpsProxyUrl || allProxyUrl,
109
+ connect
110
+ );
111
+
112
+ setGlobalDispatcher({
113
+ dispatch(options, handler) {
114
+ const origin = new URL(String(options.origin));
115
+ if (shouldBypassProxy(origin)) {
116
+ return directAgent.dispatch(options, handler);
117
+ }
118
+ const proxyAgent =
119
+ origin.protocol === 'https:' ? httpsProxyAgent : httpProxyAgent;
120
+ return (proxyAgent || directAgent).dispatch(options, handler);
121
+ },
122
+ close() {
123
+ return closeDispatchers([
124
+ directAgent,
125
+ httpProxyAgent,
126
+ httpsProxyAgent,
127
+ ]);
128
+ },
129
+ destroy(error) {
130
+ return destroyDispatchers(
131
+ [directAgent, httpProxyAgent, httpsProxyAgent],
132
+ error
133
+ );
134
+ },
135
+ });
95
136
  } catch {
96
137
  // Keep working on runtimes where undici is not directly importable.
97
138
  }
98
139
  }
140
+
141
+ function readProxyUrl(name) {
142
+ const value = process.env[name] || process.env[name.toLowerCase()];
143
+ return String(value || '').trim();
144
+ }
145
+
146
+ function createProxyAgent(ProxyAgent, uri, connect) {
147
+ if (!uri) return null;
148
+ return new ProxyAgent({ uri, connect });
149
+ }
150
+
151
+ function shouldBypassProxy(origin) {
152
+ const noProxy = readProxyUrl('NO_PROXY');
153
+ if (!noProxy) return false;
154
+ const hostname = origin.hostname.replace(/^\[|\]$/g, '').toLowerCase();
155
+ const port = origin.port || (origin.protocol === 'https:' ? '443' : '80');
156
+
157
+ return noProxy.split(',').some(rawEntry => {
158
+ const entry = rawEntry.trim().toLowerCase();
159
+ if (!entry) return false;
160
+ if (entry === '*') return true;
161
+
162
+ const normalized = entry.replace(/^https?:\/\//, '');
163
+ const separator = normalized.lastIndexOf(':');
164
+ const hasSinglePortSeparator =
165
+ separator > -1 && normalized.indexOf(':') === separator;
166
+ const entryHost = (hasSinglePortSeparator
167
+ ? normalized.slice(0, separator)
168
+ : normalized
169
+ ).replace(/^\[|\]$/g, '');
170
+ const entryPort = hasSinglePortSeparator
171
+ ? normalized.slice(separator + 1)
172
+ : '';
173
+ if (entryPort && entryPort !== port) return false;
174
+
175
+ const suffix = entryHost.replace(/^\*?\./, '');
176
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
177
+ });
178
+ }
179
+
180
+ async function closeDispatchers(dispatchers) {
181
+ await Promise.all(
182
+ uniqueDispatchers(dispatchers).map(dispatcher => dispatcher.close())
183
+ );
184
+ }
185
+
186
+ async function destroyDispatchers(dispatchers, error) {
187
+ await Promise.all(
188
+ uniqueDispatchers(dispatchers).map(dispatcher => dispatcher.destroy(error))
189
+ );
190
+ }
191
+
192
+ function uniqueDispatchers(dispatchers) {
193
+ return [...new Set(dispatchers.filter(Boolean))];
194
+ }
@@ -29,7 +29,7 @@ OpenXiangda supports two workspace modes. Classic `sy-lowcode-app-workspace` pub
29
29
  | 审批流程 / workflow / 流程节点 / JS_CODE | `openxiangda-workflow-automation` | `openxiangda workflow validate / create / publish` |
30
30
  | 自动化 / 定时任务 / 提交触发 / cron | `openxiangda-workflow-automation` | `openxiangda automation validate / create / publish / enable` |
31
31
  | App Function / function_call / 可复用后端逻辑 | `openxiangda-workflow-automation` | declare `src/resources/functions/<code>.json` + `src/functions/<code>/index.ts` |
32
- | 应用登录 / Auth SDK / 手机号验证码 / CAS / 钉钉免登 | `openxiangda-architecture-design` + `openxiangda-page` | design security gate first, then declare `src/resources/auth/<code>.json` and use `createAuthClient` / `LoginPage` |
32
+ | 应用登录 / Auth SDK / 手机号验证码 / CAS / 钉钉登录或免登 | `openxiangda-architecture-design` + `openxiangda-page` | design security gate first, then declare `src/resources/auth/<code>.json` and use `createAuthClient` / `LoginPage` |
33
33
  | 账号权限 / 角色 / 权限组 / 字段权限 / 数据范围 / RBAC | `openxiangda-permission-settings` | `openxiangda design gates --topic permissions --json` → choose permission mode → `openxiangda permission ...` / `openxiangda settings ...` |
34
34
  | 平台部门 / 平台账号 / 重置密码 / 组织账号管理 | `openxiangda-architecture-design` + `openxiangda-page` | check `openxiangda organization capabilities --json`; use `sdk.organization` / `ctx.organization` only after `app:organization:manage` is granted |
35
35
  | 外部人员无需登录访问 / 公开报名 / 公开查询 / public page | `openxiangda-architecture-design` + `openxiangda-permission-settings` + `openxiangda-page` | 先确认公开范围、外部角色、ticket、grants;再声明 `routes` + `public-access`,并用 `OpenXiangdaProvider` + `OpenXiangdaPageProvider` + `PublicAccessGate` |
@@ -173,6 +173,7 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
173
173
  - When a role such as 校区管理员 will create app roles, assign role members, grant role permissions, maintain permission groups, or manage organization accounts, its role manifest must declare the needed `apiPermissionCodes` such as `app:role:manage`, `app:page-permission-group:manage`, `app:form-permission-group:manage`, and `app:organization:manage`. If that administrator creates another administrator role, the new role must also declare its own `apiPermissionCodes`; permissions are not inherited from the creator.
174
174
  - If `resource publish` reports a missing API permission point such as `app:role:manage`, treat it as a platform seed/data problem. The platform tenant must have the permission code in `api_permissions`; do not remove the role manifest permission just to make publish pass.
175
175
  - For application login, declare auth resources in `src/resources/auth/<code>.json` and publish them with `openxiangda resource publish`. App Function auth providers may validate external credentials and return only an identity assertion; they must never issue tokens, set cookies, or write platform user/binding tables directly. The platform auth service decides create/bind/reject and issues tokens.
176
+ - For DingTalk application login, prefer `flow: "auto"`: `LoginPage` uses JSAPI only inside a confirmed DingTalk container and otherwise starts platform-managed browser OAuth. Custom login pages call `getDingTalkOAuthUrl({ returnUrl })`; they must not assemble DingTalk OAuth URLs, manage callback state, or embed tenant secrets themselves.
176
177
  - For external/no-login pages in React SPA apps, declare `src/resources/routes/<code>.json` and `src/resources/public-access/<code>.json`, put the page under `/view/:appType/public/*`, and use `PublicAccessGate` or `createPublicAccessClient`. The route tree must also include `OpenXiangdaPageProvider` inside `OpenXiangdaProvider` before any `usePageSdk()` / `usePageContext()` / `useDataSource()` call; otherwise pages throw `usePageSdkStore 必须在 PageProvider 内使用`. Always provide `fallback` and `errorFallback` for public routes so slow networks, missing tickets, or expired tickets do not render blank pages. Public guest access to forms, dataViews, functions, and connectors is denied unless policy `grants` explicitly names the resource; backend permission groups still apply.
177
178
  - When verifying public access, parse the response body and reject string business errors such as `PUBLIC_GRANT_DENIED` even if the HTTP status is 200. Do not use `response.ok` alone as the success condition.
178
179
  - Before scaffolding a real app, complex page, data management page, portal shell, status lifecycle, role governance, or automation, read `references/best-practices.md` and pick the architecture first. Prefer copying the relevant pattern from `examples/best-practices/` into `src/` instead of generating a single large page file.
@@ -186,7 +186,7 @@ Default: reusable calculations and validations become App Functions; one-off bac
186
186
 
187
187
  Ask these before generating any login/auth design:
188
188
 
189
- - Which methods are enabled: password, DingTalk in-app free login, CAS/SSO, phone code, guest?
189
+ - Which methods are enabled: password, DingTalk browser OAuth/in-app free login, CAS/SSO, phone code, guest?
190
190
  - What is the phone registration policy: reject, auto-create, bind existing only, whitelist, or manual approval?
191
191
  - What are the identity match keys and priorities: `phone`, `email`, `externalId`, `unionId`, `jobNumber`, `username`?
192
192
  - Which App Function provider validates each external credential, and what identity assertion fields must it return?
@@ -280,6 +280,7 @@ For simple CRUD/admin lists, design can proceed with the appropriate OpenXiangda
280
280
  - Auth resources use stable local codes under `src/resources/auth/`.
281
281
  - Use `/view/:appType/login` or `LoginPage` from `openxiangda/runtime/react` for default React login.
282
282
  - Use `createAuthClient({ appType, servicePrefix })` for custom React login pages.
283
+ - DingTalk `flow: "auto"` uses JSAPI only in a confirmed DingTalk container and otherwise starts platform-managed browser OAuth. The platform owns callback state and secrets; app code supplies only an app-local `returnUrl` through `getDingTalkOAuthUrl`.
283
284
  - Phone-code providers are App Functions. They validate send/verify events and return identity assertions; they do not issue tokens or mutate platform users.
284
285
  - New-user registration must be explicit, with default roles and identity conflict behavior documented.
285
286
 
@@ -78,6 +78,33 @@ Body:
78
78
 
79
79
  Requires Bearer token. Revokes the current CLI token session.
80
80
 
81
+ ## App Runtime Auth
82
+
83
+ ### POST `/apps/:appType/auth/dingtalk/oauth/start`
84
+
85
+ Starts platform-managed DingTalk browser OAuth for an application. This endpoint is called without an existing app login session. The platform creates and stores one-time callback state; application code must not build the DingTalk authorization URL itself.
86
+
87
+ Body:
88
+
89
+ ```json
90
+ {
91
+ "returnUrl": "/view/APP_EXAMPLE/admin"
92
+ }
93
+ ```
94
+
95
+ `returnUrl` is optional and, when present, must stay under the current application's `/view/:appType/*` routes.
96
+
97
+ Response data:
98
+
99
+ ```json
100
+ {
101
+ "loginUrl": "https://login.dingtalk.com/oauth2/auth?...",
102
+ "expiresIn": 600
103
+ }
104
+ ```
105
+
106
+ The platform-owned callback consumes the one-time state, creates the authenticated platform session, and redirects to the validated `returnUrl`. React applications normally use `LoginPage dingtalkFlow="auto"`; custom login pages call `createAuthClient(...).getDingTalkOAuthUrl({ returnUrl })` and navigate to `loginUrl`.
107
+
81
108
  ## App Snapshot
82
109
 
83
110
  ### GET `/apps/`
@@ -247,7 +247,7 @@ Application auth:
247
247
  import { LoginPage, useAuth, useLoginMethods } from "openxiangda/runtime/react";
248
248
 
249
249
  export function DefaultLogin() {
250
- return <LoginPage />;
250
+ return <LoginPage dingtalkFlow="auto" />;
251
251
  }
252
252
 
253
253
  export function CustomPhoneLogin() {
@@ -269,6 +269,14 @@ import { createAuthClient } from "openxiangda/runtime";
269
269
 
270
270
  const auth = createAuthClient({ appType, servicePrefix: "/service" });
271
271
  const methods = await auth.getMethods();
272
+
273
+ async function startDingTalkLogin() {
274
+ const { loginUrl } = await auth.getDingTalkOAuthUrl({
275
+ returnUrl: `/view/${encodeURIComponent(appType)}/admin`,
276
+ });
277
+ window.location.assign(loginUrl);
278
+ }
279
+
272
280
  const sent = await auth.sendPhoneCode({ phone, purpose: "login" });
273
281
  const result = await auth.phoneCodeLogin({
274
282
  phone,
@@ -277,6 +285,8 @@ const result = await auth.phoneCodeLogin({
277
285
  });
278
286
  ```
279
287
 
288
+ `dingtalkFlow="auto"` is the default. It uses DingTalk JSAPI only when both the container and `requestAuthCode` are available; otherwise it starts browser OAuth. Explicit `dingtalkFlow="jsapi"` or `"oauth"` overrides the auth method's `flow` setting. Browser OAuth is a full-page redirect whose callback is handled by the platform, so custom pages should pass an app-local `/view/:appType/*` return URL and must not implement their own callback or OAuth `state`. `LoginPage` normalizes a same-origin absolute guard callback to that relative form and rejects cross-origin or other-app callbacks before making the OAuth start request.
289
+
280
290
  For phone-code auth, the App Function provider receives `event`, `appType`, `method`, `credential`, `requestId`, `request`, and `challenge`. It may return `{ ok, providerState }` for send and `{ ok, identity }` for verify. It must not return `token`, `accessToken`, `refreshToken`, cookies, or mutate platform account tables.
281
291
 
282
292
  Public access route:
@@ -74,7 +74,7 @@ openxiangda permission role-update external_visitor --json-file src/resources/ro
74
74
  "configJson": {
75
75
  "methods": [
76
76
  { "type": "password", "enabled": true, "label": "账号密码" },
77
- { "type": "dingtalk", "enabled": true, "label": "钉钉免登" },
77
+ { "type": "dingtalk", "enabled": true, "label": "钉钉登录", "flow": "auto" },
78
78
  { "type": "sso", "enabled": true, "label": "CAS", "protocol": "cas" },
79
79
  {
80
80
  "type": "phone_code",
@@ -127,7 +127,7 @@ React 默认页:
127
127
  import { LoginPage } from "openxiangda/runtime/react";
128
128
 
129
129
  export default function AppLogin() {
130
- return <LoginPage />;
130
+ return <LoginPage dingtalkFlow="auto" />;
131
131
  }
132
132
  ```
133
133
 
@@ -138,10 +138,20 @@ import { createAuthClient } from "openxiangda/runtime";
138
138
 
139
139
  const auth = createAuthClient({ appType, servicePrefix: "/service" });
140
140
  const methods = await auth.getMethods();
141
+
142
+ async function startDingTalkLogin() {
143
+ const { loginUrl } = await auth.getDingTalkOAuthUrl({
144
+ returnUrl: `/view/${encodeURIComponent(appType)}/admin`,
145
+ });
146
+ window.location.assign(loginUrl);
147
+ }
148
+
141
149
  const sent = await auth.sendPhoneCode({ phone, purpose: "login" });
142
150
  await auth.phoneCodeLogin({ phone, code, challengeId: sent.challengeId });
143
151
  ```
144
152
 
153
+ 钉钉 `flow: "auto"` 会在确认处于钉钉容器且 JSAPI 可用时走免登,否则走平台托管的浏览器 OAuth。`returnUrl` 只能是当前应用的 `/view/:appType/*` 路径;应用不要自行拼装钉钉授权 URL、保存 OAuth `state` 或实现回调页。
154
+
145
155
  设计前必须确认启用方式、注册策略、身份匹配键、provider 边界、默认权限、安全参数、第三方配置归属。
146
156
 
147
157
  ## 1. Storage — `src/resources/storage/<code>.json`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.158",
3
+ "version": "1.0.160",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -62,7 +62,7 @@
62
62
  "scripts": {
63
63
  "build:sdk": "tsup --config packages/sdk/tsup.config.ts",
64
64
  "check": "node --check bin/openxiangda.js && node --check lib/*.js && node --check packages/sdk/src/build-source/src/cli.mjs && node --check packages/sdk/src/build-source/scripts/*.mjs && node --check packages/sdk/src/build-source/scripts/utils/*.mjs",
65
- "test": "npm run check && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs",
65
+ "test": "npm run check && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs",
66
66
  "prepack": "npm run build:sdk",
67
67
  "test:profile-isolation": "bash scripts/profile-isolation-smoke.sh",
68
68
  "test:resource-plan": "node scripts/resource-plan-smoke.mjs",
@@ -2284,6 +2284,10 @@ var createAuthClient = ({
2284
2284
  getMethods: () => request(appAuthPath("/methods")),
2285
2285
  passwordLogin: (input) => request(appAuthPath("/password/login"), postJson(input)),
2286
2286
  dingtalkLogin: (input) => request(appAuthPath("/dingtalk/login"), postJson(input)),
2287
+ getDingTalkOAuthUrl: (input) => request(
2288
+ appAuthPath("/dingtalk/oauth/start"),
2289
+ postJson(input || {})
2290
+ ),
2287
2291
  guestLogin: (input) => request(appAuthPath("/guest/login"), postJson(input || {})),
2288
2292
  sendPhoneCode: (input) => request(appAuthPath("/phone-code/send"), postJson(input)),
2289
2293
  phoneCodeLogin: (input) => request(appAuthPath("/phone-code/login"), postJson(input)),
@@ -8274,6 +8278,7 @@ var useAuth = (options = {}) => {
8274
8278
  getMethods: client.getMethods,
8275
8279
  passwordLogin: client.passwordLogin,
8276
8280
  dingtalkLogin: client.dingtalkLogin,
8281
+ getDingTalkOAuthUrl: client.getDingTalkOAuthUrl,
8277
8282
  guestLogin: client.guestLogin,
8278
8283
  sendPhoneCode: client.sendPhoneCode,
8279
8284
  phoneCodeLogin: client.phoneCodeLogin,
@@ -8340,6 +8345,7 @@ var LoginPage = ({
8340
8345
  className,
8341
8346
  style,
8342
8347
  defaultMethod,
8348
+ dingtalkFlow,
8343
8349
  redirectUrl,
8344
8350
  redirectOnSuccess = true,
8345
8351
  onSuccess,
@@ -8497,6 +8503,21 @@ var LoginPage = ({
8497
8503
  setSubmitting(true);
8498
8504
  setError(null);
8499
8505
  try {
8506
+ const flow = resolveDingTalkLoginFlow(dingtalkFlow, dingtalkMethod);
8507
+ if (flow === "oauth") {
8508
+ const returnUrl = resolveDingTalkOAuthReturnUrl(
8509
+ auth.client.appType,
8510
+ redirectUrl || getCallbackUrl() || void 0
8511
+ );
8512
+ const result = await auth.getDingTalkOAuthUrl({
8513
+ returnUrl
8514
+ });
8515
+ if (typeof window === "undefined") {
8516
+ throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u8DF3\u8F6C\u9489\u9489\u767B\u5F55");
8517
+ }
8518
+ window.location.assign(result.loginUrl);
8519
+ return;
8520
+ }
8500
8521
  const code = await requestDingTalkCode(dingtalkMethod);
8501
8522
  await handleSuccess(
8502
8523
  await auth.dingtalkLogin({
@@ -8613,7 +8634,7 @@ var LoginPage = ({
8613
8634
  icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_icons10.QrcodeOutlined, {}),
8614
8635
  loading: submitting,
8615
8636
  onClick: handleDingTalkLogin,
8616
- children: dingtalkMethod.label || "\u9489\u9489\u514D\u767B"
8637
+ children: dingtalkMethod.label || "\u9489\u9489\u767B\u5F55"
8617
8638
  }
8618
8639
  ) : null,
8619
8640
  ssoMethod ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
@@ -8764,6 +8785,50 @@ var getString = (value, key) => {
8764
8785
  const result = value[key];
8765
8786
  return typeof result === "string" ? result : void 0;
8766
8787
  };
8788
+ var resolveDingTalkLoginFlow = (explicitFlow, method) => {
8789
+ const configuredFlow = normalizeDingTalkLoginFlow(getString(method, "flow"));
8790
+ const flow = explicitFlow || configuredFlow || "auto";
8791
+ if (flow !== "auto") return flow;
8792
+ return canUseDingTalkJsApi() ? "jsapi" : "oauth";
8793
+ };
8794
+ var resolveDingTalkOAuthReturnUrl = (appType, returnUrl, origin = getCurrentOrigin()) => {
8795
+ const normalizedAppType = String(appType || "").trim();
8796
+ if (!normalizedAppType) throw new Error("appType \u4E0D\u80FD\u4E3A\u7A7A");
8797
+ const appRoot = `/view/${encodeURIComponent(normalizedAppType)}`;
8798
+ const rawReturnUrl = String(returnUrl || "").trim();
8799
+ if (!rawReturnUrl) return appRoot;
8800
+ if (!origin || rawReturnUrl.startsWith("//")) {
8801
+ throw createInvalidDingTalkReturnUrlError(appRoot);
8802
+ }
8803
+ let baseUrl;
8804
+ let targetUrl;
8805
+ try {
8806
+ baseUrl = new URL(origin);
8807
+ targetUrl = new URL(rawReturnUrl, baseUrl);
8808
+ } catch {
8809
+ throw createInvalidDingTalkReturnUrlError(appRoot);
8810
+ }
8811
+ if (targetUrl.origin !== baseUrl.origin || targetUrl.username || targetUrl.password || targetUrl.pathname !== appRoot && !targetUrl.pathname.startsWith(`${appRoot}/`)) {
8812
+ throw createInvalidDingTalkReturnUrlError(appRoot);
8813
+ }
8814
+ return `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`;
8815
+ };
8816
+ var createInvalidDingTalkReturnUrlError = (appRoot) => new Error(`\u9489\u9489\u767B\u5F55\u8FD4\u56DE\u5730\u5740\u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5E94\u7528 ${appRoot} \u4E0B`);
8817
+ var normalizeDingTalkLoginFlow = (value) => value === "auto" || value === "jsapi" || value === "oauth" ? value : void 0;
8818
+ var canUseDingTalkJsApi = () => {
8819
+ if (typeof window === "undefined") return false;
8820
+ const dd = window.dd;
8821
+ const requestAuthCode = dd?.runtime?.permission?.requestAuthCode || dd?.requestAuthCode;
8822
+ if (typeof requestAuthCode !== "function") return false;
8823
+ const platform = String(dd?.env?.platform || "").trim().toLowerCase();
8824
+ if (platform === "notindingtalk") return false;
8825
+ if (["android", "iphone", "ipad", "ios", "pc", "mac", "windows", "win"].includes(
8826
+ platform
8827
+ )) {
8828
+ return true;
8829
+ }
8830
+ return /dingtalk/i.test(window.navigator?.userAgent || "");
8831
+ };
8767
8832
  var requestDingTalkCode = (method) => {
8768
8833
  const dd = typeof window === "undefined" ? void 0 : window.dd;
8769
8834
  const corpId = getString(method, "corpId");
@@ -8808,6 +8873,7 @@ var readChallengeId = (challenge) => {
8808
8873
  };
8809
8874
  var readChallengeQuestion = (challenge) => typeof challenge.question === "string" ? challenge.question : void 0;
8810
8875
  var getCurrentHref3 = () => typeof window === "undefined" ? "" : window.location.href;
8876
+ var getCurrentOrigin = () => typeof window === "undefined" ? "" : window.location.origin;
8811
8877
  var getCurrentHostname = () => typeof window === "undefined" ? "" : window.location.hostname;
8812
8878
  var getCallbackUrl = () => {
8813
8879
  if (typeof window === "undefined") return "";