step9-localhost-cleanup-scope.test.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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++) {
  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++) {
  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, 'normalizeEmailGenerator'),
  43. extractFunction(helperSource, 'normalizeMail2925Mode'),
  44. extractFunction(helperSource, 'getMail2925Mode'),
  45. extractFunction(helperSource, 'parseUrlSafely'),
  46. extractFunction(helperSource, 'isHotmailProvider'),
  47. extractFunction(helperSource, 'isCustomMailProvider'),
  48. extractFunction(helperSource, 'isGeneratedAliasProvider'),
  49. extractFunction(helperSource, 'shouldUseCustomRegistrationEmail'),
  50. extractFunction(helperSource, 'isLocalhostOAuthCallbackUrl'),
  51. extractFunction(helperSource, 'handleStepData'),
  52. ].join('\n');
  53. const api = new Function('tabRuntimeSource', `
  54. const self = {};
  55. const HOTMAIL_PROVIDER = 'hotmail-api';
  56. const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
  57. const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
  58. const GMAIL_PROVIDER = 'gmail';
  59. const MAIL_2925_MODE_PROVIDE = 'provide';
  60. const MAIL_2925_MODE_RECEIVE = 'receive';
  61. const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
  62. let currentState = {
  63. tabRegistry: {
  64. 'signup-page': { tabId: 1, ready: true },
  65. 'vps-panel': { tabId: 99, ready: true },
  66. },
  67. };
  68. let currentTabs = [];
  69. const removedBatches = [];
  70. const logMessages = [];
  71. const chrome = {
  72. tabs: {
  73. async query() {
  74. return currentTabs;
  75. },
  76. async remove(ids) {
  77. removedBatches.push(ids);
  78. currentTabs = currentTabs.filter((tab) => !ids.includes(tab.id));
  79. },
  80. },
  81. };
  82. async function getState() {
  83. return currentState;
  84. }
  85. async function setState(updates) {
  86. currentState = { ...currentState, ...updates };
  87. }
  88. async function setEmailState(email) {
  89. currentState = { ...currentState, email };
  90. }
  91. async function setEmailStateSilently(email) {
  92. currentState = { ...currentState, email };
  93. }
  94. function isLuckmailProvider() {
  95. return false;
  96. }
  97. async function patchHotmailAccount() {}
  98. async function clearLuckmailRuntimeState() {}
  99. function broadcastDataUpdate() {}
  100. async function addLog(message) {
  101. logMessages.push(message);
  102. }
  103. async function finalizeIcloudAliasAfterSuccessfulFlow() {}
  104. function matchesSourceUrlFamily() {
  105. return false;
  106. }
  107. function getSourceLabel(source) {
  108. return source;
  109. }
  110. function isRetryableContentScriptTransportError() {
  111. return false;
  112. }
  113. function throwIfStopped() {}
  114. const LOG_PREFIX = '[test:bg]';
  115. const STOP_ERROR_MESSAGE = 'Flow stopped.';
  116. ${helperBundle}
  117. ${tabRuntimeSource}
  118. const tabRuntime = self.MultiPageBackgroundTabRuntime.createTabRuntime({
  119. addLog,
  120. chrome,
  121. getSourceLabel,
  122. getState,
  123. isLocalhostOAuthCallbackUrl,
  124. isRetryableContentScriptTransportError,
  125. LOG_PREFIX,
  126. matchesSourceUrlFamily,
  127. setState,
  128. STOP_ERROR_MESSAGE,
  129. throwIfStopped,
  130. });
  131. const closeLocalhostCallbackTabs = tabRuntime.closeLocalhostCallbackTabs;
  132. const isLocalhostOAuthCallbackTabMatch = tabRuntime.isLocalhostOAuthCallbackTabMatch;
  133. const buildLocalhostCleanupPrefix = tabRuntime.buildLocalhostCleanupPrefix;
  134. const closeTabsByUrlPrefix = tabRuntime.closeTabsByUrlPrefix;
  135. return {
  136. handleStepData,
  137. closeLocalhostCallbackTabs,
  138. isLocalhostOAuthCallbackTabMatch,
  139. reset({ tabs, tabRegistry }) {
  140. currentTabs = tabs;
  141. removedBatches.length = 0;
  142. logMessages.length = 0;
  143. currentState = {
  144. tabRegistry: tabRegistry || {},
  145. };
  146. },
  147. snapshot() {
  148. return {
  149. currentState,
  150. removedBatches,
  151. logMessages,
  152. };
  153. },
  154. };
  155. `)(tabRuntimeSource);
  156. (async () => {
  157. const codexCallbackUrl = 'http://127.0.0.1:8317/codex/callback?code=abc&state=xyz';
  158. const authCallbackUrl = 'http://localhost:1455/auth/callback?code=def&state=uvw';
  159. assert.strictEqual(api.isLocalhostOAuthCallbackTabMatch(codexCallbackUrl, codexCallbackUrl), true);
  160. assert.strictEqual(api.isLocalhostOAuthCallbackTabMatch(codexCallbackUrl, authCallbackUrl), false);
  161. assert.strictEqual(api.isLocalhostOAuthCallbackTabMatch(authCallbackUrl, codexCallbackUrl), false);
  162. api.reset({
  163. tabs: [
  164. { id: 1, url: codexCallbackUrl },
  165. { id: 2, url: 'http://127.0.0.1:8317/codex/dashboard' },
  166. { id: 3, url: 'http://127.0.0.1:8317/codex/callback?code=other&state=xyz' },
  167. { id: 4, url: authCallbackUrl },
  168. ],
  169. tabRegistry: {
  170. 'signup-page': { tabId: 1, ready: true },
  171. 'vps-panel': { tabId: 99, ready: true },
  172. },
  173. });
  174. await api.handleStepData(9, { localhostUrl: codexCallbackUrl });
  175. let snapshot = api.snapshot();
  176. assert.deepStrictEqual(snapshot.removedBatches, [[1], [2]]);
  177. assert.strictEqual(snapshot.currentState.tabRegistry['signup-page'], null);
  178. assert.deepStrictEqual(snapshot.currentState.tabRegistry['vps-panel'], { tabId: 99, ready: true });
  179. api.reset({
  180. tabs: [
  181. { id: 1, url: codexCallbackUrl },
  182. { id: 4, url: authCallbackUrl },
  183. { id: 5, url: 'http://localhost:1455/auth/dashboard' },
  184. ],
  185. tabRegistry: {},
  186. });
  187. const closedCount = await api.closeLocalhostCallbackTabs(authCallbackUrl);
  188. snapshot = api.snapshot();
  189. assert.strictEqual(closedCount, 1);
  190. assert.deepStrictEqual(snapshot.removedBatches, [[4]]);
  191. assert.strictEqual(snapshot.logMessages.length, 1);
  192. console.log('step9 localhost cleanup scope tests passed');
  193. })().catch((error) => {
  194. console.error(error);
  195. process.exit(1);
  196. });