This repository was archived by the owner on May 16, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbackground.js
More file actions
279 lines (241 loc) · 9.52 KB
/
Copy pathbackground.js
File metadata and controls
279 lines (241 loc) · 9.52 KB
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/**
* WordPress Browser Extension — background service worker
*
* Responsibilities:
* - Cache per-origin detection results in chrome.storage.local
* - Refresh cached entries no more than once per REFRESH_INTERVAL
* - Purge origins unvisited for longer than PURGE_AFTER
* - Update the toolbar icon to reflect WP detection per tab
* - Serve cached detection to the popup
*/
const CACHE_KEY = 'wp_detection_cache_v1';
const REFRESH_INTERVAL = 7 * 24 * 60 * 60 * 1000; // 1 week
const PURGE_AFTER = 28 * 24 * 60 * 60 * 1000; // 4 weeks
const HOST_REFRESH_INTERVAL = 90 * 24 * 60 * 60 * 1000; // 90 days
// --- Cache helpers --------------------------------------------------------
async function getCache() {
const data = await chrome.storage.local.get(CACHE_KEY);
return data[CACHE_KEY] || {};
}
async function writeCache(cache) {
await chrome.storage.local.set({ [CACHE_KEY]: cache });
}
async function getEntry(origin) {
const cache = await getCache();
return cache[origin] || null;
}
async function upsertEntry(origin, entry) {
const cache = await getCache();
cache[origin] = entry;
await writeCache(cache);
}
async function purgeStale() {
const cache = await getCache();
const now = Date.now();
let changed = false;
for (const origin of Object.keys(cache)) {
const entry = cache[origin];
if (!entry || !entry.lastSeen || now - entry.lastSeen > PURGE_AFTER) {
delete cache[origin];
changed = true;
}
}
if (changed) await writeCache(cache);
}
// --- Detection handling ---------------------------------------------------
chrome.runtime.onStartup.addListener(onLoad);
chrome.runtime.onInstalled.addListener(onLoad);
async function onLoad() {
await purgeStale();
await repaintAllTabs();
}
// On SW startup (browser launch) and onInstalled (extension install/reload)
// the content scripts in already-open tabs are orphaned and can no longer
// report to us, so their toolbar icons stay at the default until the user
// navigates. Walk the open tabs and re-paint from cache instead.
async function repaintAllTabs() {
let tabs;
try { tabs = await chrome.tabs.query({}); } catch (_) { return; }
const cache = await getCache();
for (const tab of tabs) {
if (!tab.id || !tab.url || !/^https?:/.test(tab.url)) continue;
try {
const origin = new URL(tab.url).origin;
const entry = cache[origin];
await updateToolbar(
tab.id,
entry?.isWordPress || false,
{ isLoggedIn: entry?.isLoggedIn || false },
);
} catch (_) { /* invalid URL or tab closed */ }
}
}
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (!msg) return;
if (msg.type === 'WP_DETECTION') {
if (!sender.tab) return; // only accept from content scripts
handleDetection(msg, sender).then(() => sendResponse({ ok: true }));
return true; // keep channel open for async response
}
if (msg.type === 'GET_CACHED_DETECTION') {
getEntry(msg.origin).then(sendResponse);
return true;
}
// Popup pushes back its final detection (which may include the cookie-API
// login override) so the toolbar icon and cache reflect it without waiting
// for a navigation.
if (msg.type === 'POPUP_DETECTION_RESOLVED') {
handlePopupResolution(msg).then(() => sendResponse({ ok: true }));
return true;
}
});
async function handlePopupResolution(msg) {
const { origin, tabId, isLoggedIn, isWordPress } = msg;
if (!origin) return;
const cache = await getCache();
const existing = cache[origin] || null;
if (existing) {
existing.isLoggedIn = !!isLoggedIn;
existing.lastSeen = Date.now();
cache[origin] = existing;
await writeCache(cache);
}
if (tabId) await updateToolbar(tabId, !!isWordPress, { isLoggedIn });
}
async function handleDetection(msg, sender) {
const { origin, detection, hostFromDOM } = msg;
const now = Date.now();
const cache = await getCache();
const existing = cache[origin] || null;
// Decide whether to trust this detection or the cache.
// - If the current page strongly suggests WP, use it.
// - If not, keep the cached answer (the home page may have different
// signals than a deep page; headless WP setups especially).
const freshlyDetected = detection.isWordPress;
const cacheIsFresh = existing &&
existing.isWordPress &&
(now - existing.checkedAt) < REFRESH_INTERVAL;
// Host: prefer a fresh DOM signal, fall back to cached value.
const host = hostFromDOM || existing?.host || null;
const hostCheckedAt = hostFromDOM ? now : (existing?.hostCheckedAt || null);
const entry = {
origin,
isWordPress: freshlyDetected || cacheIsFresh || false,
confidence: Math.max(
detection.confidence,
existing ? existing.confidence : 0,
),
signals: detection.signals,
// Cached so the toolbar repaint after SW startup/install can show the
// active variant without waiting for fresh detection. May be stale if
// the user logs out elsewhere; corrected on next page load and by the
// popup pushing its cookie-API result via POPUP_DETECTION_RESOLVED.
isLoggedIn: !!detection.context?.isLoggedIn,
// Only advance checkedAt when we have a confident positive detection,
// so a single ambiguous page view doesn't reset the clock.
checkedAt: freshlyDetected
? now
: (existing?.checkedAt || now),
lastSeen: now,
host,
hostCheckedAt,
};
// If WordPress but host is still unknown and we haven't checked
// recently, ask the content script to inspect response headers.
// Resolve before the first write so we don't write twice.
const needsHostCheck = entry.isWordPress && !entry.host &&
(!entry.hostCheckedAt || (now - entry.hostCheckedAt) > HOST_REFRESH_INTERVAL);
if (needsHostCheck) {
try {
const res = await chrome.tabs.sendMessage(
sender.tab.id, { type: 'RESOLVE_HOST_HEADERS' },
);
entry.host = res?.host || null;
entry.hostCheckedAt = now;
} catch (_) { /* content script gone */ }
}
cache[origin] = entry;
await writeCache(cache);
await updateToolbar(sender.tab.id, entry.isWordPress, detection.context);
}
// --- Toolbar icon + title -------------------------------------------------
async function updateToolbar(tabId, isWordPress, context) {
// Three states: not WP (gray + slash), WP but not logged in (gray),
// WP + logged in (blue). The cache doesn't carry isLoggedIn so on a
// tab-URL-change icon refresh we'll briefly show the gray "WP" variant
// until the content script reports back with auth context.
const variant = !isWordPress ? '-inactive'
: context?.isLoggedIn ? '-active'
: '';
try {
await chrome.action.setIcon({
tabId,
path: {
16: `icons/icon-16${variant}.png`,
32: `icons/icon-32${variant}.png`,
},
});
} catch (_) { /* icons not shipped yet */ }
const title = isWordPress
? `WordPress detected${context?.isLoggedIn ? ' — logged in' : ''}`
: 'WordPress Browser Extension';
try {
await chrome.action.setTitle({ tabId, title });
} catch (_) { /* tab may have closed */ }
}
// --- Keyboard shortcut: edit this page ------------------------------------
chrome.commands.onCommand.addListener(async (command) => {
if (command !== 'edit-this-page') return;
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || !tab.url || !/^https?:/.test(tab.url)) return;
// Ask the content script for live detection — it has the freshest context.
let result;
try {
result = await chrome.tabs.sendMessage(tab.id, { type: 'GET_LIVE_DETECTION' });
} catch (_) { return; /* content script unreachable */ }
if (!result?.detection?.isWordPress) return;
const ctx = result.detection.context || {};
if (!ctx.isLoggedIn) return;
const origin = result.origin;
// Try sync resolution first (covers most cases).
// resolveEditUrlSync isn't available here (it's in lib/rest.js, loaded
// only in content scripts), so we inline the priority logic.
let editUrl = null;
if (ctx.adminBarEditHref) {
try {
if (new URL(ctx.adminBarEditHref).origin === origin) editUrl = ctx.adminBarEditHref;
} catch (_) { /* malformed href — ignore */ }
}
if (!editUrl && ctx.postId && ctx.pageType === 'single') {
editUrl = `${origin}/wp-admin/post.php?post=${ctx.postId}&action=edit`;
}
if (!editUrl && ctx.pageType === 'term' && ctx.taxonomy && ctx.termId) {
editUrl = `${origin}/wp-admin/term.php?taxonomy=${encodeURIComponent(ctx.taxonomy)}&tag_ID=${ctx.termId}`;
}
if (!editUrl && ctx.pageType === 'author' && ctx.authorId) {
editUrl = `${origin}/wp-admin/user-edit.php?user_id=${ctx.authorId}`;
}
// If sync didn't resolve, try the REST fallback via content script.
if (!editUrl) {
try {
const res = await chrome.tabs.sendMessage(tab.id, { type: 'RESOLVE_EDIT_URL_REST' });
editUrl = res?.url || null;
} catch (_) { /* content script gone */ }
}
if (editUrl) {
chrome.tabs.update(tab.id, { url: editUrl });
}
});
// Re-check cache when a tab changes URL, so the icon reflects cached state
// even before the content script reports in.
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (!changeInfo.url && changeInfo.status !== 'loading') return;
if (!tab.url || !/^https?:/.test(tab.url)) return;
try {
const origin = new URL(tab.url).origin;
const entry = await getEntry(origin);
if (entry) await updateToolbar(tabId, entry.isWordPress, {
isLoggedIn: entry.isLoggedIn || false,
});
} catch (_) { /* invalid URL */ }
});