Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions packages/message/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,95 @@ export function getEventFlag(messageFlag: string, onReady: (eventFlag: string) =
pageDispatchCustomEvent(messageFlag, { action: "requestEventFlag" });
}

/**
* 在同一个页面中,通过自定义事件「协商」出一个唯一可用的 EventFlag
*
* 设计目的:
* - 页面中可能同时存在多个实例
* - 需要确保最终只有一个 EventFlag 被选中并使用
*
* 协商思路(基于同步事件机制):
* 1. 先广播一次【不带 EventFlag 的询问事件】
* 2. 所有实例都会收到该事件,并根据收到的内容做判断:
* - 如果收到【已带 EventFlag 的事件】
* → 说明已有实例成功声明旗标,直接采用该值
* → 如果不是自己期望的旗标,立刻退出协商
* - 如果收到【不带 EventFlag 的事件】
* → 视为一次“空回应”
* → 在可接受次数内,主动声明自己的 preferredFlag
* 3. 若空回应次数超过上限仍未成功,则放弃协商
*
* 注意事项:
* - dispatchEvent 是同步执行的
* - 实例也会收到自己发出的事件
* - 只有一个实例时,通常立即采用 preferredFlag
* - 多实例并存时,先成功拦截并声明的实例胜出
*/
export function getSyncFlag(channelKey: string, preferredFlag: string, maxEmptyResponses: number = 3) {
/** 协商所使用的事件名称 */
const eventName = `${channelKey}_syncFlag`;

/** 最终确认并采用的 EventFlag */
let finalFlag = "";

/** 已收到的“空事件”次数(不带 EventFlag) */
let emptyEventCount = 0;

/**
* 处理协商事件的核心监听函数
*/
const fnHandler: EventListener = (event) => {
if (!(event instanceof CustomEvent)) return;
if (event.defaultPrevented) return;

const receivedFlag = event.detail?.EventFlag;

// ───────────── 情况一:收到已声明 EventFlag 的事件 ─────────────
if (receivedFlag) {
// 只在尚未确定最终结果时处理
if (!finalFlag) {
finalFlag = receivedFlag;

// 若旗标不是自己期望的,说明其他实例已胜出
if (receivedFlag !== preferredFlag) {
pageRemoveEventListener(eventName, fnHandler);
}
}
return;
}

// ───────────── 情况二:收到不带 EventFlag 的空事件 ─────────────
emptyEventCount++;

if (emptyEventCount <= maxEmptyResponses) {
// 在允许范围内,主动声明自己的旗标
pageDispatchCustomEvent(eventName, {
EventFlag: preferredFlag,
});

// 阻止事件继续传播,避免被其他实例抢先处理
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
} else {
// 超过最大尝试次数,放弃协商
pageRemoveEventListener(eventName, fnHandler);
}
};

// 开始监听协商事件
pageAddEventListener(eventName, fnHandler);

// 发送第一次询问事件(不带 EventFlag)
pageDispatchCustomEvent(eventName, {});

if (!finalFlag) {
throw new Error("Unexpected Error in syncFlag");
}

return finalFlag;
}

