From 1ace631d85a01ea6df44ef875469f5f58c58892c Mon Sep 17 00:00:00 2001 From: Huangzhe Date: Wed, 30 Apr 2025 16:37:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=B7=BB=E5=8A=A0=20iframe=20?= =?UTF-8?q?=E9=80=9A=E4=BF=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为应用添加 iframe 嵌入功能的通信支持 --- web/app/components/browser-initor.tsx | 7 ++++++- web/service/base.ts | 20 ++++++++++++++------ web/utils/message-channel.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 web/utils/message-channel.ts diff --git a/web/app/components/browser-initor.tsx b/web/app/components/browser-initor.tsx index 5117d3490..1d69cb27c 100644 --- a/web/app/components/browser-initor.tsx +++ b/web/app/components/browser-initor.tsx @@ -1,5 +1,7 @@ 'use client' +import { activeMessageChannel } from '@/utils/message-channel' + class StorageMock { data: Record @@ -30,7 +32,7 @@ try { localStorage = globalThis.localStorage sessionStorage = globalThis.sessionStorage } -catch (e) { +catch (_) { localStorage = new StorageMock() sessionStorage = new StorageMock() } @@ -43,6 +45,9 @@ Object.defineProperty(globalThis, 'sessionStorage', { value: sessionStorage, }) +// 激活消息通道 +activeMessageChannel() + const BrowserInitor = ({ children, }: { children: React.ReactNode }) => { diff --git a/web/service/base.ts b/web/service/base.ts index f683011fd..b7aafc3f3 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -24,6 +24,7 @@ import { removeAccessToken } from '@/app/components/share/utils' import type { FetchOptionType, ResponseError } from './fetch' import { ContentType, base, baseOptions, getAccessToken } from './fetch' import { asyncRunSafe } from '@/utils' + const TIME_OUT = 100000 export type IOnDataMoreInfo = { @@ -154,6 +155,7 @@ const handleStream = ( let buffer = '' let bufferObj: Record let isFirstMessage = true + function read() { let hasError = false reader?.read().then((result: any) => { @@ -281,6 +283,7 @@ const handleStream = ( read() }) } + read() } @@ -385,7 +388,7 @@ export const ssePost = ( options.body = JSON.stringify(body) const accessToken = getAccessToken(isPublicAPI) - ;(options.headers as Headers).set('Authorization', `Bearer ${accessToken}`) + ;(options.headers as Headers).set('Authorization', `Bearer ${accessToken}`) globalThis.fetch(urlWithPrefix, options as RequestInit) .then((res) => { @@ -457,17 +460,22 @@ export const ssePost = ( } // base request -export const request = async(url: string, options = {}, otherOptions?: IOtherOptions) => { +export const request = async (url: string, options = {}, otherOptions?: IOtherOptions) => { + const baseURL = process.env.NEXT_PUBLIC_BASE_URL try { const otherOptionsForBaseFetch = otherOptions || {} const [err, resp] = await asyncRunSafe(baseFetch(url, options, otherOptionsForBaseFetch)) if (err === null) return resp const errResp: Response = err as any + // 状态未认证,401, 重定向到 signin 界面 if (errResp.status === 401) { const [parseErr, errRespData] = await asyncRunSafe(errResp.json()) - const loginUrl = `${globalThis.location.origin}/signin` + const loginUrl = `${baseURL}/signin` if (parseErr) { + // 如果是 iframe 状态,则不进行跳转,向上返回错误,等待上层处理 + const isIframe = typeof window !== 'undefined' && window.self !== window.top + // 跳转到登录页面 globalThis.location.href = loginUrl return Promise.reject(err) } @@ -498,11 +506,11 @@ export const request = async(url: string, options = {}, otherOptions?: IOther return Promise.reject(err) } if (code === 'not_init_validated' && IS_CE_EDITION) { - globalThis.location.href = `${globalThis.location.origin}/init` + globalThis.location.href = `${baseURL}/init` return Promise.reject(err) } if (code === 'not_setup' && IS_CE_EDITION) { - globalThis.location.href = `${globalThis.location.origin}/install` + globalThis.location.href = `${baseURL}/install` return Promise.reject(err) } @@ -510,7 +518,7 @@ export const request = async(url: string, options = {}, otherOptions?: IOther const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT)) if (refreshErr === null) return baseFetch(url, options, otherOptionsForBaseFetch) - if (location.pathname !== '/signin' || !IS_CE_EDITION) { + if (location.pathname !== `${baseURL}/signin` || !IS_CE_EDITION) { globalThis.location.href = loginUrl return Promise.reject(err) } diff --git a/web/utils/message-channel.ts b/web/utils/message-channel.ts new file mode 100644 index 000000000..49d023673 --- /dev/null +++ b/web/utils/message-channel.ts @@ -0,0 +1,26 @@ +// 定义一个全局的 MessageChannel 用于 iframe 通信 +let port + +export function activeMessageChannel() { + // 只监听一次初始化消息,接收到后自动移除监听器 + window.addEventListener('message', function initHandler(event) { + console.log('尝试接收初始化消息:', event.data) + + console.log('接收到初始化消息,设置端口') + port = event.ports[0] + + // 设置端口消息处理函数 + port.onmessage = (msgEvent) => { + console.log('接收到worker消息:', msgEvent.data) + } + + Object.defineProperty(globalThis, 'port', { + value: port, + }) + + // 初始化完成后移除这个事件监听器 + window.removeEventListener('message', initHandler) + }, /* { + once: true, + } */) +}