signup-page-tab-cleanup.test.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. const assert = require('assert');
  2. const fs = require('fs');
  3. const helperSource = fs.readFileSync('background.js', 'utf8');
  4. const tabRuntimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
  5. function extractFunction(source, name) {
  6. const markers = [`async function ${name}(`, `function ${name}(`];
  7. const start = markers
  8. .map(marker => source.indexOf(marker))
  9. .find(index => index >= 0);
  10. if (start < 0) throw new Error(`missing function ${name}`);
  11. let parenDepth = 0;
  12. let signatureEnded = false;
  13. let braceStart = -1;
  14. for (let i = start; i < source.length; i += 1) {
  15. const ch = source[i];
  16. if (ch === '(') parenDepth += 1;
  17. else if (ch === ')') {
  18. parenDepth -= 1;
  19. if (parenDepth === 0) signatureEnded = true;
  20. } else if (ch === '{' && signatureEnded) {
  21. braceStart = i;
  22. break;
  23. }
  24. }
  25. if (braceStart < 0) throw new Error(`missing body for function ${name}`);
  26. let depth = 0;
  27. let end = braceStart;
  28. for (; end < source.length; end += 1) {
  29. const ch = source[end];
  30. if (ch === '{') depth += 1;
  31. if (ch === '}') {
  32. depth -= 1;
  33. if (depth === 0) {
  34. end += 1;
  35. break;
  36. }
  37. }
  38. }
  39. return source.slice(start, end);
  40. }
  41. const helperBundle = [
  42. extractFunction(helperSource, 'parseUrlSafely'),
  43. extractFunction(helperSource, 'isSignupPageHost'),
  44. extractFunction(helperSource, 'isSignupEntryHost'),
  45. extractFunction(helperSource, 'matchesSourceUrlFamily'),
  46. ].join('\n');
  47. const api = new Function('tabRuntimeSource', `
  48. const self = {};
  49. let currentState = {
  50. sourceLastUrls: {},
  51. tabRegistry: {},
  52. };
  53. let currentTabs = [];
  54. const removedBatches = [];
  55. const logMessages = [];
  56. const chrome = {
  57. tabs: {
  58. async query() {
  59. return currentTabs;
  60. },
  61. async remove(ids) {
  62. removedBatches.push(ids);
  63. currentTabs = currentTabs.filter((tab) => !ids.includes(tab.id));
  64. },
  65. },
  66. };
  67. async function getState() {
  68. return currentState;
  69. }
  70. async function setState(updates) {
  71. currentState = { ...currentState, ...updates };
  72. }
  73. async function addLog(message, level = 'info') {
  74. logMessages.push({ message, level });
  75. }
  76. function getSourceLabel(source) {
  77. return source;
  78. }
  79. function isLocalhostOAuthCallbackUrl() {
  80. return false;
  81. }
  82. function isRetryableContentScriptTransportError() {
  83. return false;
  84. }
  85. function throwIfStopped() {}
  86. const LOG_PREFIX = '[test:bg]';
  87. const STOP_ERROR_MESSAGE = 'Flow stopped.';
  88. ${helperBundle}
  89. ${tabRuntimeSource}
  90. const runtime = self.MultiPageBackgroundTabRuntime.createTabRuntime({
  91. addLog,
  92. chrome,
  93. getSourceLabel,
  94. getState,
  95. isLocalhostOAuthCallbackUrl,
  96. isRetryableContentScriptTransportError,
  97. LOG_PREFIX,
  98. matchesSourceUrlFamily,
  99. setState,
  100. STOP_ERROR_MESSAGE,
  101. throwIfStopped,
  102. });
  103. return {
  104. matchesSourceUrlFamily,
  105. closeConflictingTabsForSource: runtime.closeConflictingTabsForSource,
  106. reset({ tabs, state }) {
  107. currentTabs = tabs;
  108. removedBatches.length = 0;
  109. logMessages.length = 0;
  110. currentState = {
  111. sourceLastUrls: {},
  112. tabRegistry: {},
  113. ...(state || {}),
  114. };
  115. },
  116. snapshot() {
  117. return {
  118. currentState,
  119. currentTabs,
  120. removedBatches,
  121. logMessages,
  122. };
  123. },
  124. };
  125. `)(tabRuntimeSource);
  126. (async () => {
  127. assert.strictEqual(
  128. api.matchesSourceUrlFamily('signup-page', 'https://chatgpt.com/', 'https://chatgpt.com/'),
  129. true,
  130. 'signup-page family should include chatgpt.com'
  131. );
  132. assert.strictEqual(
  133. api.matchesSourceUrlFamily('signup-page', 'https://chat.openai.com/', 'https://auth.openai.com/authorize'),
  134. true,
  135. 'signup-page family should include legacy chat.openai.com'
  136. );
  137. assert.strictEqual(
  138. api.matchesSourceUrlFamily('mail-phplife', 'https://mail.phplife.net/?_task=mail&_mbox=INBOX', 'https://mail.phplife.net/?_task=mail&_mbox=INBOX'),
  139. true,
  140. 'mail-phplife family should include mail.phplife.net'
  141. );
  142. api.reset({
  143. tabs: [
  144. { id: 1, url: 'https://chatgpt.com/' },
  145. { id: 2, url: 'https://chat.openai.com/' },
  146. { id: 3, url: 'https://auth.openai.com/authorize?client_id=test' },
  147. { id: 4, url: 'https://example.com/' },
  148. ],
  149. state: {
  150. sourceLastUrls: {
  151. 'signup-page': 'https://chatgpt.com/',
  152. },
  153. tabRegistry: {
  154. 'signup-page': { tabId: 3, ready: true },
  155. },
  156. },
  157. });
  158. await api.closeConflictingTabsForSource('signup-page', 'https://auth.openai.com/authorize', {
  159. excludeTabIds: [3],
  160. });
  161. let snapshot = api.snapshot();
  162. assert.deepStrictEqual(snapshot.removedBatches, [[1, 2]]);
  163. assert.deepStrictEqual(snapshot.currentTabs, [
  164. { id: 3, url: 'https://auth.openai.com/authorize?client_id=test' },
  165. { id: 4, url: 'https://example.com/' },
  166. ]);
  167. api.reset({
  168. tabs: [
  169. { id: 11, url: 'https://chatgpt.com/' },
  170. { id: 12, url: 'https://auth.openai.com/authorize?client_id=test' },
  171. ],
  172. state: {
  173. sourceLastUrls: {
  174. 'signup-page': 'https://auth.openai.com/authorize?client_id=test',
  175. },
  176. tabRegistry: {
  177. 'signup-page': { tabId: 11, ready: true },
  178. },
  179. },
  180. });
  181. await api.closeConflictingTabsForSource('signup-page', 'https://chatgpt.com/');
  182. snapshot = api.snapshot();
  183. assert.deepStrictEqual(snapshot.removedBatches, [[11, 12]]);
  184. assert.strictEqual(snapshot.currentState.tabRegistry['signup-page'], null);
  185. console.log('signup page tab cleanup tests passed');
  186. })().catch((error) => {
  187. console.error(error);
  188. process.exit(1);
  189. });