yunti-browser-runtime 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,8 @@ import {
12
12
  export function createSessionManager() {
13
13
  const sessionsByTab = new Map()
14
14
  const pollers = new Map()
15
+ const pendingTabRecovery = new Map()
16
+ const lastInjectionAtByTab = new Map()
15
17
  let cachedPlatformMatches = null
16
18
  let toolRequestHandler = null
17
19
 
@@ -38,6 +40,104 @@ export function createSessionManager() {
38
40
  }).catch(() => {})
39
41
  }
40
42
 
43
+ async function ensureAllTabsRegistered(options = {}) {
44
+ const settings = await getSettings()
45
+ cachedPlatformMatches = settings.platformMatches
46
+ const tabs = await chrome.tabs.query({})
47
+ const results = await Promise.allSettled(
48
+ tabs.map((tab) => ensureTabRegistered(tab.id, { ...options, tab, settings }))
49
+ )
50
+ return {
51
+ ok: true,
52
+ reason: options.reason || "manual",
53
+ checked: tabs.length,
54
+ recovered: results.filter((result) => result.value?.registered || result.value?.injected)
55
+ .length,
56
+ skipped: results.filter((result) => result.value?.skipped).length,
57
+ failed: results.filter((result) => result.status === "rejected" || result.value?.ok === false)
58
+ .length,
59
+ }
60
+ }
61
+
62
+ async function ensureTabRegistered(tabId, options = {}) {
63
+ if (!Number.isFinite(Number(tabId))) {
64
+ return { ok: false, skipped: true, reason: "missing_tab_id" }
65
+ }
66
+ const key = Number(tabId)
67
+ if (pendingTabRecovery.has(key)) return pendingTabRecovery.get(key)
68
+ const promise = recoverTabRegistration(key, options).finally(() => {
69
+ pendingTabRecovery.delete(key)
70
+ })
71
+ pendingTabRecovery.set(key, promise)
72
+ return promise
73
+ }
74
+
75
+ async function recoverTabRegistration(tabId, options = {}) {
76
+ const settings = options.settings || (await getSettings())
77
+ cachedPlatformMatches = settings.platformMatches
78
+ const tab = options.tab || (await chrome.tabs.get(tabId).catch(() => null))
79
+ if (!isInjectableTab(tab, settings)) {
80
+ return { ok: true, skipped: true, reason: "unsupported_tab" }
81
+ }
82
+ if (sessionsByTab.has(tabId)) {
83
+ await refreshTabRegistration(tabId).catch(() => null)
84
+ return { ok: true, registered: true, reason: "already_registered" }
85
+ }
86
+ const ping = await refreshTabRegistration(tabId).catch((error) => ({
87
+ ok: false,
88
+ error: error?.message || String(error),
89
+ }))
90
+ if (ping?.ok) {
91
+ return { ok: true, registered: true, reason: "content_script_present" }
92
+ }
93
+ const lastInjectionAt = lastInjectionAtByTab.get(tabId) || 0
94
+ if (Date.now() - lastInjectionAt < 5000) {
95
+ return { ok: true, skipped: true, reason: "recently_injected" }
96
+ }
97
+ const injected = await injectContentScripts(tabId).catch((error) => ({
98
+ ok: false,
99
+ error: error?.message || String(error),
100
+ }))
101
+ if (!injected?.ok) {
102
+ return { ok: false, reason: "inject_failed", error: injected?.error || "inject failed" }
103
+ }
104
+ lastInjectionAtByTab.set(tabId, Date.now())
105
+ await delay(50)
106
+ await refreshTabRegistration(tabId).catch(() => null)
107
+ return { ok: true, injected: true, reason: options.reason || "auto_recovery" }
108
+ }
109
+
110
+ function isInjectableTab(tab, settings) {
111
+ if (!tab?.id) return false
112
+ if (tab.discarded) return false
113
+ return isPlatformUrl(tab.url, settings)
114
+ }
115
+
116
+ async function refreshTabRegistration(tabId) {
117
+ return chrome.tabs.sendMessage(tabId, { type: "yunti_refresh_registration" })
118
+ }
119
+
120
+ async function injectContentScripts(tabId) {
121
+ if (!chrome.scripting?.executeScript) {
122
+ return { ok: false, error: "chrome.scripting permission is unavailable" }
123
+ }
124
+ if (chrome.scripting.insertCSS) {
125
+ await chrome.scripting.insertCSS({
126
+ target: { tabId },
127
+ files: ["content.css"],
128
+ })
129
+ }
130
+ await chrome.scripting.executeScript({
131
+ target: { tabId },
132
+ files: ["dom-observer.js"],
133
+ })
134
+ await chrome.scripting.executeScript({
135
+ target: { tabId },
136
+ files: ["content.js"],
137
+ })
138
+ return { ok: true }
139
+ }
140
+
41
141
  async function handleMessage(message, sender) {
42
142
  switch (message?.type) {
43
143
  case "yunti_content_ready":
@@ -244,9 +344,7 @@ export function createSessionManager() {
244
344
  async function refreshActiveTab() {
245
345
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
246
346
  if (!tab?.id) return { ok: false, error: "no active tab" }
247
- await chrome.tabs
248
- .sendMessage(tab.id, { type: "yunti_refresh_registration" })
249
- .catch(() => null)
347
+ await ensureTabRegistered(tab.id, { tab, reason: "popup_refresh" }).catch(() => null)
250
348
  return getPanelState()
251
349
  }
252
350
 
@@ -254,6 +352,8 @@ export function createSessionManager() {
254
352
  sessionsByTab,
255
353
  pollers,
256
354
  activateTab,
355
+ ensureAllTabsRegistered,
356
+ ensureTabRegistered,
257
357
  forgetTab,
258
358
  forwardConsoleEvent,
259
359
  getPlatformMatches,
@@ -279,7 +379,7 @@ function normalizePageAuth(auth, source = "unknown") {
279
379
  return {
280
380
  state: "missing_from_content_script",
281
381
  loggedIn: false,
282
- reason: "Content script did not report page state; reload the extension and refresh the page",
382
+ reason: "Content script did not report page state; Yunti will try automatic registration recovery before asking for a page refresh",
283
383
  checkedAt: new Date().toISOString(),
284
384
  checkedBy: source,
285
385
  }
@@ -300,6 +400,7 @@ function normalizeClientInfo(client) {
300
400
  const value = client && typeof client === "object" ? client : {}
301
401
  return {
302
402
  family: normalizeBrowserFamily(value.family || value.userAgent),
403
+ extensionVersion: String(value.extensionVersion || "").slice(0, 80),
303
404
  userAgent: String(value.userAgent || "").slice(0, 500),
304
405
  platform: String(value.platform || "").slice(0, 120),
305
406
  language: String(value.language || "").slice(0, 80),
@@ -308,6 +409,7 @@ function normalizeClientInfo(client) {
308
409
 
309
410
  function getBackgroundClientInfo() {
310
411
  return {
412
+ extensionVersion: chrome.runtime?.getManifest?.().version || "",
311
413
  userAgent: String(globalThis.navigator?.userAgent || ""),
312
414
  platform: String(globalThis.navigator?.platform || ""),
313
415
  language: String(globalThis.navigator?.language || ""),