export const createMouseEvent =
process.env.VI_TESTING === "true"
? (type: string, eventInitDict?: MouseEventInit | undefined): MouseEvent => {
Expand Down
29 changes: 16 additions & 13 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,32 @@ import { CustomEventMessage } from "@Packages/message/custom_event_message";
import { Server } from "@Packages/message/server";
import { ScriptExecutor } from "./app/service/content/script_executor";
import type { Message } from "@Packages/message/types";
import { getEventFlag } from "@Packages/message/common";
import { getEventFlag, getSyncFlag } from "@Packages/message/common";
import { ScriptRuntime } from "./app/service/content/script_runtime";
import { ScriptEnvTag } from "@Packages/message/consts";
import { isContent } from "./app/service/content/gm_api/gm_api";
import { randomMessageFlag } from "./pkg/utils/utils";

const messageFlag = process.env.SC_RANDOM_KEY!;
const syncFlag = getSyncFlag(messageFlag, randomMessageFlag(), 3);
const scriptEnvTag = isContent ? ScriptEnvTag.content : ScriptEnvTag.inject;

getEventFlag(messageFlag, (eventFlag: string) => {
const scriptEnvTag = isContent ? ScriptEnvTag.content : ScriptEnvTag.inject;
const msg: Message = new CustomEventMessage(`${syncFlag}${scriptEnvTag}`, false);

const msg: Message = new CustomEventMessage(`${eventFlag}${scriptEnvTag}`, false);
// 初始化日志组件
const logger = new LoggerCore({
writer: new MessageWriter(msg, "scripting/logger"),
consoleLevel: process.env.NODE_ENV === "development" ? "debug" : "none", // 只让日志在scripting环境中打印
labels: { env: "content", href: window.location.href },
});
logger.logger().debug("content start");

// 初始化日志组件
const logger = new LoggerCore({
writer: new MessageWriter(msg, "scripting/logger"),
consoleLevel: process.env.NODE_ENV === "development" ? "debug" : "none", // 只让日志在scripting环境中打印
labels: { env: "content", href: window.location.href },
});
const server = new Server("content", msg);
const scriptExecutor = new ScriptExecutor(msg);

logger.logger().debug("content start");
getEventFlag(messageFlag, (_eventFlag: string) => {
logger.logger().debug("content getEventFlag");

const server = new Server("content", msg);
const scriptExecutor = new ScriptExecutor(msg);
const runtime = new ScriptRuntime(scriptEnvTag, server, msg, scriptExecutor, messageFlag);
runtime.init();
});
29 changes: 16 additions & 13 deletions src/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,32 @@ import { CustomEventMessage } from "@Packages/message/custom_event_message";
import { Server } from "@Packages/message/server";
import { ScriptExecutor } from "./app/service/content/script_executor";
import type { Message } from "@Packages/message/types";
import { getEventFlag } from "@Packages/message/common";
import { getEventFlag, getSyncFlag } from "@Packages/message/common";
import { ScriptRuntime } from "./app/service/content/script_runtime";
import { ScriptEnvTag } from "@Packages/message/consts";
import { isContent } from "./app/service/content/gm_api/gm_api";
import { randomMessageFlag } from "./pkg/utils/utils";

const messageFlag = process.env.SC_RANDOM_KEY!;
const syncFlag = getSyncFlag(messageFlag, randomMessageFlag(), 3);
const scriptEnvTag = isContent ? ScriptEnvTag.content : ScriptEnvTag.inject;

getEventFlag(messageFlag, (eventFlag: string) => {
const scriptEnvTag = isContent ? ScriptEnvTag.content : ScriptEnvTag.inject;
const msg: Message = new CustomEventMessage(`${syncFlag}${scriptEnvTag}`, false);

const msg: Message = new CustomEventMessage(`${eventFlag}${scriptEnvTag}`, false);
// 初始化日志组件
const logger = new LoggerCore({
writer: new MessageWriter(msg, "scripting/logger"),
consoleLevel: process.env.NODE_ENV === "development" ? "debug" : "none", // 只让日志在scripting环境中打印
labels: { env: "inject", href: window.location.href },
});
logger.logger().debug("inject start");

// 初始化日志组件
const logger = new LoggerCore({
writer: new MessageWriter(msg, "scripting/logger"),
consoleLevel: process.env.NODE_ENV === "development" ? "debug" : "none", // 只让日志在scripting环境中打印
labels: { env: "inject", href: window.location.href },
});
const server = new Server("inject", msg);
const scriptExecutor = new ScriptExecutor(msg);

logger.logger().debug("inject start");
getEventFlag(messageFlag, (_eventFlag: string) => {
logger.logger().debug("inject getEventFlag");

const server = new Server("inject", msg);
const scriptExecutor = new ScriptExecutor(msg);
const runtime = new ScriptRuntime(scriptEnvTag, server, msg, scriptExecutor, messageFlag);
runtime.init();

Expand Down
39 changes: 22 additions & 17 deletions src/scripting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,35 @@ import { CustomEventMessage } from "@Packages/message/custom_event_message";
import { ScriptEnvTag } from "@Packages/message/consts";
import { Server } from "@Packages/message/server";
import ScriptingRuntime from "./app/service/content/scripting";
import { negotiateEventFlag } from "@Packages/message/common";
import { getSyncFlag, negotiateEventFlag } from "@Packages/message/common";
import { randomMessageFlag } from "./pkg/utils/utils";

const messageFlag = process.env.SC_RANDOM_KEY!;
const syncFlag = getSyncFlag(messageFlag, randomMessageFlag(), 3);

// 将初始化流程完成后,将EventFlag通知到其他环境
negotiateEventFlag(messageFlag, 2, (eventFlag) => {
// 建立与service_worker页面的连接
const extMsgComm: Message = new ExtensionMessage(false);
// 初始化日志组件
const logger = new LoggerCore({
writer: new MessageWriter(extMsgComm, "serviceWorker/logger"),
labels: { env: "scripting" },
});
// 建立与service_worker页面的连接
const extMsgComm: Message = new ExtensionMessage(false);
// 初始化日志组件
const logger = new LoggerCore({
writer: new MessageWriter(extMsgComm, "serviceWorker/logger"),
labels: { env: "scripting" },
});

logger.logger().debug("scripting start");

logger.logger().debug("scripting start");
const contentMsg = new CustomEventMessage(`${syncFlag}${ScriptEnvTag.content}`, true);
const injectMsg = new CustomEventMessage(`${syncFlag}${ScriptEnvTag.inject}`, true);

const contentMsg = new CustomEventMessage(`${eventFlag}${ScriptEnvTag.content}`, true);
const injectMsg = new CustomEventMessage(`${eventFlag}${ScriptEnvTag.inject}`, true);
const server = new Server("scripting", [contentMsg, injectMsg]);

const server = new Server("scripting", [contentMsg, injectMsg]);
// Opera中没有chrome.runtime.onConnect,并且content也不需要chrome.runtime.onConnect
// 所以不需要处理连接,设置为false
const extServer = new Server("scripting", extMsgComm, false);

// 将初始化流程完成后,将EventFlag通知到其他环境
negotiateEventFlag(messageFlag, 2, (_eventFlag) => {
logger.logger().debug("scripting negotiateEventFlag");

// Opera中没有chrome.runtime.onConnect,并且content也不需要chrome.runtime.onConnect
// 所以不需要处理连接,设置为false
const extServer = new Server("scripting", extMsgComm, false);
// scriptExecutor的消息接口
// 初始化运行环境
const runtime = new ScriptingRuntime(extServer, server, extMsgComm, contentMsg, injectMsg);
Expand Down
Loading