-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy paththisTarget.ts
112 lines (93 loc) · 3.08 KB
/
thisTarget.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import {
isBackground,
isContentScript,
isExtensionContext,
} from "webext-detect-page";
import { messenger } from "./sender.js";
import { registerMethods } from "./receiver.js";
import { AnyTarget, MessengerMeta, Sender } from "./types.js";
import { debug } from "./shared.js";
import { Entries } from "type-fest";
// Soft warning: Race conditions are possible.
// This CANNOT be awaited because waiting for it means "I will handle the message."
// If a message is received before this is ready, it will just have to be ignored.
let thisTarget: AnyTarget | undefined;
function compareTargets(to: AnyTarget, thisTarget: AnyTarget): boolean {
for (const [key, value] of Object.entries(to) as Entries<typeof to>) {
if (thisTarget[key] === value) {
continue;
}
if (key !== "page") {
return false;
}
const toUrl = new URL(to.page!, location.origin);
const thisUrl = new URL(thisTarget.page!, location.origin);
if (toUrl.pathname !== thisUrl.pathname) {
return false;
}
for (const [parameterKey, parameterValue] of toUrl.searchParams) {
if (thisUrl.searchParams.get(parameterKey) !== parameterValue) {
return false;
}
}
}
return true;
}
export function getActionForMessage(
from: Sender,
{ ...to }: AnyTarget // Clone object because we're editing it
): "respond" | "forward" | "ignore" {
if (to.page === "any") {
return "respond";
}
// Content scripts only receive messages that are meant for them. In the future
// they'll also forward them, but that still means they need to be handled here.
if (isContentScript()) {
return "respond";
}
// We're in an extension page, but the target is not one.
if (!to.page) {
return "forward";
}
if (!thisTarget) {
console.warn("A message was received before this context was ready");
// If this *was* the target, then probably no one else answered
return "ignore";
}
// Set "this" tab to the current tabId
if (to.tabId === "this" && thisTarget.tabId === from.tab?.id) {
to.tabId = thisTarget.tabId;
}
// Every `target` key must match `thisTarget`
const isThisTarget = compareTargets(to, thisTarget);
if (!isThisTarget) {
debug("The message’s target is", to, "but this is", thisTarget);
}
return isThisTarget ? "respond" : "ignore";
}
let nameRequested = false;
export async function nameThisTarget() {
// Same as above: CS receives messages correctly
if (!nameRequested && !thisTarget && !isContentScript()) {
nameRequested = true;
thisTarget = await messenger("__getTabData", {}, { page: "any" });
thisTarget.page = location.pathname + location.search;
}
}
function __getTabData(this: MessengerMeta): AnyTarget {
return { tabId: this.trace[0]?.tab?.id, frameId: this.trace[0]?.frameId };
}
declare global {
interface MessengerMethods {
__getTabData: typeof __getTabData;
}
}
export function initPrivateApi(): void {
if (isBackground()) {
thisTarget = { page: "background" };
}
if (isExtensionContext()) {
// Any context can handler this message
registerMethods({ __getTabData });
}
}