diff --git a/src/App.vue b/src/App.vue
index 1ba91c8..3ae44b8 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,6 +2,7 @@
import { onMounted, onUnmounted } from "vue";
import { RouterView } from "vue-router";
+import { isDingTalkContainer } from "@/api/dingtalk";
import router from "@/router";
import { useAuthStore } from "@/stores/auth.store";
@@ -17,9 +18,28 @@ function handleAuthExpired() {
}
}
-onMounted(() =>
- window.addEventListener("risk-auth-expired", handleAuthExpired),
-);
+async function syncDingTalkUser() {
+ if (
+ !isDingTalkContainer() ||
+ !auth.user?.dingtalkId ||
+ auth.user.dingtalkId.startsWith("local-")
+ ) {
+ return;
+ }
+ try {
+ await auth.dingTalkLogin(true);
+ } catch {
+ // A stale token must remain usable if DingTalk is temporarily unavailable.
+ }
+}
+
+onMounted(async () => {
+ window.addEventListener("risk-auth-expired", handleAuthExpired);
+ // Restore /auth/me first: on a hard refresh auth.user starts as null, so a
+ // synchronous check would skip the DingTalk profile resync entirely.
+ if (auth.token && !auth.user) await auth.restore();
+ await syncDingTalkUser();
+});
onUnmounted(() =>
window.removeEventListener("risk-auth-expired", handleAuthExpired),
);
diff --git a/src/api/dingtalk.ts b/src/api/dingtalk.ts
index e9b4824..91aeac6 100644
--- a/src/api/dingtalk.ts
+++ b/src/api/dingtalk.ts
@@ -3,14 +3,43 @@ import dd from "dingtalk-jsapi";
import { getDingTalkConfig, loginWithDingTalk } from "@/api/auth";
import type { AuthTokenData } from "@/types/auth";
+declare global {
+ interface Window {
+ dingtalk?: { platform?: { invokeAPI?: unknown } };
+ DingTalkJSBridge?: unknown;
+ }
+}
+
export function isDingTalkContainer() {
- return (
- typeof navigator !== "undefined" && /DingTalk/i.test(navigator.userAgent)
+ if (typeof window === "undefined" || typeof navigator === "undefined")
+ return false;
+ const userAgent =
+ navigator.userAgent ||
+ (navigator as Navigator & { swuserAgent?: string }).swuserAgent ||
+ "";
+ return Boolean(
+ /DingTalk|dingtalk-win|dd-web/i.test(userAgent) ||
+ window.DingTalkJSBridge ||
+ window.dingtalk?.platform?.invokeAPI ||
+ // DingTalk desktop can expose its container identity through window.name
+ // even when the embedded browser omits DingTalk from userAgent.
+ /dingtalk/i.test(window.name || ""),
);
}
+export async function waitForDingTalkContainer(timeoutMs = 5000) {
+ if (isDingTalkContainer()) return true;
+ const startedAt = Date.now();
+ while (Date.now() - startedAt < timeoutMs) {
+ await new Promise((resolve) => window.setTimeout(resolve, 100));
+ if (isDingTalkContainer()) return true;
+ }
+ return false;
+}
+
export async function authenticateWithDingTalk(): Promise