feat(web): 添加 iframe 通信支持

为应用添加 iframe 嵌入功能的通信支持
This commit is contained in:
Huangzhe
2025-04-30 16:37:31 +08:00
parent 21b2c9ae87
commit 1ace631d85
3 changed files with 46 additions and 7 deletions

View File

@@ -1,5 +1,7 @@
'use client' 'use client'
import { activeMessageChannel } from '@/utils/message-channel'
class StorageMock { class StorageMock {
data: Record<string, string> data: Record<string, string>
@@ -30,7 +32,7 @@ try {
localStorage = globalThis.localStorage localStorage = globalThis.localStorage
sessionStorage = globalThis.sessionStorage sessionStorage = globalThis.sessionStorage
} }
catch (e) { catch (_) {
localStorage = new StorageMock() localStorage = new StorageMock()
sessionStorage = new StorageMock() sessionStorage = new StorageMock()
} }
@@ -43,6 +45,9 @@ Object.defineProperty(globalThis, 'sessionStorage', {
value: sessionStorage, value: sessionStorage,
}) })
// 激活消息通道
activeMessageChannel()
const BrowserInitor = ({ const BrowserInitor = ({
children, children,
}: { children: React.ReactNode }) => { }: { children: React.ReactNode }) => {

View File

@@ -24,6 +24,7 @@ import { removeAccessToken } from '@/app/components/share/utils'
import type { FetchOptionType, ResponseError } from './fetch' import type { FetchOptionType, ResponseError } from './fetch'
import { ContentType, base, baseOptions, getAccessToken } from './fetch' import { ContentType, base, baseOptions, getAccessToken } from './fetch'
import { asyncRunSafe } from '@/utils' import { asyncRunSafe } from '@/utils'
const TIME_OUT = 100000 const TIME_OUT = 100000
export type IOnDataMoreInfo = { export type IOnDataMoreInfo = {
@@ -154,6 +155,7 @@ const handleStream = (
let buffer = '' let buffer = ''
let bufferObj: Record<string, any> let bufferObj: Record<string, any>
let isFirstMessage = true let isFirstMessage = true
function read() { function read() {
let hasError = false let hasError = false
reader?.read().then((result: any) => { reader?.read().then((result: any) => {
@@ -281,6 +283,7 @@ const handleStream = (
read() read()
}) })
} }
read() read()
} }
@@ -385,7 +388,7 @@ export const ssePost = (
options.body = JSON.stringify(body) options.body = JSON.stringify(body)
const accessToken = getAccessToken(isPublicAPI) 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) globalThis.fetch(urlWithPrefix, options as RequestInit)
.then((res) => { .then((res) => {
@@ -457,17 +460,22 @@ export const ssePost = (
} }
// base request // base request
export const request = async<T>(url: string, options = {}, otherOptions?: IOtherOptions) => { export const request = async <T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
const baseURL = process.env.NEXT_PUBLIC_BASE_URL
try { try {
const otherOptionsForBaseFetch = otherOptions || {} const otherOptionsForBaseFetch = otherOptions || {}
const [err, resp] = await asyncRunSafe<T>(baseFetch(url, options, otherOptionsForBaseFetch)) const [err, resp] = await asyncRunSafe<T>(baseFetch(url, options, otherOptionsForBaseFetch))
if (err === null) if (err === null)
return resp return resp
const errResp: Response = err as any const errResp: Response = err as any
// 状态未认证401 重定向到 signin 界面
if (errResp.status === 401) { if (errResp.status === 401) {
const [parseErr, errRespData] = await asyncRunSafe<ResponseError>(errResp.json()) const [parseErr, errRespData] = await asyncRunSafe<ResponseError>(errResp.json())
const loginUrl = `${globalThis.location.origin}/signin` const loginUrl = `${baseURL}/signin`
if (parseErr) { if (parseErr) {
// 如果是 iframe 状态,则不进行跳转,向上返回错误,等待上层处理
const isIframe = typeof window !== 'undefined' && window.self !== window.top
// 跳转到登录页面
globalThis.location.href = loginUrl globalThis.location.href = loginUrl
return Promise.reject(err) return Promise.reject(err)
} }
@@ -498,11 +506,11 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther
return Promise.reject(err) return Promise.reject(err)
} }
if (code === 'not_init_validated' && IS_CE_EDITION) { if (code === 'not_init_validated' && IS_CE_EDITION) {
globalThis.location.href = `${globalThis.location.origin}/init` globalThis.location.href = `${baseURL}/init`
return Promise.reject(err) return Promise.reject(err)
} }
if (code === 'not_setup' && IS_CE_EDITION) { if (code === 'not_setup' && IS_CE_EDITION) {
globalThis.location.href = `${globalThis.location.origin}/install` globalThis.location.href = `${baseURL}/install`
return Promise.reject(err) return Promise.reject(err)
} }
@@ -510,7 +518,7 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther
const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT)) const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT))
if (refreshErr === null) if (refreshErr === null)
return baseFetch<T>(url, options, otherOptionsForBaseFetch) return baseFetch<T>(url, options, otherOptionsForBaseFetch)
if (location.pathname !== '/signin' || !IS_CE_EDITION) { if (location.pathname !== `${baseURL}/signin` || !IS_CE_EDITION) {
globalThis.location.href = loginUrl globalThis.location.href = loginUrl
return Promise.reject(err) return Promise.reject(err)
} }

View File

@@ -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,
} */)
}