|
@@ -0,0 +1,206 @@
|
|
|
|
|
+const assert = require('assert');
|
|
|
|
|
+const fs = require('fs');
|
|
|
|
|
+
|
|
|
|
|
+const source = fs.readFileSync('background.js', 'utf8');
|
|
|
|
|
+
|
|
|
|
|
+function extractFunction(name) {
|
|
|
|
|
+ const markers = [`async function ${name}(`, `function ${name}(`];
|
|
|
|
|
+ const start = markers
|
|
|
|
|
+ .map(marker => source.indexOf(marker))
|
|
|
|
|
+ .find(index => index >= 0);
|
|
|
|
|
+ if (start < 0) {
|
|
|
|
|
+ throw new Error(`missing function ${name}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let parenDepth = 0;
|
|
|
|
|
+ let signatureEnded = false;
|
|
|
|
|
+ let braceStart = -1;
|
|
|
|
|
+ for (let i = start; i < source.length; i += 1) {
|
|
|
|
|
+ const ch = source[i];
|
|
|
|
|
+ if (ch === '(') {
|
|
|
|
|
+ parenDepth += 1;
|
|
|
|
|
+ } else if (ch === ')') {
|
|
|
|
|
+ parenDepth -= 1;
|
|
|
|
|
+ if (parenDepth === 0) {
|
|
|
|
|
+ signatureEnded = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (ch === '{' && signatureEnded) {
|
|
|
|
|
+ braceStart = i;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (braceStart < 0) {
|
|
|
|
|
+ throw new Error(`missing body for function ${name}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let depth = 0;
|
|
|
|
|
+ let end = braceStart;
|
|
|
|
|
+ for (; end < source.length; end += 1) {
|
|
|
|
|
+ const ch = source[end];
|
|
|
|
|
+ if (ch === '{') depth += 1;
|
|
|
|
|
+ if (ch === '}') {
|
|
|
|
|
+ depth -= 1;
|
|
|
|
|
+ if (depth === 0) {
|
|
|
|
|
+ end += 1;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return source.slice(start, end);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const bundle = [
|
|
|
|
|
+ extractFunction('getTabRegistry'),
|
|
|
|
|
+ extractFunction('parseUrlSafely'),
|
|
|
|
|
+ extractFunction('isSignupPageHost'),
|
|
|
|
|
+ extractFunction('isSignupEntryHost'),
|
|
|
|
|
+ extractFunction('matchesSourceUrlFamily'),
|
|
|
|
|
+ extractFunction('closeConflictingTabsForSource'),
|
|
|
|
|
+].join('\n');
|
|
|
|
|
+
|
|
|
|
|
+const api = new Function(`
|
|
|
|
|
+let currentState = {
|
|
|
|
|
+ sourceLastUrls: {},
|
|
|
|
|
+ tabRegistry: {},
|
|
|
|
|
+};
|
|
|
|
|
+let currentTabs = [];
|
|
|
|
|
+const removedBatches = [];
|
|
|
|
|
+const logMessages = [];
|
|
|
|
|
+
|
|
|
|
|
+const chrome = {
|
|
|
|
|
+ tabs: {
|
|
|
|
|
+ async query() {
|
|
|
|
|
+ return currentTabs;
|
|
|
|
|
+ },
|
|
|
|
|
+ async remove(ids) {
|
|
|
|
|
+ removedBatches.push(ids);
|
|
|
|
|
+ currentTabs = currentTabs.filter((tab) => !ids.includes(tab.id));
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+async function getState() {
|
|
|
|
|
+ return currentState;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function setState(updates) {
|
|
|
|
|
+ currentState = { ...currentState, ...updates };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function addLog(message, level = 'info') {
|
|
|
|
|
+ logMessages.push({ message, level });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getSourceLabel(source) {
|
|
|
|
|
+ return source;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+${bundle}
|
|
|
|
|
+
|
|
|
|
|
+return {
|
|
|
|
|
+ matchesSourceUrlFamily,
|
|
|
|
|
+ closeConflictingTabsForSource,
|
|
|
|
|
+ reset({ tabs, state }) {
|
|
|
|
|
+ currentTabs = tabs;
|
|
|
|
|
+ removedBatches.length = 0;
|
|
|
|
|
+ logMessages.length = 0;
|
|
|
|
|
+ currentState = {
|
|
|
|
|
+ sourceLastUrls: {},
|
|
|
|
|
+ tabRegistry: {},
|
|
|
|
|
+ ...(state || {}),
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ snapshot() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ currentState,
|
|
|
|
|
+ currentTabs,
|
|
|
|
|
+ removedBatches,
|
|
|
|
|
+ logMessages,
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+};
|
|
|
|
|
+`)();
|
|
|
|
|
+
|
|
|
|
|
+(async () => {
|
|
|
|
|
+ assert.strictEqual(
|
|
|
|
|
+ api.matchesSourceUrlFamily('signup-page', 'https://chatgpt.com/', 'https://chatgpt.com/'),
|
|
|
|
|
+ true,
|
|
|
|
|
+ 'signup-page family should include chatgpt.com'
|
|
|
|
|
+ );
|
|
|
|
|
+ assert.strictEqual(
|
|
|
|
|
+ api.matchesSourceUrlFamily('signup-page', 'https://chat.openai.com/', 'https://auth.openai.com/authorize'),
|
|
|
|
|
+ true,
|
|
|
|
|
+ 'signup-page family should include legacy chat.openai.com'
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ api.reset({
|
|
|
|
|
+ tabs: [
|
|
|
|
|
+ { id: 1, url: 'https://chatgpt.com/' },
|
|
|
|
|
+ { id: 2, url: 'https://chat.openai.com/' },
|
|
|
|
|
+ { id: 3, url: 'https://auth.openai.com/authorize?client_id=test' },
|
|
|
|
|
+ { id: 4, url: 'https://example.com/' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ state: {
|
|
|
|
|
+ sourceLastUrls: {
|
|
|
|
|
+ 'signup-page': 'https://chatgpt.com/',
|
|
|
|
|
+ },
|
|
|
|
|
+ tabRegistry: {
|
|
|
|
|
+ 'signup-page': { tabId: 3, ready: true },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await api.closeConflictingTabsForSource('signup-page', 'https://auth.openai.com/authorize', {
|
|
|
|
|
+ excludeTabIds: [3],
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ let snapshot = api.snapshot();
|
|
|
|
|
+ assert.deepStrictEqual(
|
|
|
|
|
+ snapshot.removedBatches,
|
|
|
|
|
+ [[1, 2]],
|
|
|
|
|
+ 'opening auth page should clean up stale ChatGPT entry tabs'
|
|
|
|
|
+ );
|
|
|
|
|
+ assert.deepStrictEqual(
|
|
|
|
|
+ snapshot.currentTabs,
|
|
|
|
|
+ [
|
|
|
|
|
+ { id: 3, url: 'https://auth.openai.com/authorize?client_id=test' },
|
|
|
|
|
+ { id: 4, url: 'https://example.com/' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'non-signup tabs and excluded current tab should remain'
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ api.reset({
|
|
|
|
|
+ tabs: [
|
|
|
|
|
+ { id: 11, url: 'https://chatgpt.com/' },
|
|
|
|
|
+ { id: 12, url: 'https://auth.openai.com/authorize?client_id=test' },
|
|
|
|
|
+ ],
|
|
|
|
|
+ state: {
|
|
|
|
|
+ sourceLastUrls: {
|
|
|
|
|
+ 'signup-page': 'https://auth.openai.com/authorize?client_id=test',
|
|
|
|
|
+ },
|
|
|
|
|
+ tabRegistry: {
|
|
|
|
|
+ 'signup-page': { tabId: 11, ready: true },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await api.closeConflictingTabsForSource('signup-page', 'https://chatgpt.com/');
|
|
|
|
|
+
|
|
|
|
|
+ snapshot = api.snapshot();
|
|
|
|
|
+ assert.deepStrictEqual(
|
|
|
|
|
+ snapshot.removedBatches,
|
|
|
|
|
+ [[11, 12]],
|
|
|
|
|
+ 'opening ChatGPT entry should remove older signup-family tabs'
|
|
|
|
|
+ );
|
|
|
|
|
+ assert.strictEqual(
|
|
|
|
|
+ snapshot.currentState.tabRegistry['signup-page'],
|
|
|
|
|
+ null,
|
|
|
|
|
+ 'registry should be cleared when the tracked signup tab is removed'
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ console.log('signup page tab cleanup tests passed');
|
|
|
|
|
+})().catch((error) => {
|
|
|
|
|
+ console.error(error);
|
|
|
|
|
+ process.exit(1);
|
|
|
|
|
+});
|