-165
EXTENSION_STATUS.md
-165
EXTENSION_STATUS.md
···
1
-
# Extension Implementation Status
2
-
3
-
## Current State: โ
COMPLETE - Ready for Production Testing
4
-
5
-
### What's Complete โ
6
-
7
-
1. **Environment Configuration**
8
-
- Dev/prod builds with correct API URLs
9
-
- Build: `npm run build` (dev) or `npm run build:prod`
10
-
- Dev: `http://127.0.0.1:8888`
11
-
- Prod: `https://atlast.byarielm.fyi`
12
-
13
-
2. **Server Health Check**
14
-
- Extension checks if dev server is running (dev mode only)
15
-
- Shows "Server offline" state with instructions
16
-
- "Check Again" button to retry
17
-
18
-
3. **Authentication Flow**
19
-
- Extension checks `/session` endpoint on init
20
-
- Shows "Not logged in" state if no session
21
-
- "Open ATlast" button to log in
22
-
- "Check Again" to retry after login
23
-
- **User must be logged in to ATlast BEFORE using extension**
24
-
25
-
4. **Upload Flow** (matches file upload)
26
-
- Scan Twitter Following page
27
-
- POST to `/extension-import` (requires auth)
28
-
- Backend:
29
-
- Gets DID from session
30
-
- Creates `user_upload` entry
31
-
- Saves to `source_accounts` table
32
-
- Returns `uploadId`
33
-
- Opens `/results?uploadId={id}`
34
-
- Frontend searches and displays (same as file upload)
35
-
36
-
5. **CORS Permissions**
37
-
- Extension has host_permissions for:
38
-
- `http://127.0.0.1:8888/*`
39
-
- `http://localhost:8888/*`
40
-
- `https://atlast.byarielm.fyi/*`
41
-
42
-
6. **Cleanup Complete**
43
-
- โ Removed `extension_imports` table
44
-
- โ Removed `get-extension-import` function
45
-
- โ Removed `ExtensionImport.tsx` page
46
-
- โ Removed `/import/:id` route
47
-
- โ Removed `utils/import-store.ts`
48
-
49
-
### What Needs Testing ๐งช
50
-
51
-
1. **Full Flow Test**
52
-
```bash
53
-
# 1. Start dev server
54
-
npx netlify-cli dev --filter @atlast/web
55
-
56
-
# 2. Build extension
57
-
cd packages/extension
58
-
npm run build
59
-
60
-
# 3. Load extension in Chrome
61
-
chrome://extensions/ โ Load unpacked โ packages/extension/dist/chrome/
62
-
63
-
# 4. Log in to ATlast
64
-
Open http://127.0.0.1:8888 โ Log in
65
-
66
-
# 5. Go to Twitter
67
-
https://twitter.com/justadev_atlast/following
68
-
69
-
# 6. Open extension popup
70
-
- Should show "Ready to scan Twitter/X"
71
-
- Click "Start Scan"
72
-
- Wait for completion
73
-
- Click "Open in ATlast"
74
-
- Should open /results?uploadId={id}
75
-
- Results should load and search automatically
76
-
```
77
-
78
-
2. **Error Cases to Test**
79
-
- Not logged in โ should show login prompt
80
-
- Server offline โ should show offline state
81
-
- Empty results โ should show appropriate message
82
-
- Network errors โ should handle gracefully
83
-
84
-
### Recently Completed (Dec 2024 - Jan 2025) ๐
85
-
86
-
**Extension Flow Fixes:**
87
-
- โ
NaN database error - Fixed missing `matchedUsers` parameter in `extension-import.ts` (node #287)
88
-
- โ
Database initialized successfully (node #288)
89
-
- โ
API response unwrapping - Fixed api-client to access ApiResponse.data field (nodes #290-295)
90
-
- โ
Loading screen during extension upload search (node #325)
91
-
- โ
Timezone fixes - All timestamp columns use TIMESTAMPTZ (node #326)
92
-
- โ
Vite dev server optimization - Pre-bundling dependencies for faster startup (node #327)
93
-
94
-
**Decision Graph Documentation:**
95
-
- โ
Fixed 18 orphan nodes and linked to parent goals (nodes #328-331)
96
-
- โ
Improved decision graph workflow with lifecycle management (node #332)
97
-
- โ
Updated CLAUDE.md with node status transitions and common mistakes
98
-
99
-
### Current Status ๐
100
-
101
-
**All extension bugs resolved!** The extension is fully functional and ready for production testing and deployment.
102
-
103
-
### Next Steps ๐
104
-
105
-
1. โ
Build extension: `cd packages/extension && pnpm run build`
106
-
2. โ
Reload extension in Chrome
107
-
3. โ
Test login flow
108
-
4. โ
Test scan and upload
109
-
5. โ
Verify results page works
110
-
6. โ
All bugs fixed
111
-
7. ๐ Test production build: `pnpm run build:prod`
112
-
8. ๐ Chrome Web Store submission
113
-
9. ๐ Firefox Add-ons support and submission
114
-
115
-
### Architecture Notes ๐
116
-
117
-
**Removed temporary import storage approach:**
118
-
- Previously tried in-memory storage (doesn't work in serverless)
119
-
- Then tried database storage with temp table (overkill)
120
-
121
-
**Current approach:**
122
-
- User logs in to ATlast FIRST
123
-
- Extension requires authentication
124
-
- Upload creates permanent records immediately
125
-
- No temporary storage needed
126
-
- Matches file upload behavior exactly
127
-
128
-
**Why this is better:**
129
-
- Simpler architecture
130
-
- No temporary storage to expire
131
-
- Proper user association from the start
132
-
- Reuses existing upload/search infrastructure
133
-
- Same flow as file uploads (consistency)
134
-
135
-
### Files Modified in Latest Refactor
136
-
137
-
**Deleted:**
138
-
- `packages/functions/src/get-extension-import.ts`
139
-
- `packages/functions/src/utils/import-store.ts`
140
-
- `packages/web/src/pages/ExtensionImport.tsx`
141
-
142
-
**Modified:**
143
-
- `packages/functions/src/extension-import.ts` - Now requires auth, creates upload
144
-
- `packages/functions/src/infrastructure/database/DatabaseService.ts` - Removed extension_imports table
145
-
- `packages/functions/src/core/types/database.types.ts` - Removed ExtensionImportRow
146
-
- `packages/web/src/Router.tsx` - Removed /import/:id route
147
-
- `packages/extension/src/popup/popup.ts` - Added session check, login state
148
-
- `packages/extension/src/popup/popup.html` - Added not-logged-in state
149
-
- `packages/extension/src/lib/api-client.ts` - Added checkSession(), credentials: 'include'
150
-
151
-
### Decision Graph Summary
152
-
153
-
**Total nodes:** 332 nodes, 333 edges
154
-
**Key decisions tracked:**
155
-
- Environment configuration approach (#261-269)
156
-
- Port 8888 conflict resolution (#270-274)
157
-
- CORS permissions fix (#275-277)
158
-
- Storage approach: in-memory โ database โ proper auth flow (#278-284)
159
-
- Refactor and build (#285-286)
160
-
- Bug fixes: NaN parameter error (#287), database initialization (#288)
161
-
- API response unwrapping fix (#290-295)
162
-
- Extension upload flow fixes (#296-327)
163
-
- Decision graph integrity fixes (#328-332)
164
-
165
-
**Live graph:** https://notactuallytreyanastasio.github.io/deciduous/
+43
-19
docs/git-history.json
+43
-19
docs/git-history.json
···
1
1
[
2
2
{
3
-
"hash": "fe29bb3e5faa0151f63c14724f7509af669860de",
4
-
"short_hash": "fe29bb3",
3
+
"hash": "15b67054a684ebb2a21761a1774ba15f9b1c29e2",
4
+
"short_hash": "15b6705",
5
+
"author": "Ariel M. Lighty",
6
+
"date": "2025-12-28T20:38:38-05:00",
7
+
"message": "fix: add health check function for extension server detection\n\n- Created /health function endpoint with CORS support\n- Updated checkServerHealth to use function endpoint instead of root URL\n- Fixes Firefox extension server detection with proper CORS headers",
8
+
"files_changed": 5
9
+
},
10
+
{
11
+
"hash": "603cf0a187850664336a12c9e5cbb49038906f53",
12
+
"short_hash": "603cf0a",
5
13
"author": "Ariel M. Lighty",
6
-
"date": "2025-12-27T16:02:10-05:00",
7
-
"message": "docs: update all .md files to reflect current project status\n\nUpdated 4 markdown files with current state:\n\nEXTENSION_STATUS.md:\n- Changed status from DEBUGGING to COMPLETE\n- Updated decision graph count (295 โ 332 nodes)\n- Added recently completed section (nodes 296-332)\n- Marked all extension bugs as resolved\n\nCONTRIBUTING.md:\n- Replaced npm with pnpm throughout\n- Added monorepo structure documentation\n- Updated development commands (netlify-cli dev --filter)\n- Added extension development workflow\n\nPLAN.md:\n- Updated status to Phase 1 COMPLETE\n- Added all recent fixes to completion list\n- Updated decision graph count to 332 nodes\n- Added changelog entries for latest work\n\npackages/extension/README.md:\n- Added prerequisites section (dev server + login required)\n- Updated build commands with dev/prod distinction\n- Added Step 0: Start ATlast Dev Server\n- Added common issues for auth and server states\n\nAll files now accurately reflect completion status and use pnpm.",
8
-
"files_changed": 6
14
+
"date": "2025-12-27T22:42:43-05:00",
15
+
"message": "fix: CORS for extension credentialed requests\n\nUpdated CORS headers to support credentials from Chrome extensions:\n- Added getCorsHeaders() to detect chrome-extension:// origins\n- Changed from wildcard Access-Control-Allow-Origin to specific origin\n- Added Access-Control-Allow-Credentials: true for credentialed requests\n- Updated session endpoint to pass event for CORS header detection",
16
+
"files_changed": 4
17
+
},
18
+
{
19
+
"hash": "bd3aabb75abb1875aef125610fcdccb14967a8e3",
20
+
"short_hash": "bd3aabb",
21
+
"author": "Ariel M. Lighty",
22
+
"date": "2025-12-27T22:10:11-05:00",
23
+
"message": "fix: extension dark mode and build mode messaging\n\n- Changed darkMode from 'class' to 'media' for automatic system preference detection\n- Made server offline message conditional on build mode (dev vs prod)\n- Hide dev server instructions in production builds",
24
+
"files_changed": 5
25
+
},
26
+
{
27
+
"hash": "bd3aabb75abb1875aef125610fcdccb14967a8e3",
28
+
"short_hash": "bd3aabb",
29
+
"author": "Ariel M. Lighty",
30
+
"date": "2025-12-27T22:10:11-05:00",
31
+
"message": "fix: extension dark mode and build mode messaging\n\n- Changed darkMode from 'class' to 'media' for automatic system preference detection\n- Made server offline message conditional on build mode (dev vs prod)\n- Hide dev server instructions in production builds",
32
+
"files_changed": 5
33
+
},
34
+
{
35
+
"hash": "d07180cd3a19328b82b35118e525b59d4e2e060b",
36
+
"short_hash": "d07180c",
37
+
"author": "Ariel M. Lighty",
38
+
"date": "2025-12-27T18:38:39-05:00",
39
+
"message": "feat: add Tailwind CSS to extension\n\nReplaced 299 lines of vanilla CSS with Tailwind for design consistency with web app. Production build minified to 13KB.",
40
+
"files_changed": 9
9
41
},
10
42
{
11
43
"hash": "fe29bb3e5faa0151f63c14724f7509af669860de",
···
22
54
"date": "2025-12-27T15:48:44-05:00",
23
55
"message": "docs: improve decision graph workflow with lifecycle management\n\nUpdated CLAUDE.md with comprehensive node lifecycle management:\n- Added node status transitions (pending โ in_progress โ completed)\n- Correct orphan detection commands (awk instead of cut)\n- Common mistakes section with examples\n- Enhanced audit checklist with status verification\n- Verification workflow after node creation\n\nAlso updated extension popup with ATmosphere branding.\n\nDecision graph now at 331 nodes, 332 edges - all orphans resolved.",
24
56
"files_changed": 4
25
-
},
26
-
{
27
-
"hash": "e04934ffb5e2d78791fcd23bc3afeb4d438a5546",
28
-
"short_hash": "e04934f",
29
-
"author": "Ariel M. Lighty",
30
-
"date": "2025-12-26T21:57:05-05:00",
31
-
"message": "perf: optimize Vite dev server startup\n\nAdded explicit optimizeDeps.include to pre-bundle common dependencies:\n- React ecosystem (react, react-dom, react-router-dom)\n- Icon libraries (@icons-pack/react-simple-icons, lucide-react)\n- Other deps (date-fns, jszip, zustand, @tanstack/react-virtual)\n\nAlso added server.fs.allow config for monorepo file serving.\n\nThis should speed up subsequent dev server starts by ensuring these\ndependencies are consistently pre-bundled.",
32
-
"files_changed": 1
33
57
},
34
58
{
35
59
"hash": "e04934ffb5e2d78791fcd23bc3afeb4d438a5546",
···
136
160
"files_changed": 3
137
161
},
138
162
{
139
-
"hash": "32cdee3aeac7ef986df47e0fff786b5f7471e55b",
140
-
"short_hash": "32cdee3",
163
+
"hash": "ba29fd68872913ba0a587aa7f29f97b3d373a732",
164
+
"short_hash": "ba29fd6",
141
165
"author": "Ariel M. Lighty",
142
166
"date": "2025-12-25T13:22:32-05:00",
143
167
"message": "configure Netlify dev for monorepo with --filter flag\n\nFixed Netlify CLI monorepo detection issue by using --filter flag:\n- Updated root package.json scripts to use 'npx netlify-cli dev --filter @atlast/web'\n- Updated netlify.toml [dev] section to use npm with --prefix for framework command\n- Added monorepo development instructions to CLAUDE.md\n- Documented Windows Git Bash compatibility issue with netlify command\n\nSolution: Use 'npx netlify-cli dev --filter @atlast/web' to bypass monorepo\nproject selection prompt and specify which workspace package to run.\n\nDev server now runs successfully at http://localhost:8888 with all backend\nfunctions loaded.",
144
-
"files_changed": 4
168
+
"files_changed": 5
145
169
},
146
170
{
147
-
"hash": "ba29fd68872913ba0a587aa7f29f97b3d373a732",
148
-
"short_hash": "ba29fd6",
171
+
"hash": "32cdee3aeac7ef986df47e0fff786b5f7471e55b",
172
+
"short_hash": "32cdee3",
149
173
"author": "Ariel M. Lighty",
150
174
"date": "2025-12-25T13:22:32-05:00",
151
175
"message": "configure Netlify dev for monorepo with --filter flag\n\nFixed Netlify CLI monorepo detection issue by using --filter flag:\n- Updated root package.json scripts to use 'npx netlify-cli dev --filter @atlast/web'\n- Updated netlify.toml [dev] section to use npm with --prefix for framework command\n- Added monorepo development instructions to CLAUDE.md\n- Documented Windows Git Bash compatibility issue with netlify command\n\nSolution: Use 'npx netlify-cli dev --filter @atlast/web' to bypass monorepo\nproject selection prompt and specify which workspace package to run.\n\nDev server now runs successfully at http://localhost:8888 with all backend\nfunctions loaded.",
152
-
"files_changed": 5
176
+
"files_changed": 4
153
177
},
154
178
{
155
179
"hash": "c3e7afad396d130791d801a85cbfc9643bcd6309",
+968
docs/graph-data.json
+968
docs/graph-data.json
···
3849
3849
"created_at": "2025-12-27T18:07:49.869572400-05:00",
3850
3850
"updated_at": "2025-12-27T18:07:52.136827400-05:00",
3851
3851
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
3852
+
},
3853
+
{
3854
+
"id": 351,
3855
+
"change_id": "9468bcb3-78ec-4dae-8d8f-968ba6f5b3fe",
3856
+
"node_type": "outcome",
3857
+
"title": "Committed Tailwind CSS integration to git",
3858
+
"description": null,
3859
+
"status": "completed",
3860
+
"created_at": "2025-12-27T18:38:55.689869700-05:00",
3861
+
"updated_at": "2025-12-27T18:39:01.013284600-05:00",
3862
+
"metadata_json": "{\"branch\":\"master\",\"commit\":\"d07180c\",\"confidence\":95}"
3863
+
},
3864
+
{
3865
+
"id": 352,
3866
+
"change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5",
3867
+
"node_type": "goal",
3868
+
"title": "Fix extension dark mode and dev/prod detection issues",
3869
+
"description": null,
3870
+
"status": "completed",
3871
+
"created_at": "2025-12-27T22:05:50.675487800-05:00",
3872
+
"updated_at": "2025-12-27T22:09:32.111749500-05:00",
3873
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"there now seems to be an issue with dark mode not activating, and either an issue with detecting dev vs prod or the copy is just wrong. analyze and fix.\"}"
3874
+
},
3875
+
{
3876
+
"id": 353,
3877
+
"change_id": "eaed6e9b-9f16-4b45-8783-44ea2ea1f2a9",
3878
+
"node_type": "observation",
3879
+
"title": "Found two issues: 1) darkMode: 'class' requires manual .dark class addition, 2) Dev/prod detection may be incorrect",
3880
+
"description": null,
3881
+
"status": "completed",
3882
+
"created_at": "2025-12-27T22:06:19.509001-05:00",
3883
+
"updated_at": "2025-12-27T22:06:23.515277300-05:00",
3884
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
3885
+
},
3886
+
{
3887
+
"id": 354,
3888
+
"change_id": "d66fc83e-9737-4047-8ce2-e2ba857aeea9",
3889
+
"node_type": "decision",
3890
+
"title": "Choose dark mode strategy: media queries vs class-based with JS",
3891
+
"description": null,
3892
+
"status": "completed",
3893
+
"created_at": "2025-12-27T22:07:01.587088200-05:00",
3894
+
"updated_at": "2025-12-27T22:07:07.798171700-05:00",
3895
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":85}"
3896
+
},
3897
+
{
3898
+
"id": 355,
3899
+
"change_id": "76e2a379-7803-4c82-8013-be6b62f2d360",
3900
+
"node_type": "outcome",
3901
+
"title": "Chose media queries - simpler and matches original behavior",
3902
+
"description": null,
3903
+
"status": "completed",
3904
+
"created_at": "2025-12-27T22:07:04.660558100-05:00",
3905
+
"updated_at": "2025-12-27T22:07:07.897193100-05:00",
3906
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
3907
+
},
3908
+
{
3909
+
"id": 356,
3910
+
"change_id": "df681aa8-e470-4ead-a0d2-a4095febfa3d",
3911
+
"node_type": "action",
3912
+
"title": "Fixing dark mode config to use media queries",
3913
+
"description": null,
3914
+
"status": "completed",
3915
+
"created_at": "2025-12-27T22:07:24.774976300-05:00",
3916
+
"updated_at": "2025-12-27T22:07:30.392290200-05:00",
3917
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
3918
+
},
3919
+
{
3920
+
"id": 357,
3921
+
"change_id": "57060303-5a30-4f11-a752-a02376df5ea7",
3922
+
"node_type": "action",
3923
+
"title": "Making server offline message conditional on build mode",
3924
+
"description": null,
3925
+
"status": "completed",
3926
+
"created_at": "2025-12-27T22:07:49.952419800-05:00",
3927
+
"updated_at": "2025-12-27T22:09:00.514201500-05:00",
3928
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
3929
+
},
3930
+
{
3931
+
"id": 358,
3932
+
"change_id": "fc211ac7-7a1a-4b69-835a-992c354e8237",
3933
+
"node_type": "outcome",
3934
+
"title": "Successfully fixed dark mode and dev/prod messaging",
3935
+
"description": null,
3936
+
"status": "completed",
3937
+
"created_at": "2025-12-27T22:09:28.843864300-05:00",
3938
+
"updated_at": "2025-12-27T22:09:32.017503200-05:00",
3939
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
3940
+
},
3941
+
{
3942
+
"id": 359,
3943
+
"change_id": "4a7d5885-1713-4ba7-ad13-bb12b58c9410",
3944
+
"node_type": "outcome",
3945
+
"title": "Committed fixes to git",
3946
+
"description": null,
3947
+
"status": "completed",
3948
+
"created_at": "2025-12-27T22:10:25.576235500-05:00",
3949
+
"updated_at": "2025-12-27T22:10:28.961887300-05:00",
3950
+
"metadata_json": "{\"branch\":\"master\",\"commit\":\"bd3aabb\",\"confidence\":95}"
3951
+
},
3952
+
{
3953
+
"id": 360,
3954
+
"change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a",
3955
+
"node_type": "goal",
3956
+
"title": "Fix extension not detecting login session despite dev server running",
3957
+
"description": null,
3958
+
"status": "completed",
3959
+
"created_at": "2025-12-27T22:23:13.072419900-05:00",
3960
+
"updated_at": "2025-12-27T22:41:49.160848100-05:00",
3961
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90,\"prompt\":\"dark mode is fixed, but the extension in /chrome/ uploaded still is saying login with atlast and dev server is running\"}"
3962
+
},
3963
+
{
3964
+
"id": 361,
3965
+
"change_id": "aecf2327-d20d-4c6c-b6b0-06ccf26a2b27",
3966
+
"node_type": "observation",
3967
+
"title": "Extension dist/chrome contains production build, not dev build. User ran build:prod last.",
3968
+
"description": null,
3969
+
"status": "completed",
3970
+
"created_at": "2025-12-27T22:23:45.918832500-05:00",
3971
+
"updated_at": "2025-12-27T22:23:48.919570500-05:00",
3972
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
3973
+
},
3974
+
{
3975
+
"id": 362,
3976
+
"change_id": "e897db97-44d8-4993-b4c3-0d829265b2f8",
3977
+
"node_type": "observation",
3978
+
"title": "Dev build now deployed. Extension will check session at http://127.0.0.1:8888/.netlify/functions/session with credentials:include",
3979
+
"description": null,
3980
+
"status": "completed",
3981
+
"created_at": "2025-12-27T22:24:17.767230200-05:00",
3982
+
"updated_at": "2025-12-27T22:24:20.981953100-05:00",
3983
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
3984
+
},
3985
+
{
3986
+
"id": 363,
3987
+
"change_id": "2c62bfa3-d148-4448-8c2b-d0cf1e94ceb0",
3988
+
"node_type": "observation",
3989
+
"title": "Found CORS issue: successResponse uses 'Access-Control-Allow-Origin: *' which blocks credentialed requests from extension",
3990
+
"description": null,
3991
+
"status": "completed",
3992
+
"created_at": "2025-12-27T22:24:51.861265800-05:00",
3993
+
"updated_at": "2025-12-27T22:24:55.482724500-05:00",
3994
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
3995
+
},
3996
+
{
3997
+
"id": 364,
3998
+
"change_id": "560d6bea-47ec-408d-919b-15ca7198aac9",
3999
+
"node_type": "action",
4000
+
"title": "Updating CORS headers to support credentialed requests from extension",
4001
+
"description": null,
4002
+
"status": "completed",
4003
+
"created_at": "2025-12-27T22:25:23.035212700-05:00",
4004
+
"updated_at": "2025-12-27T22:26:03.046221900-05:00",
4005
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
4006
+
},
4007
+
{
4008
+
"id": 365,
4009
+
"change_id": "3ef0c9e9-aa40-4914-a5f4-32bcfaf68d04",
4010
+
"node_type": "outcome",
4011
+
"title": "Fixed CORS to support credentialed requests from extensions",
4012
+
"description": null,
4013
+
"status": "completed",
4014
+
"created_at": "2025-12-27T22:41:38.430661200-05:00",
4015
+
"updated_at": "2025-12-27T22:41:48.981429600-05:00",
4016
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4017
+
},
4018
+
{
4019
+
"id": 366,
4020
+
"change_id": "77b7ed7e-a113-41f6-a677-50d376f3f008",
4021
+
"node_type": "outcome",
4022
+
"title": "Committed CORS fixes to git",
4023
+
"description": null,
4024
+
"status": "completed",
4025
+
"created_at": "2025-12-27T22:42:49.037783-05:00",
4026
+
"updated_at": "2025-12-27T22:42:54.162857-05:00",
4027
+
"metadata_json": "{\"branch\":\"master\",\"commit\":\"603cf0a\",\"confidence\":95}"
4028
+
},
4029
+
{
4030
+
"id": 367,
4031
+
"change_id": "df6abf7a-e7a4-45f3-8485-b933319416d9",
4032
+
"node_type": "goal",
4033
+
"title": "Create Firefox-compatible version of Twitter scraper extension",
4034
+
"description": null,
4035
+
"status": "completed",
4036
+
"created_at": "2025-12-28T18:09:33.241860800-05:00",
4037
+
"updated_at": "2025-12-28T19:21:32.412499-05:00",
4038
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":85,\"prompt\":\"let's make the extension have a firefox compatible version too.\"}"
4039
+
},
4040
+
{
4041
+
"id": 368,
4042
+
"change_id": "79721edf-aa05-4580-8c28-7d20941ef155",
4043
+
"node_type": "observation",
4044
+
"title": "Current extension uses Manifest V3 with Chrome-specific APIs",
4045
+
"description": null,
4046
+
"status": "pending",
4047
+
"created_at": "2025-12-28T18:10:08.441348100-05:00",
4048
+
"updated_at": "2025-12-28T18:10:08.441348100-05:00",
4049
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4050
+
},
4051
+
{
4052
+
"id": 369,
4053
+
"change_id": "783841d0-c096-48f6-be18-193a9dcc7d4b",
4054
+
"node_type": "observation",
4055
+
"title": "Firefox compatibility analysis: Extension uses chrome.* APIs (runtime.sendMessage, storage.local, tabs.query/sendMessage), MV3 service worker. Firefox supports MV3 but has differences. Options: 1) Use webextension-polyfill for cross-browser, 2) Dual manifests (MV3 Chrome + MV2 Firefox), 3) Keep MV3 for both with minimal changes. Current build outputs to dist/chrome only.",
4056
+
"description": null,
4057
+
"status": "pending",
4058
+
"created_at": "2025-12-28T18:10:48.087066800-05:00",
4059
+
"updated_at": "2025-12-28T18:10:48.087066800-05:00",
4060
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
4061
+
},
4062
+
{
4063
+
"id": 370,
4064
+
"change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6",
4065
+
"node_type": "decision",
4066
+
"title": "Choose Firefox compatibility approach: webextension-polyfill, dual manifests, or minimal MV3 changes",
4067
+
"description": null,
4068
+
"status": "pending",
4069
+
"created_at": "2025-12-28T18:10:50.375270400-05:00",
4070
+
"updated_at": "2025-12-28T18:10:50.375270400-05:00",
4071
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":80}"
4072
+
},
4073
+
{
4074
+
"id": 371,
4075
+
"change_id": "159906da-984f-4a1d-a1a6-98e0fc0cf369",
4076
+
"node_type": "option",
4077
+
"title": "Use webextension-polyfill library for unified cross-browser API",
4078
+
"description": null,
4079
+
"status": "pending",
4080
+
"created_at": "2025-12-28T18:11:05.947924200-05:00",
4081
+
"updated_at": "2025-12-28T18:11:05.947924200-05:00",
4082
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":85}"
4083
+
},
4084
+
{
4085
+
"id": 372,
4086
+
"change_id": "df5e42e6-53c1-4b30-8b6f-f2385cd9e247",
4087
+
"node_type": "option",
4088
+
"title": "Dual manifests: MV3 for Chrome, MV2 for Firefox with separate builds",
4089
+
"description": null,
4090
+
"status": "pending",
4091
+
"created_at": "2025-12-28T18:11:08.179938100-05:00",
4092
+
"updated_at": "2025-12-28T18:11:08.179938100-05:00",
4093
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":85}"
4094
+
},
4095
+
{
4096
+
"id": 373,
4097
+
"change_id": "7bb58202-7a9b-4e8b-8b9e-927e5106bce7",
4098
+
"node_type": "option",
4099
+
"title": "Keep MV3 for both browsers with minimal manifest tweaks",
4100
+
"description": null,
4101
+
"status": "pending",
4102
+
"created_at": "2025-12-28T18:11:10.370113600-05:00",
4103
+
"updated_at": "2025-12-28T18:11:10.370113600-05:00",
4104
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":85}"
4105
+
},
4106
+
{
4107
+
"id": 374,
4108
+
"change_id": "d41b29e0-cd48-4dac-a6c8-c6179612702e",
4109
+
"node_type": "outcome",
4110
+
"title": "Chose webextension-polyfill approach. Provides unified browser.* API, Promise-based, future-proof MV3 for both browsers, +20KB but cleaner codebase",
4111
+
"description": null,
4112
+
"status": "pending",
4113
+
"created_at": "2025-12-28T19:04:24.676770900-05:00",
4114
+
"updated_at": "2025-12-28T19:04:24.676770900-05:00",
4115
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4116
+
},
4117
+
{
4118
+
"id": 375,
4119
+
"change_id": "5bb34b8b-aec4-4f84-993e-eb9bf7a2d13f",
4120
+
"node_type": "action",
4121
+
"title": "Installing webextension-polyfill and updating source files to use browser.* API",
4122
+
"description": null,
4123
+
"status": "completed",
4124
+
"created_at": "2025-12-28T19:08:14.642882400-05:00",
4125
+
"updated_at": "2025-12-28T19:21:32.531034800-05:00",
4126
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
4127
+
},
4128
+
{
4129
+
"id": 376,
4130
+
"change_id": "644181ee-5a44-4967-9657-e9dd5f648c5e",
4131
+
"node_type": "outcome",
4132
+
"title": "Successfully implemented Firefox compatibility with webextension-polyfill. Both Chrome and Firefox builds compile successfully. Chrome uses service_worker (MV3), Firefox uses scripts array with browser_specific_settings. All chrome.* API calls replaced with browser.* imports.",
4133
+
"description": null,
4134
+
"status": "completed",
4135
+
"created_at": "2025-12-28T19:14:22.309457600-05:00",
4136
+
"updated_at": "2025-12-28T19:21:32.658297400-05:00",
4137
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4138
+
},
4139
+
{
4140
+
"id": 377,
4141
+
"change_id": "1dffa024-413f-4a95-b069-66db350abfaa",
4142
+
"node_type": "goal",
4143
+
"title": "Fix Firefox extension server detection and login check",
4144
+
"description": null,
4145
+
"status": "completed",
4146
+
"created_at": "2025-12-28T20:14:51.646204800-05:00",
4147
+
"updated_at": "2025-12-28T20:32:19.249555-05:00",
4148
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":85,\"prompt\":\"The extension works in chrome. In firefox, it's failing to detect that the dev server is running and open + logged in on firefox. There's no right-click to inspect on the popup either.\"}"
4149
+
},
4150
+
{
4151
+
"id": 378,
4152
+
"change_id": "9d5626d2-a9ae-42aa-8fda-be3c7528156f",
4153
+
"node_type": "observation",
4154
+
"title": "Firefox extension debugging differs from Chrome - need to use about:debugging Inspect button or Browser Console, not right-click popup",
4155
+
"description": null,
4156
+
"status": "pending",
4157
+
"created_at": "2025-12-28T20:15:11.710473-05:00",
4158
+
"updated_at": "2025-12-28T20:15:11.710473-05:00",
4159
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4160
+
},
4161
+
{
4162
+
"id": 379,
4163
+
"change_id": "7a5af3fe-8567-4f1c-85cd-e47891704974",
4164
+
"node_type": "observation",
4165
+
"title": "Potential Firefox issues: 1) CORS with credentials:include may be stricter, 2) Cookie partitioning/third-party cookie blocking, 3) Extension needs explicit host_permissions for cookies to work. Firefox manifest has host_permissions but may need additional cookie permissions.",
4166
+
"description": null,
4167
+
"status": "pending",
4168
+
"created_at": "2025-12-28T20:15:31.278249900-05:00",
4169
+
"updated_at": "2025-12-28T20:15:31.278249900-05:00",
4170
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":85}"
4171
+
},
4172
+
{
4173
+
"id": 380,
4174
+
"change_id": "9c197aae-18d5-46ae-87e7-82c240c8f313",
4175
+
"node_type": "action",
4176
+
"title": "Adding cookies permission to Firefox manifest for credentials:include support",
4177
+
"description": null,
4178
+
"status": "pending",
4179
+
"created_at": "2025-12-28T20:16:12.019659700-05:00",
4180
+
"updated_at": "2025-12-28T20:16:12.019659700-05:00",
4181
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
4182
+
},
4183
+
{
4184
+
"id": 381,
4185
+
"change_id": "485a03b0-8a25-4fdf-a8e2-9d3a25c8edf8",
4186
+
"node_type": "outcome",
4187
+
"title": "Fixed Firefox cookie issue by adding cookies permission to manifest. Firefox requires explicit permission even with host_permissions. Rebuild successful.",
4188
+
"description": null,
4189
+
"status": "pending",
4190
+
"created_at": "2025-12-28T20:16:41.702322300-05:00",
4191
+
"updated_at": "2025-12-28T20:16:41.702322300-05:00",
4192
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4193
+
},
4194
+
{
4195
+
"id": 382,
4196
+
"change_id": "35b13d37-0228-435f-a4bc-c5c42811fec3",
4197
+
"node_type": "observation",
4198
+
"title": "Firefox blocks extension fetch with CORS error despite host_permissions. Server responds 200 but missing Access-Control-Allow-Origin header. Firefox stricter than Chrome on extension CORS.",
4199
+
"description": null,
4200
+
"status": "pending",
4201
+
"created_at": "2025-12-28T20:17:23.414134300-05:00",
4202
+
"updated_at": "2025-12-28T20:17:23.414134300-05:00",
4203
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4204
+
},
4205
+
{
4206
+
"id": 383,
4207
+
"change_id": "adc120cd-e56d-400a-9b3e-8207880378c3",
4208
+
"node_type": "action",
4209
+
"title": "Adding CORS headers to netlify.toml for extension compatibility - wildcard origin with credentials for dev",
4210
+
"description": null,
4211
+
"status": "pending",
4212
+
"created_at": "2025-12-28T20:18:22.172869600-05:00",
4213
+
"updated_at": "2025-12-28T20:18:22.172869600-05:00",
4214
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
4215
+
},
4216
+
{
4217
+
"id": 384,
4218
+
"change_id": "0f77bfd9-590f-4f1e-be08-78a9deef6d8a",
4219
+
"node_type": "outcome",
4220
+
"title": "Added CORS headers to netlify.toml for all paths including root and functions. Headers include Access-Control-Allow-Origin:*, Allow-Credentials:true for dev environment. User needs to restart dev server.",
4221
+
"description": null,
4222
+
"status": "pending",
4223
+
"created_at": "2025-12-28T20:19:54.829093600-05:00",
4224
+
"updated_at": "2025-12-28T20:19:54.829093600-05:00",
4225
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":90}"
4226
+
},
4227
+
{
4228
+
"id": 385,
4229
+
"change_id": "cc0910f0-2381-4aee-bb5d-397cb0f804d1",
4230
+
"node_type": "observation",
4231
+
"title": "CORS wildcard (*) incompatible with credentials:include. Browser security prevents wildcard CORS with credentialed requests. Extension origins are dynamic (moz-extension://, chrome-extension://). Need to handle CORS in serverless functions by reflecting request origin.",
4232
+
"description": null,
4233
+
"status": "pending",
4234
+
"created_at": "2025-12-28T20:27:31.848523900-05:00",
4235
+
"updated_at": "2025-12-28T20:27:31.848523900-05:00",
4236
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4237
+
},
4238
+
{
4239
+
"id": 386,
4240
+
"change_id": "ad4a5ca7-15d1-4776-8ede-6b615613f6e1",
4241
+
"node_type": "action",
4242
+
"title": "Adding moz-extension:// origin detection to CORS handler for Firefox extension support",
4243
+
"description": null,
4244
+
"status": "completed",
4245
+
"created_at": "2025-12-28T20:28:31.661326900-05:00",
4246
+
"updated_at": "2025-12-28T20:32:19.367968600-05:00",
4247
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4248
+
},
4249
+
{
4250
+
"id": 387,
4251
+
"change_id": "cffdee0f-8535-4d88-83ed-fdf6101f7ac3",
4252
+
"node_type": "outcome",
4253
+
"title": "Fixed Firefox extension CORS by adding moz-extension:// origin detection to response.utils.ts. Reverted netlify.toml changes as functions handle CORS correctly. User needs to restart dev server.",
4254
+
"description": null,
4255
+
"status": "completed",
4256
+
"created_at": "2025-12-28T20:29:39.856303800-05:00",
4257
+
"updated_at": "2025-12-28T20:32:19.494690-05:00",
4258
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4259
+
},
4260
+
{
4261
+
"id": 388,
4262
+
"change_id": "0ada864e-be98-4a2f-a14e-ffd3eea9aaa9",
4263
+
"node_type": "observation",
4264
+
"title": "Health check uses HEAD request to root URL (Vite server), not a Netlify function. Doesn't get CORS headers from getCorsHeaders. Need dedicated health endpoint or change check to use existing function.",
4265
+
"description": null,
4266
+
"status": "completed",
4267
+
"created_at": "2025-12-28T20:37:22.132717600-05:00",
4268
+
"updated_at": "2025-12-28T20:38:41.630020900-05:00",
4269
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4270
+
},
4271
+
{
4272
+
"id": 389,
4273
+
"change_id": "f522d5b2-c325-4f34-9f27-b8ea5c50618d",
4274
+
"node_type": "outcome",
4275
+
"title": "Created /health function endpoint with CORS support. Updated checkServerHealth to use /.netlify/functions/health instead of root URL. Extension rebuilt successfully.",
4276
+
"description": null,
4277
+
"status": "completed",
4278
+
"created_at": "2025-12-28T20:38:19.981309500-05:00",
4279
+
"updated_at": "2025-12-28T20:38:41.780183300-05:00",
4280
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4281
+
},
4282
+
{
4283
+
"id": 390,
4284
+
"change_id": "cfdcf45b-47b3-4239-8053-417bd31957ed",
4285
+
"node_type": "observation",
4286
+
"title": "Server receives session request but returns CORS wildcard (*) instead of extension origin. No session cookie received. Origin header might not be sent by Firefox extension or not detected correctly.",
4287
+
"description": null,
4288
+
"status": "pending",
4289
+
"created_at": "2025-12-28T20:48:12.770638500-05:00",
4290
+
"updated_at": "2025-12-28T20:48:12.770638500-05:00",
4291
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4292
+
},
4293
+
{
4294
+
"id": 391,
4295
+
"change_id": "2b53a419-9a47-4285-9a12-9bdfaeeb9ff0",
4296
+
"node_type": "observation",
4297
+
"title": "Health endpoint gets CORS headers correctly (moz-extension detected). Session endpoint error middleware doesn't pass event to errorResponse, returns wildcard CORS. Need to fix error middleware to pass event.",
4298
+
"description": null,
4299
+
"status": "completed",
4300
+
"created_at": "2025-12-28T20:55:32.024834200-05:00",
4301
+
"updated_at": "2025-12-28T21:38:14.729731500-05:00",
4302
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4303
+
},
4304
+
{
4305
+
"id": 392,
4306
+
"change_id": "c941d136-3405-483d-bf34-7fb011f6d072",
4307
+
"node_type": "action",
4308
+
"title": "Fixed error middleware to pass event to errorResponse for proper CORS headers on errors",
4309
+
"description": null,
4310
+
"status": "completed",
4311
+
"created_at": "2025-12-28T20:56:38.876266200-05:00",
4312
+
"updated_at": "2025-12-28T21:38:14.888627800-05:00",
4313
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4314
+
},
4315
+
{
4316
+
"id": 393,
4317
+
"change_id": "aafd9977-8800-4152-9f7f-b817db6df573",
4318
+
"node_type": "outcome",
4319
+
"title": "Fixed Firefox extension CORS completely. Error middleware now passes event to errorResponse so Firefox extension origin is properly reflected in error responses with credentials. Debug logging removed.",
4320
+
"description": null,
4321
+
"status": "completed",
4322
+
"created_at": "2025-12-28T21:37:22.780953600-05:00",
4323
+
"updated_at": "2025-12-28T21:38:15.071425500-05:00",
4324
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4325
+
},
4326
+
{
4327
+
"id": 394,
4328
+
"change_id": "3b0dea7a-c3cd-45a8-ba1a-f1040aa4e1d9",
4329
+
"node_type": "observation",
4330
+
"title": "CORS fully working - Firefox extension origin properly reflected with credentials. But cookies not sent from extension despite credentials:include. Cookie set in web context not accessible from extension context due to Firefox cookie partitioning.",
4331
+
"description": null,
4332
+
"status": "pending",
4333
+
"created_at": "2025-12-28T21:46:45.822343200-05:00",
4334
+
"updated_at": "2025-12-28T21:46:45.822343200-05:00",
4335
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4336
+
},
4337
+
{
4338
+
"id": 395,
4339
+
"change_id": "8a93413f-a09c-4cc1-8693-4fe90dc055c4",
4340
+
"node_type": "action",
4341
+
"title": "Updated extension checkSession to read cookie via browser.cookies API and pass as query parameter. Workaround for Firefox SameSite=Lax cookie partitioning.",
4342
+
"description": null,
4343
+
"status": "pending",
4344
+
"created_at": "2025-12-28T21:52:22.059862700-05:00",
4345
+
"updated_at": "2025-12-28T21:52:22.059862700-05:00",
4346
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
4347
+
},
4348
+
{
4349
+
"id": 396,
4350
+
"change_id": "864dd973-5f15-4e31-a7da-c548dbbe1f0e",
4351
+
"node_type": "outcome",
4352
+
"title": "Extension now uses browser.cookies.get() API to read session cookie and pass as query parameter. Workaround for Firefox SameSite=Lax cookie partitioning in extensions. Extension rebuilt successfully.",
4353
+
"description": null,
4354
+
"status": "pending",
4355
+
"created_at": "2025-12-28T22:51:31.578965200-05:00",
4356
+
"updated_at": "2025-12-28T22:51:31.578965200-05:00",
4357
+
"metadata_json": "{\"branch\":\"master\",\"confidence\":95}"
3852
4358
}
3853
4359
],
3854
4360
"edges": [
···
7701
8207
"weight": 1.0,
7702
8208
"rationale": "Final outcome of Tailwind integration",
7703
8209
"created_at": "2025-12-27T18:07:51.011406300-05:00"
8210
+
},
8211
+
{
8212
+
"id": 351,
8213
+
"from_node_id": 344,
8214
+
"to_node_id": 351,
8215
+
"from_change_id": "2a06900e-ea62-4adf-81d5-7f0cf1a29b31",
8216
+
"to_change_id": "9468bcb3-78ec-4dae-8d8f-968ba6f5b3fe",
8217
+
"edge_type": "leads_to",
8218
+
"weight": 1.0,
8219
+
"rationale": "Git commit for Tailwind integration",
8220
+
"created_at": "2025-12-27T18:38:58.347778400-05:00"
8221
+
},
8222
+
{
8223
+
"id": 352,
8224
+
"from_node_id": 352,
8225
+
"to_node_id": 353,
8226
+
"from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5",
8227
+
"to_change_id": "eaed6e9b-9f16-4b45-8783-44ea2ea1f2a9",
8228
+
"edge_type": "leads_to",
8229
+
"weight": 1.0,
8230
+
"rationale": "Initial analysis of issues",
8231
+
"created_at": "2025-12-27T22:06:21.516165300-05:00"
8232
+
},
8233
+
{
8234
+
"id": 353,
8235
+
"from_node_id": 352,
8236
+
"to_node_id": 354,
8237
+
"from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5",
8238
+
"to_change_id": "d66fc83e-9737-4047-8ce2-e2ba857aeea9",
8239
+
"edge_type": "leads_to",
8240
+
"weight": 1.0,
8241
+
"rationale": "Need to decide dark mode approach",
8242
+
"created_at": "2025-12-27T22:07:03.103941500-05:00"
8243
+
},
8244
+
{
8245
+
"id": 354,
8246
+
"from_node_id": 354,
8247
+
"to_node_id": 355,
8248
+
"from_change_id": "d66fc83e-9737-4047-8ce2-e2ba857aeea9",
8249
+
"to_change_id": "76e2a379-7803-4c82-8013-be6b62f2d360",
8250
+
"edge_type": "leads_to",
8251
+
"weight": 1.0,
8252
+
"rationale": "Decision outcome",
8253
+
"created_at": "2025-12-27T22:07:06.239151500-05:00"
8254
+
},
8255
+
{
8256
+
"id": 355,
8257
+
"from_node_id": 352,
8258
+
"to_node_id": 356,
8259
+
"from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5",
8260
+
"to_change_id": "df681aa8-e470-4ead-a0d2-a4095febfa3d",
8261
+
"edge_type": "leads_to",
8262
+
"weight": 1.0,
8263
+
"rationale": "Implementation of dark mode fix",
8264
+
"created_at": "2025-12-27T22:07:26.713411300-05:00"
8265
+
},
8266
+
{
8267
+
"id": 356,
8268
+
"from_node_id": 352,
8269
+
"to_node_id": 357,
8270
+
"from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5",
8271
+
"to_change_id": "57060303-5a30-4f11-a752-a02376df5ea7",
8272
+
"edge_type": "leads_to",
8273
+
"weight": 1.0,
8274
+
"rationale": "Implementation of server message fix",
8275
+
"created_at": "2025-12-27T22:07:51.662925600-05:00"
8276
+
},
8277
+
{
8278
+
"id": 357,
8279
+
"from_node_id": 352,
8280
+
"to_node_id": 358,
8281
+
"from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5",
8282
+
"to_change_id": "fc211ac7-7a1a-4b69-835a-992c354e8237",
8283
+
"edge_type": "leads_to",
8284
+
"weight": 1.0,
8285
+
"rationale": "Final outcome of fixes",
8286
+
"created_at": "2025-12-27T22:09:30.425884400-05:00"
8287
+
},
8288
+
{
8289
+
"id": 358,
8290
+
"from_node_id": 352,
8291
+
"to_node_id": 359,
8292
+
"from_change_id": "b852ce18-1747-4c26-a65e-acfbbed2b1a5",
8293
+
"to_change_id": "4a7d5885-1713-4ba7-ad13-bb12b58c9410",
8294
+
"edge_type": "leads_to",
8295
+
"weight": 1.0,
8296
+
"rationale": "Git commit for fixes",
8297
+
"created_at": "2025-12-27T22:10:27.225192300-05:00"
8298
+
},
8299
+
{
8300
+
"id": 359,
8301
+
"from_node_id": 360,
8302
+
"to_node_id": 361,
8303
+
"from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a",
8304
+
"to_change_id": "aecf2327-d20d-4c6c-b6b0-06ccf26a2b27",
8305
+
"edge_type": "leads_to",
8306
+
"weight": 1.0,
8307
+
"rationale": "Root cause analysis",
8308
+
"created_at": "2025-12-27T22:23:47.445630900-05:00"
8309
+
},
8310
+
{
8311
+
"id": 360,
8312
+
"from_node_id": 360,
8313
+
"to_node_id": 362,
8314
+
"from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a",
8315
+
"to_change_id": "e897db97-44d8-4993-b4c3-0d829265b2f8",
8316
+
"edge_type": "leads_to",
8317
+
"weight": 1.0,
8318
+
"rationale": "Rebuilt dev version",
8319
+
"created_at": "2025-12-27T22:24:19.438433600-05:00"
8320
+
},
8321
+
{
8322
+
"id": 361,
8323
+
"from_node_id": 360,
8324
+
"to_node_id": 363,
8325
+
"from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a",
8326
+
"to_change_id": "2c62bfa3-d148-4448-8c2b-d0cf1e94ceb0",
8327
+
"edge_type": "leads_to",
8328
+
"weight": 1.0,
8329
+
"rationale": "Root cause: CORS configuration",
8330
+
"created_at": "2025-12-27T22:24:53.741163700-05:00"
8331
+
},
8332
+
{
8333
+
"id": 362,
8334
+
"from_node_id": 360,
8335
+
"to_node_id": 364,
8336
+
"from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a",
8337
+
"to_change_id": "560d6bea-47ec-408d-919b-15ca7198aac9",
8338
+
"edge_type": "leads_to",
8339
+
"weight": 1.0,
8340
+
"rationale": "Implementation of CORS fix",
8341
+
"created_at": "2025-12-27T22:25:24.843330900-05:00"
8342
+
},
8343
+
{
8344
+
"id": 363,
8345
+
"from_node_id": 360,
8346
+
"to_node_id": 365,
8347
+
"from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a",
8348
+
"to_change_id": "3ef0c9e9-aa40-4914-a5f4-32bcfaf68d04",
8349
+
"edge_type": "leads_to",
8350
+
"weight": 1.0,
8351
+
"rationale": "CORS fix completed",
8352
+
"created_at": "2025-12-27T22:41:44.160528300-05:00"
8353
+
},
8354
+
{
8355
+
"id": 364,
8356
+
"from_node_id": 360,
8357
+
"to_node_id": 366,
8358
+
"from_change_id": "706d5a7f-08ed-43f7-aee5-0bed28d9402a",
8359
+
"to_change_id": "77b7ed7e-a113-41f6-a677-50d376f3f008",
8360
+
"edge_type": "leads_to",
8361
+
"weight": 1.0,
8362
+
"rationale": "Git commit for CORS fixes",
8363
+
"created_at": "2025-12-27T22:42:51.663598100-05:00"
8364
+
},
8365
+
{
8366
+
"id": 365,
8367
+
"from_node_id": 367,
8368
+
"to_node_id": 368,
8369
+
"from_change_id": "df6abf7a-e7a4-45f3-8485-b933319416d9",
8370
+
"to_change_id": "79721edf-aa05-4580-8c28-7d20941ef155",
8371
+
"edge_type": "leads_to",
8372
+
"weight": 1.0,
8373
+
"rationale": "Analysis step for Firefox compatibility",
8374
+
"created_at": "2025-12-28T18:10:09.484445500-05:00"
8375
+
},
8376
+
{
8377
+
"id": 366,
8378
+
"from_node_id": 368,
8379
+
"to_node_id": 369,
8380
+
"from_change_id": "79721edf-aa05-4580-8c28-7d20941ef155",
8381
+
"to_change_id": "783841d0-c096-48f6-be18-193a9dcc7d4b",
8382
+
"edge_type": "leads_to",
8383
+
"weight": 1.0,
8384
+
"rationale": "Detailed analysis of compatibility issues",
8385
+
"created_at": "2025-12-28T18:10:49.163552300-05:00"
8386
+
},
8387
+
{
8388
+
"id": 367,
8389
+
"from_node_id": 369,
8390
+
"to_node_id": 370,
8391
+
"from_change_id": "783841d0-c096-48f6-be18-193a9dcc7d4b",
8392
+
"to_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6",
8393
+
"edge_type": "leads_to",
8394
+
"weight": 1.0,
8395
+
"rationale": "Need to decide implementation strategy",
8396
+
"created_at": "2025-12-28T18:10:51.434960600-05:00"
8397
+
},
8398
+
{
8399
+
"id": 368,
8400
+
"from_node_id": 370,
8401
+
"to_node_id": 371,
8402
+
"from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6",
8403
+
"to_change_id": "159906da-984f-4a1d-a1a6-98e0fc0cf369",
8404
+
"edge_type": "leads_to",
8405
+
"weight": 1.0,
8406
+
"rationale": "Option A",
8407
+
"created_at": "2025-12-28T18:11:07.060637-05:00"
8408
+
},
8409
+
{
8410
+
"id": 369,
8411
+
"from_node_id": 370,
8412
+
"to_node_id": 372,
8413
+
"from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6",
8414
+
"to_change_id": "df5e42e6-53c1-4b30-8b6f-f2385cd9e247",
8415
+
"edge_type": "leads_to",
8416
+
"weight": 1.0,
8417
+
"rationale": "Option B",
8418
+
"created_at": "2025-12-28T18:11:09.223792400-05:00"
8419
+
},
8420
+
{
8421
+
"id": 370,
8422
+
"from_node_id": 370,
8423
+
"to_node_id": 373,
8424
+
"from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6",
8425
+
"to_change_id": "7bb58202-7a9b-4e8b-8b9e-927e5106bce7",
8426
+
"edge_type": "leads_to",
8427
+
"weight": 1.0,
8428
+
"rationale": "Option C",
8429
+
"created_at": "2025-12-28T18:11:11.439827800-05:00"
8430
+
},
8431
+
{
8432
+
"id": 371,
8433
+
"from_node_id": 370,
8434
+
"to_node_id": 374,
8435
+
"from_change_id": "fd2d5b63-c26c-4592-89a6-3ccb4234c3c6",
8436
+
"to_change_id": "d41b29e0-cd48-4dac-a6c8-c6179612702e",
8437
+
"edge_type": "leads_to",
8438
+
"weight": 1.0,
8439
+
"rationale": "User selected option 1",
8440
+
"created_at": "2025-12-28T19:04:26.708742600-05:00"
8441
+
},
8442
+
{
8443
+
"id": 372,
8444
+
"from_node_id": 374,
8445
+
"to_node_id": 375,
8446
+
"from_change_id": "d41b29e0-cd48-4dac-a6c8-c6179612702e",
8447
+
"to_change_id": "5bb34b8b-aec4-4f84-993e-eb9bf7a2d13f",
8448
+
"edge_type": "leads_to",
8449
+
"weight": 1.0,
8450
+
"rationale": "Implementation based on decision",
8451
+
"created_at": "2025-12-28T19:08:16.677078600-05:00"
8452
+
},
8453
+
{
8454
+
"id": 373,
8455
+
"from_node_id": 375,
8456
+
"to_node_id": 376,
8457
+
"from_change_id": "5bb34b8b-aec4-4f84-993e-eb9bf7a2d13f",
8458
+
"to_change_id": "644181ee-5a44-4967-9657-e9dd5f648c5e",
8459
+
"edge_type": "leads_to",
8460
+
"weight": 1.0,
8461
+
"rationale": "Implementation completed successfully",
8462
+
"created_at": "2025-12-28T19:14:24.961595600-05:00"
8463
+
},
8464
+
{
8465
+
"id": 374,
8466
+
"from_node_id": 377,
8467
+
"to_node_id": 378,
8468
+
"from_change_id": "1dffa024-413f-4a95-b069-66db350abfaa",
8469
+
"to_change_id": "9d5626d2-a9ae-42aa-8fda-be3c7528156f",
8470
+
"edge_type": "leads_to",
8471
+
"weight": 1.0,
8472
+
"rationale": "First observation about debugging",
8473
+
"created_at": "2025-12-28T20:15:13.725635900-05:00"
8474
+
},
8475
+
{
8476
+
"id": 375,
8477
+
"from_node_id": 378,
8478
+
"to_node_id": 379,
8479
+
"from_change_id": "9d5626d2-a9ae-42aa-8fda-be3c7528156f",
8480
+
"to_change_id": "7a5af3fe-8567-4f1c-85cd-e47891704974",
8481
+
"edge_type": "leads_to",
8482
+
"weight": 1.0,
8483
+
"rationale": "Hypothesis about root causes",
8484
+
"created_at": "2025-12-28T20:15:33.187041700-05:00"
8485
+
},
8486
+
{
8487
+
"id": 376,
8488
+
"from_node_id": 379,
8489
+
"to_node_id": 380,
8490
+
"from_change_id": "7a5af3fe-8567-4f1c-85cd-e47891704974",
8491
+
"to_change_id": "9c197aae-18d5-46ae-87e7-82c240c8f313",
8492
+
"edge_type": "leads_to",
8493
+
"weight": 1.0,
8494
+
"rationale": "Fix based on hypothesis",
8495
+
"created_at": "2025-12-28T20:16:14.104406300-05:00"
8496
+
},
8497
+
{
8498
+
"id": 377,
8499
+
"from_node_id": 380,
8500
+
"to_node_id": 381,
8501
+
"from_change_id": "9c197aae-18d5-46ae-87e7-82c240c8f313",
8502
+
"to_change_id": "485a03b0-8a25-4fdf-a8e2-9d3a25c8edf8",
8503
+
"edge_type": "leads_to",
8504
+
"weight": 1.0,
8505
+
"rationale": "Fix implemented and tested",
8506
+
"created_at": "2025-12-28T20:16:43.953511400-05:00"
8507
+
},
8508
+
{
8509
+
"id": 378,
8510
+
"from_node_id": 381,
8511
+
"to_node_id": 382,
8512
+
"from_change_id": "485a03b0-8a25-4fdf-a8e2-9d3a25c8edf8",
8513
+
"to_change_id": "35b13d37-0228-435f-a4bc-c5c42811fec3",
8514
+
"edge_type": "leads_to",
8515
+
"weight": 1.0,
8516
+
"rationale": "Root cause identified from error logs",
8517
+
"created_at": "2025-12-28T20:17:25.488041200-05:00"
8518
+
},
8519
+
{
8520
+
"id": 379,
8521
+
"from_node_id": 382,
8522
+
"to_node_id": 383,
8523
+
"from_change_id": "35b13d37-0228-435f-a4bc-c5c42811fec3",
8524
+
"to_change_id": "adc120cd-e56d-400a-9b3e-8207880378c3",
8525
+
"edge_type": "leads_to",
8526
+
"weight": 1.0,
8527
+
"rationale": "Fix for CORS issue",
8528
+
"created_at": "2025-12-28T20:19:41.484076700-05:00"
8529
+
},
8530
+
{
8531
+
"id": 380,
8532
+
"from_node_id": 383,
8533
+
"to_node_id": 384,
8534
+
"from_change_id": "adc120cd-e56d-400a-9b3e-8207880378c3",
8535
+
"to_change_id": "0f77bfd9-590f-4f1e-be08-78a9deef6d8a",
8536
+
"edge_type": "leads_to",
8537
+
"weight": 1.0,
8538
+
"rationale": "Implementation complete",
8539
+
"created_at": "2025-12-28T20:19:56.872404900-05:00"
8540
+
},
8541
+
{
8542
+
"id": 381,
8543
+
"from_node_id": 384,
8544
+
"to_node_id": 385,
8545
+
"from_change_id": "0f77bfd9-590f-4f1e-be08-78a9deef6d8a",
8546
+
"to_change_id": "cc0910f0-2381-4aee-bb5d-397cb0f804d1",
8547
+
"edge_type": "leads_to",
8548
+
"weight": 1.0,
8549
+
"rationale": "New error reveals real issue",
8550
+
"created_at": "2025-12-28T20:27:34.035766400-05:00"
8551
+
},
8552
+
{
8553
+
"id": 382,
8554
+
"from_node_id": 385,
8555
+
"to_node_id": 386,
8556
+
"from_change_id": "cc0910f0-2381-4aee-bb5d-397cb0f804d1",
8557
+
"to_change_id": "ad4a5ca7-15d1-4776-8ede-6b615613f6e1",
8558
+
"edge_type": "leads_to",
8559
+
"weight": 1.0,
8560
+
"rationale": "Fix for Firefox extension origin",
8561
+
"created_at": "2025-12-28T20:28:33.839045700-05:00"
8562
+
},
8563
+
{
8564
+
"id": 383,
8565
+
"from_node_id": 386,
8566
+
"to_node_id": 387,
8567
+
"from_change_id": "ad4a5ca7-15d1-4776-8ede-6b615613f6e1",
8568
+
"to_change_id": "cffdee0f-8535-4d88-83ed-fdf6101f7ac3",
8569
+
"edge_type": "leads_to",
8570
+
"weight": 1.0,
8571
+
"rationale": "Complete fix implemented",
8572
+
"created_at": "2025-12-28T20:30:09.745415200-05:00"
8573
+
},
8574
+
{
8575
+
"id": 384,
8576
+
"from_node_id": 387,
8577
+
"to_node_id": 388,
8578
+
"from_change_id": "cffdee0f-8535-4d88-83ed-fdf6101f7ac3",
8579
+
"to_change_id": "0ada864e-be98-4a2f-a14e-ffd3eea9aaa9",
8580
+
"edge_type": "leads_to",
8581
+
"weight": 1.0,
8582
+
"rationale": "New issue discovered in health check",
8583
+
"created_at": "2025-12-28T20:37:24.355885500-05:00"
8584
+
},
8585
+
{
8586
+
"id": 385,
8587
+
"from_node_id": 388,
8588
+
"to_node_id": 389,
8589
+
"from_change_id": "0ada864e-be98-4a2f-a14e-ffd3eea9aaa9",
8590
+
"to_change_id": "f522d5b2-c325-4f34-9f27-b8ea5c50618d",
8591
+
"edge_type": "leads_to",
8592
+
"weight": 1.0,
8593
+
"rationale": "Fix implemented",
8594
+
"created_at": "2025-12-28T20:38:22.044029100-05:00"
8595
+
},
8596
+
{
8597
+
"id": 386,
8598
+
"from_node_id": 389,
8599
+
"to_node_id": 390,
8600
+
"from_change_id": "f522d5b2-c325-4f34-9f27-b8ea5c50618d",
8601
+
"to_change_id": "cfdcf45b-47b3-4239-8053-417bd31957ed",
8602
+
"edge_type": "leads_to",
8603
+
"weight": 1.0,
8604
+
"rationale": "Issue persists - need to debug headers",
8605
+
"created_at": "2025-12-28T20:48:14.949702100-05:00"
8606
+
},
8607
+
{
8608
+
"id": 387,
8609
+
"from_node_id": 390,
8610
+
"to_node_id": 391,
8611
+
"from_change_id": "cfdcf45b-47b3-4239-8053-417bd31957ed",
8612
+
"to_change_id": "2b53a419-9a47-4285-9a12-9bdfaeeb9ff0",
8613
+
"edge_type": "leads_to",
8614
+
"weight": 1.0,
8615
+
"rationale": "Root cause identified from debug logs",
8616
+
"created_at": "2025-12-28T20:55:34.094943700-05:00"
8617
+
},
8618
+
{
8619
+
"id": 388,
8620
+
"from_node_id": 391,
8621
+
"to_node_id": 392,
8622
+
"from_change_id": "2b53a419-9a47-4285-9a12-9bdfaeeb9ff0",
8623
+
"to_change_id": "c941d136-3405-483d-bf34-7fb011f6d072",
8624
+
"edge_type": "leads_to",
8625
+
"weight": 1.0,
8626
+
"rationale": "Fix implemented",
8627
+
"created_at": "2025-12-28T20:57:35.872426900-05:00"
8628
+
},
8629
+
{
8630
+
"id": 389,
8631
+
"from_node_id": 392,
8632
+
"to_node_id": 393,
8633
+
"from_change_id": "c941d136-3405-483d-bf34-7fb011f6d072",
8634
+
"to_change_id": "aafd9977-8800-4152-9f7f-b817db6df573",
8635
+
"edge_type": "leads_to",
8636
+
"weight": 1.0,
8637
+
"rationale": "Complete fix with cleanup",
8638
+
"created_at": "2025-12-28T21:37:27.704906300-05:00"
8639
+
},
8640
+
{
8641
+
"id": 390,
8642
+
"from_node_id": 393,
8643
+
"to_node_id": 394,
8644
+
"from_change_id": "aafd9977-8800-4152-9f7f-b817db6df573",
8645
+
"to_change_id": "3b0dea7a-c3cd-45a8-ba1a-f1040aa4e1d9",
8646
+
"edge_type": "leads_to",
8647
+
"weight": 1.0,
8648
+
"rationale": "New issue - cookie partitioning",
8649
+
"created_at": "2025-12-28T21:46:48.417911400-05:00"
8650
+
},
8651
+
{
8652
+
"id": 391,
8653
+
"from_node_id": 394,
8654
+
"to_node_id": 395,
8655
+
"from_change_id": "3b0dea7a-c3cd-45a8-ba1a-f1040aa4e1d9",
8656
+
"to_change_id": "8a93413f-a09c-4cc1-8693-4fe90dc055c4",
8657
+
"edge_type": "leads_to",
8658
+
"weight": 1.0,
8659
+
"rationale": "Workaround using browser.cookies API",
8660
+
"created_at": "2025-12-28T21:52:52.704792400-05:00"
8661
+
},
8662
+
{
8663
+
"id": 392,
8664
+
"from_node_id": 395,
8665
+
"to_node_id": 396,
8666
+
"from_change_id": "8a93413f-a09c-4cc1-8693-4fe90dc055c4",
8667
+
"to_change_id": "864dd973-5f15-4e31-a7da-c548dbbe1f0e",
8668
+
"edge_type": "leads_to",
8669
+
"weight": 1.0,
8670
+
"rationale": "Complete workaround",
8671
+
"created_at": "2025-12-28T22:51:33.159870400-05:00"
7704
8672
}
7705
8673
]
7706
8674
}
+173
packages/extension/FIREFOX.md
+173
packages/extension/FIREFOX.md
···
1
+
# Firefox Extension Installation Guide
2
+
3
+
The ATlast Importer extension now supports both Chrome and Firefox!
4
+
5
+
## Building for Firefox
6
+
7
+
The build system automatically creates both Chrome and Firefox versions:
8
+
9
+
```bash
10
+
pnpm run build # Development build for both browsers
11
+
pnpm run build:prod # Production build for both browsers
12
+
```
13
+
14
+
Output directories:
15
+
- `dist/chrome/` - Chrome/Edge version (Manifest V3 with service worker)
16
+
- `dist/firefox/` - Firefox version (Manifest V3 with scripts array)
17
+
18
+
## Installing in Firefox (Development)
19
+
20
+
### Option 1: Temporary Installation (for testing)
21
+
22
+
1. Open Firefox
23
+
2. Navigate to `about:debugging#/runtime/this-firefox`
24
+
3. Click "Load Temporary Add-on..."
25
+
4. Navigate to `packages/extension/dist/firefox/`
26
+
5. Select the `manifest.json` file
27
+
28
+
**Note:** Temporary extensions are removed when Firefox restarts.
29
+
30
+
### Option 2: Loading from ZIP (for distribution)
31
+
32
+
1. Build the production version:
33
+
```bash
34
+
pnpm run build:prod
35
+
pnpm run package:firefox
36
+
```
37
+
38
+
2. This creates `dist/firefox.zip`
39
+
40
+
3. For testing:
41
+
- Go to `about:debugging#/runtime/this-firefox`
42
+
- Click "Load Temporary Add-on..."
43
+
- Select the `firefox.zip` file
44
+
45
+
4. For publishing:
46
+
- Submit `firefox.zip` to [addons.mozilla.org](https://addons.mozilla.org/developers/)
47
+
48
+
## Key Differences from Chrome Version
49
+
50
+
### Manifest Differences
51
+
52
+
**Chrome (`manifest.chrome.json`):**
53
+
```json
54
+
{
55
+
"manifest_version": 3,
56
+
"background": {
57
+
"service_worker": "background/service-worker.js",
58
+
"type": "module"
59
+
}
60
+
}
61
+
```
62
+
63
+
**Firefox (`manifest.firefox.json`):**
64
+
```json
65
+
{
66
+
"manifest_version": 3,
67
+
"background": {
68
+
"scripts": ["background/service-worker.js"],
69
+
"type": "module"
70
+
},
71
+
"browser_specific_settings": {
72
+
"gecko": {
73
+
"id": "atlast-importer@byarielm.fyi",
74
+
"strict_min_version": "109.0"
75
+
}
76
+
}
77
+
}
78
+
```
79
+
80
+
### Cross-Browser Compatibility
81
+
82
+
- All code uses `webextension-polyfill` library
83
+
- Chrome-specific `chrome.*` APIs replaced with unified `browser.*` API
84
+
- Promise-based instead of callback-based
85
+
- Single codebase works across both browsers
86
+
87
+
### Requirements
88
+
89
+
- **Firefox:** Version 109+ (for Manifest V3 support)
90
+
- **Chrome/Edge:** Latest version
91
+
92
+
## Testing
93
+
94
+
After loading the extension in Firefox:
95
+
96
+
1. Navigate to Twitter/X Following page (e.g., `https://twitter.com/username/following`)
97
+
2. Click the extension icon in the toolbar
98
+
3. The popup should show "Ready to scan" state
99
+
4. Click "Start Scan" to scrape usernames
100
+
5. Click "Open on ATlast" to upload results
101
+
102
+
## Debugging
103
+
104
+
### View Console Logs
105
+
106
+
**Background Script:**
107
+
- Go to `about:debugging#/runtime/this-firefox`
108
+
- Find "ATlast Importer" in the list
109
+
- Click "Inspect"
110
+
111
+
**Popup:**
112
+
- Right-click extension icon โ "Inspect Extension"
113
+
114
+
**Content Script:**
115
+
- Open DevTools on Twitter/X page (F12)
116
+
- Look for `[ATlast]` prefixed logs in Console
117
+
118
+
### Common Issues
119
+
120
+
1. **Extension not loading:**
121
+
- Check Firefox version is 109+
122
+
- Ensure manifest.json is valid
123
+
- Check browser console for errors
124
+
125
+
2. **Scan not starting:**
126
+
- Verify you're on Twitter/X Following page
127
+
- Check content script is injected (look for console logs)
128
+
- Ensure page is fully loaded
129
+
130
+
3. **"Server offline" message:**
131
+
- Make sure dev server is running (`netlify dev`)
132
+
- Check API URL in extension settings
133
+
134
+
## Packaging for Distribution
135
+
136
+
Create production builds for both browsers:
137
+
138
+
```bash
139
+
pnpm run package:prod
140
+
```
141
+
142
+
This creates:
143
+
- `dist/chrome.zip` - Ready for Chrome Web Store
144
+
- `dist/firefox.zip` - Ready for Firefox Add-ons
145
+
146
+
## Development Workflow
147
+
148
+
```bash
149
+
# Watch mode (auto-rebuild on changes)
150
+
pnpm run dev
151
+
152
+
# In Firefox:
153
+
# 1. about:debugging โ Reload extension after each rebuild
154
+
# 2. Or use web-ext for auto-reload:
155
+
156
+
npx web-ext run --source-dir=dist/firefox
157
+
```
158
+
159
+
## Differences You Might Notice
160
+
161
+
1. **Background page persistence:**
162
+
- Chrome: Service worker (non-persistent)
163
+
- Firefox: Scripts array (similar behavior in MV3)
164
+
165
+
2. **API behavior:**
166
+
- Firefox: Native Promise support
167
+
- Chrome: Promises via polyfill
168
+
169
+
3. **Extension ID:**
170
+
- Chrome: Auto-generated
171
+
- Firefox: Explicitly set as `atlast-importer@byarielm.fyi`
172
+
173
+
Both versions use the same source code and should behave identically!
+61
-42
packages/extension/build.js
+61
-42
packages/extension/build.js
···
21
21
console.log(`๐ API URL: ${ATLAST_API_URL}`);
22
22
23
23
// Clean dist directory
24
-
const distDir = path.join(__dirname, 'dist', 'chrome');
25
-
if (fs.existsSync(distDir)) {
26
-
fs.rmSync(distDir, { recursive: true });
24
+
const distBaseDir = path.join(__dirname, 'dist');
25
+
if (fs.existsSync(distBaseDir)) {
26
+
fs.rmSync(distBaseDir, { recursive: true });
27
27
}
28
-
fs.mkdirSync(distDir, { recursive: true });
28
+
fs.mkdirSync(distBaseDir, { recursive: true });
29
29
30
30
// Build configuration base
31
31
const buildConfigBase = {
···
38
38
'__ATLAST_API_URL__': JSON.stringify(ATLAST_API_URL),
39
39
'__BUILD_MODE__': JSON.stringify(mode),
40
40
},
41
+
// Include webextension-polyfill in the bundle
42
+
external: [],
41
43
};
42
44
43
-
// Build scripts
44
-
const scripts = [
45
-
{
46
-
...buildConfigBase,
47
-
entryPoints: ['src/content/index.ts'],
48
-
outfile: path.join(distDir, 'content', 'index.js'),
49
-
},
50
-
{
51
-
...buildConfigBase,
52
-
entryPoints: ['src/background/service-worker.ts'],
53
-
outfile: path.join(distDir, 'background', 'service-worker.js'),
54
-
},
55
-
{
56
-
...buildConfigBase,
57
-
entryPoints: ['src/popup/popup.ts'],
58
-
outfile: path.join(distDir, 'popup', 'popup.js'),
59
-
},
60
-
];
45
+
// Build scripts for a specific browser
46
+
function getScripts(browser) {
47
+
const distDir = path.join(distBaseDir, browser);
48
+
return [
49
+
{
50
+
...buildConfigBase,
51
+
entryPoints: ['src/content/index.ts'],
52
+
outfile: path.join(distDir, 'content', 'index.js'),
53
+
},
54
+
{
55
+
...buildConfigBase,
56
+
entryPoints: ['src/background/service-worker.ts'],
57
+
outfile: path.join(distDir, 'background', 'service-worker.js'),
58
+
},
59
+
{
60
+
...buildConfigBase,
61
+
entryPoints: ['src/popup/popup.ts'],
62
+
outfile: path.join(distDir, 'popup', 'popup.js'),
63
+
},
64
+
];
65
+
}
61
66
62
67
// Build function
63
68
async function build() {
64
69
try {
65
-
console.log('๐จ Building extension...');
70
+
console.log('๐จ Building extension for Chrome and Firefox...');
71
+
72
+
const browsers = ['chrome', 'firefox'];
66
73
67
-
// Build all scripts
68
-
for (const config of scripts) {
69
-
if (watch) {
70
-
const ctx = await esbuild.context(config);
71
-
await ctx.watch();
72
-
console.log(`๐ Watching ${path.basename(config.entryPoints[0])}...`);
73
-
} else {
74
-
await esbuild.build(config);
75
-
console.log(`โ
Built ${path.basename(config.entryPoints[0])}`);
74
+
for (const browser of browsers) {
75
+
console.log(`\n๐ฆ Building ${browser} version...`);
76
+
const scripts = getScripts(browser);
77
+
78
+
// Build all scripts
79
+
for (const config of scripts) {
80
+
if (watch) {
81
+
const ctx = await esbuild.context(config);
82
+
await ctx.watch();
83
+
console.log(`๐ Watching ${browser}/${path.basename(config.entryPoints[0])}...`);
84
+
} else {
85
+
await esbuild.build(config);
86
+
console.log(`โ
Built ${browser}/${path.basename(config.entryPoints[0])}`);
87
+
}
76
88
}
77
-
}
78
89
79
-
// Copy static files
80
-
copyStaticFiles();
90
+
// Copy static files
91
+
copyStaticFiles(browser);
81
92
82
-
// Process CSS with Tailwind
83
-
await processCSS();
93
+
// Process CSS with Tailwind
94
+
await processCSS(browser);
95
+
}
84
96
85
97
if (!watch) {
86
-
console.log('โจ Build complete!');
98
+
console.log('\nโจ Build complete for both browsers!');
87
99
}
88
100
} catch (error) {
89
101
console.error('โ Build failed:', error);
···
92
104
}
93
105
94
106
// Process CSS with PostCSS (Tailwind + Autoprefixer)
95
-
async function processCSS() {
107
+
async function processCSS(browser) {
96
108
const cssPath = path.join(__dirname, 'src/popup/popup.css');
109
+
const distDir = path.join(distBaseDir, browser);
97
110
const outputPath = path.join(distDir, 'popup/popup.css');
98
111
99
112
const css = fs.readFileSync(cssPath, 'utf8');
···
121
134
}
122
135
123
136
// Copy static files
124
-
function copyStaticFiles() {
137
+
function copyStaticFiles(browser) {
138
+
const distDir = path.join(distBaseDir, browser);
139
+
125
140
const filesToCopy = [
126
-
{ from: 'manifest.json', to: 'manifest.json' },
141
+
{ from: `manifest.${browser}.json`, to: 'manifest.json', fallback: 'manifest.json' },
127
142
{ from: 'src/popup/popup.html', to: 'popup/popup.html' },
128
143
];
129
144
130
145
for (const file of filesToCopy) {
131
-
const srcPath = path.join(__dirname, file.from);
146
+
// Try to use browser-specific file first, fall back to default
147
+
let srcPath = path.join(__dirname, file.from);
148
+
if (file.fallback && !fs.existsSync(srcPath)) {
149
+
srcPath = path.join(__dirname, file.fallback);
150
+
}
132
151
const destPath = path.join(distDir, file.to);
133
152
134
153
// Create directory if it doesn't exist
+44
packages/extension/manifest.chrome.json
+44
packages/extension/manifest.chrome.json
···
1
+
{
2
+
"manifest_version": 3,
3
+
"name": "ATlast Importer",
4
+
"version": "1.0.0",
5
+
"description": "Import your Twitter/X follows to find them on Bluesky",
6
+
"permissions": [
7
+
"activeTab",
8
+
"storage"
9
+
],
10
+
"host_permissions": [
11
+
"https://twitter.com/*",
12
+
"https://x.com/*",
13
+
"http://127.0.0.1:8888/*",
14
+
"http://localhost:8888/*",
15
+
"https://atlast.byarielm.fyi/*"
16
+
],
17
+
"background": {
18
+
"service_worker": "background/service-worker.js",
19
+
"type": "module"
20
+
},
21
+
"content_scripts": [
22
+
{
23
+
"matches": [
24
+
"https://twitter.com/*",
25
+
"https://x.com/*"
26
+
],
27
+
"js": ["content/index.js"],
28
+
"run_at": "document_idle"
29
+
}
30
+
],
31
+
"action": {
32
+
"default_popup": "popup/popup.html",
33
+
"default_icon": {
34
+
"16": "assets/icon-16.png",
35
+
"48": "assets/icon-48.png",
36
+
"128": "assets/icon-128.png"
37
+
}
38
+
},
39
+
"icons": {
40
+
"16": "assets/icon-16.png",
41
+
"48": "assets/icon-48.png",
42
+
"128": "assets/icon-128.png"
43
+
}
44
+
}
+51
packages/extension/manifest.firefox.json
+51
packages/extension/manifest.firefox.json
···
1
+
{
2
+
"manifest_version": 3,
3
+
"name": "ATlast Importer",
4
+
"version": "1.0.0",
5
+
"description": "Import your Twitter/X follows to find them on Bluesky",
6
+
"permissions": [
7
+
"activeTab",
8
+
"storage",
9
+
"cookies"
10
+
],
11
+
"host_permissions": [
12
+
"https://twitter.com/*",
13
+
"https://x.com/*",
14
+
"http://127.0.0.1:8888/*",
15
+
"http://localhost:8888/*",
16
+
"https://atlast.byarielm.fyi/*"
17
+
],
18
+
"background": {
19
+
"scripts": ["background/service-worker.js"],
20
+
"type": "module"
21
+
},
22
+
"content_scripts": [
23
+
{
24
+
"matches": [
25
+
"https://twitter.com/*",
26
+
"https://x.com/*"
27
+
],
28
+
"js": ["content/index.js"],
29
+
"run_at": "document_idle"
30
+
}
31
+
],
32
+
"action": {
33
+
"default_popup": "popup/popup.html",
34
+
"default_icon": {
35
+
"16": "assets/icon-16.png",
36
+
"48": "assets/icon-48.png",
37
+
"128": "assets/icon-128.png"
38
+
}
39
+
},
40
+
"icons": {
41
+
"16": "assets/icon-16.png",
42
+
"48": "assets/icon-48.png",
43
+
"128": "assets/icon-128.png"
44
+
},
45
+
"browser_specific_settings": {
46
+
"gecko": {
47
+
"id": "atlast-importer@byarielm.fyi",
48
+
"strict_min_version": "109.0"
49
+
}
50
+
}
51
+
}
+6
-2
packages/extension/package.json
+6
-2
packages/extension/package.json
···
9
9
"build:prod": "node build.js --prod",
10
10
"dev": "node build.js --watch",
11
11
"package:chrome": "cd dist/chrome && zip -r ../chrome.zip .",
12
-
"package:prod": "npm run build:prod && npm run package:chrome"
12
+
"package:firefox": "cd dist/firefox && zip -r ../firefox.zip .",
13
+
"package:all": "pnpm run package:chrome && pnpm run package:firefox",
14
+
"package:prod": "pnpm run build:prod && pnpm run package:all"
13
15
},
14
16
"dependencies": {
15
-
"@atlast/shared": "workspace:*"
17
+
"@atlast/shared": "workspace:*",
18
+
"webextension-polyfill": "^0.12.0"
16
19
},
17
20
"devDependencies": {
18
21
"@types/chrome": "^0.0.256",
22
+
"@types/webextension-polyfill": "^0.12.4",
19
23
"autoprefixer": "^10.4.23",
20
24
"cssnano": "^7.1.2",
21
25
"esbuild": "^0.19.11",
+2
-1
packages/extension/src/background/service-worker.ts
+2
-1
packages/extension/src/background/service-worker.ts
···
1
+
import browser from 'webextension-polyfill';
1
2
import {
2
3
MessageType,
3
4
onMessage,
···
150
151
/**
151
152
* Log extension installation
152
153
*/
153
-
chrome.runtime.onInstalled.addListener((details) => {
154
+
browser.runtime.onInstalled.addListener((details) => {
154
155
console.log('[Background] Extension installed:', details.reason);
155
156
156
157
if (details.reason === 'install') {
+36
-9
packages/extension/src/lib/api-client.ts
+36
-9
packages/extension/src/lib/api-client.ts
···
2
2
* ATlast API client for extension
3
3
*/
4
4
5
+
import browser from 'webextension-polyfill';
6
+
5
7
// These are replaced at build time by esbuild
6
8
declare const __ATLAST_API_URL__: string;
7
9
declare const __BUILD_MODE__: string;
···
67
69
* Get extension version from manifest
68
70
*/
69
71
export function getExtensionVersion(): string {
70
-
return chrome.runtime.getManifest().version;
72
+
return browser.runtime.getManifest().version;
71
73
}
72
74
73
75
/**
···
76
78
*/
77
79
export async function checkServerHealth(): Promise<boolean> {
78
80
try {
79
-
// Try to fetch the root URL with a short timeout
81
+
// Try to fetch the health endpoint with a short timeout
80
82
const controller = new AbortController();
81
83
const timeoutId = setTimeout(() => controller.abort(), 3000);
82
84
83
-
const response = await fetch(ATLAST_API_URL, {
84
-
method: 'HEAD',
85
-
signal: controller.signal
85
+
const response = await fetch(`${ATLAST_API_URL}/.netlify/functions/health`, {
86
+
method: 'GET',
87
+
signal: controller.signal,
88
+
credentials: 'include', // Include for CORS
86
89
});
87
90
88
91
clearTimeout(timeoutId);
89
92
90
-
// Any response (even 404) means server is running
91
-
return true;
93
+
// Any successful response means server is running
94
+
return response.ok;
92
95
} catch (error) {
93
96
console.error('[API Client] Server health check failed:', error);
94
97
return false;
···
113
116
avatar?: string;
114
117
} | null> {
115
118
try {
116
-
const response = await fetch(`${ATLAST_API_URL}/.netlify/functions/session`, {
119
+
// Try to get session cookie using browser.cookies API
120
+
// This works around Firefox's cookie partitioning for extensions
121
+
let sessionId: string | null = null;
122
+
123
+
try {
124
+
const cookieName = __BUILD_MODE__ === 'production' ? 'atlast_session' : 'atlast_session_dev';
125
+
const cookie = await browser.cookies.get({
126
+
url: ATLAST_API_URL,
127
+
name: cookieName
128
+
});
129
+
130
+
if (cookie) {
131
+
sessionId = cookie.value;
132
+
console.log('[API Client] Found session cookie:', cookieName);
133
+
}
134
+
} catch (cookieError) {
135
+
console.log('[API Client] Could not read cookie:', cookieError);
136
+
}
137
+
138
+
// Build URL with session parameter if we have one
139
+
const url = sessionId
140
+
? `${ATLAST_API_URL}/.netlify/functions/session?session=${sessionId}`
141
+
: `${ATLAST_API_URL}/.netlify/functions/session`;
142
+
143
+
const response = await fetch(url, {
117
144
method: 'GET',
118
-
credentials: 'include', // Include cookies
145
+
credentials: 'include', // Include cookies as fallback
119
146
headers: {
120
147
'Accept': 'application/json'
121
148
}
+6
-5
packages/extension/src/lib/messaging.ts
+6
-5
packages/extension/src/lib/messaging.ts
···
1
+
import browser from 'webextension-polyfill';
1
2
import type { ScraperProgress, ScraperResult } from '../content/scrapers/base-scraper.js';
2
3
3
4
/**
···
87
88
* Send message to background script
88
89
*/
89
90
export function sendToBackground<T = any>(message: Message): Promise<T> {
90
-
return chrome.runtime.sendMessage(message);
91
+
return browser.runtime.sendMessage(message);
91
92
}
92
93
93
94
/**
94
95
* Send message to active tab's content script
95
96
*/
96
97
export async function sendToContent(message: Message): Promise<any> {
97
-
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
98
+
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
98
99
if (!tab.id) {
99
100
throw new Error('No active tab found');
100
101
}
101
-
return chrome.tabs.sendMessage(tab.id, message);
102
+
return browser.tabs.sendMessage(tab.id, message);
102
103
}
103
104
104
105
/**
105
106
* Listen for messages
106
107
*/
107
108
export function onMessage(
108
-
handler: (message: Message, sender: chrome.runtime.MessageSender) => any | Promise<any>
109
+
handler: (message: Message, sender: browser.Runtime.MessageSender) => any | Promise<any>
109
110
): void {
110
-
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
111
+
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
111
112
const result = handler(message, sender);
112
113
113
114
// Handle async handlers
+4
-3
packages/extension/src/lib/storage.ts
+4
-3
packages/extension/src/lib/storage.ts
···
1
+
import browser from 'webextension-polyfill';
1
2
import type { ExtensionState } from './messaging.js';
2
3
3
4
/**
···
11
12
* Get extension state from storage
12
13
*/
13
14
export async function getState(): Promise<ExtensionState> {
14
-
const result = await chrome.storage.local.get(STORAGE_KEYS.STATE);
15
+
const result = await browser.storage.local.get(STORAGE_KEYS.STATE);
15
16
return result[STORAGE_KEYS.STATE] || { status: 'idle' };
16
17
}
17
18
···
19
20
* Save extension state to storage
20
21
*/
21
22
export async function setState(state: ExtensionState): Promise<void> {
22
-
await chrome.storage.local.set({ [STORAGE_KEYS.STATE]: state });
23
+
await browser.storage.local.set({ [STORAGE_KEYS.STATE]: state });
23
24
}
24
25
25
26
/**
26
27
* Clear extension state
27
28
*/
28
29
export async function clearState(): Promise<void> {
29
-
await chrome.storage.local.remove(STORAGE_KEYS.STATE);
30
+
await browser.storage.local.remove(STORAGE_KEYS.STATE);
30
31
}
+2
-2
packages/extension/src/popup/popup.html
+2
-2
packages/extension/src/popup/popup.html
···
80
80
<!-- Server offline state -->
81
81
<div id="state-offline" class="w-full text-center hidden">
82
82
<div class="text-5xl mb-4">๐</div>
83
-
<p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">ATlast server not running</p>
84
-
<p class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-red-50 dark:bg-red-950/50 rounded border-l-[3px] border-red-600">
83
+
<p class="text-base font-semibold mb-3 text-slate-700 dark:text-cyan-50">Server not available</p>
84
+
<p id="dev-instructions" class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-red-50 dark:bg-red-950/50 rounded border-l-[3px] border-red-600">
85
85
Start the dev server:<br />
86
86
<code class="bg-black/10 dark:bg-white/10 px-2 py-1 rounded font-mono text-[11px] inline-block my-2">npx netlify-cli dev --filter @atlast/web</code>
87
87
</p>
+22
-4
packages/extension/src/popup/popup.ts
+22
-4
packages/extension/src/popup/popup.ts
···
1
+
import browser from 'webextension-polyfill';
1
2
import {
2
3
MessageType,
3
4
sendToBackground,
4
5
sendToContent,
5
6
type ExtensionState
6
7
} from '../lib/messaging.js';
8
+
9
+
// Build mode injected at build time
10
+
declare const __BUILD_MODE__: string;
7
11
8
12
/**
9
13
* DOM elements
···
26
30
statusMessage: document.getElementById('status-message')!,
27
31
errorMessage: document.getElementById('error-message')!,
28
32
serverUrl: document.getElementById('server-url')!,
33
+
devInstructions: document.getElementById('dev-instructions')!,
29
34
progressFill: document.getElementById('progress-fill')! as HTMLElement,
30
35
btnStart: document.getElementById('btn-start')! as HTMLButtonElement,
31
36
btnUpload: document.getElementById('btn-upload')! as HTMLButtonElement,
···
163
168
// Open ATlast at results page with upload data
164
169
const { getApiUrl } = await import('../lib/api-client.js');
165
170
const resultsUrl = `${getApiUrl()}${response.redirectUrl}`;
166
-
chrome.tabs.create({ url: resultsUrl });
171
+
browser.tabs.create({ url: resultsUrl });
167
172
168
173
} catch (error) {
169
174
console.error('[Popup] Error uploading:', error);
···
214
219
if (!isOnline) {
215
220
console.log('[Popup] โ Server is offline');
216
221
showState('offline');
217
-
elements.serverUrl.textContent = `Trying to reach: ${getApiUrl()}`;
222
+
223
+
// Show appropriate message based on build mode
224
+
const apiUrl = getApiUrl();
225
+
const isDev = __BUILD_MODE__ === 'development';
226
+
227
+
// Hide dev instructions in production
228
+
if (!isDev) {
229
+
elements.devInstructions.classList.add('hidden');
230
+
}
231
+
232
+
elements.serverUrl.textContent = isDev
233
+
? `Development server at ${apiUrl}`
234
+
: `Cannot reach ${apiUrl}`;
235
+
218
236
return false;
219
237
}
220
238
···
264
282
265
283
// Set up login buttons
266
284
elements.btnOpenAtlast.addEventListener('click', () => {
267
-
chrome.tabs.create({ url: getApiUrl() });
285
+
browser.tabs.create({ url: getApiUrl() });
268
286
});
269
287
270
288
elements.btnRetryLogin.addEventListener('click', async () => {
···
305
323
});
306
324
307
325
// Listen for storage changes (when background updates state)
308
-
chrome.storage.onChanged.addListener((changes, areaName) => {
326
+
browser.storage.onChanged.addListener((changes, areaName) => {
309
327
if (areaName === 'local' && changes.extensionState) {
310
328
const newState = changes.extensionState.newValue;
311
329
console.log('[Popup] ๐ Storage changed, new state:', newState);
+2
-2
packages/extension/tailwind.config.js
+2
-2
packages/extension/tailwind.config.js
···
1
1
/** @type {import('tailwindcss').Config} */
2
2
export default {
3
-
// Use class-based dark mode to match web app
4
-
darkMode: "class",
3
+
// Use media query dark mode to automatically respect system preference
4
+
darkMode: "media",
5
5
6
6
// Scan popup HTML and TypeScript files
7
7
content: [
+4
-2
packages/functions/src/core/middleware/error.middleware.ts
+4
-2
packages/functions/src/core/middleware/error.middleware.ts
···
21
21
}
22
22
23
23
if (error instanceof ApiError) {
24
-
return errorResponse(error.message, error.statusCode, error.details);
24
+
return errorResponse(error.message, error.statusCode, error.details, event);
25
25
}
26
26
27
27
// Unknown errors
···
29
29
"Internal server error",
30
30
500,
31
31
error instanceof Error ? error.message : "Unknown error",
32
+
event,
32
33
);
33
34
}
34
35
};
···
48
49
console.error("Authenticated handler error:", error);
49
50
50
51
if (error instanceof ApiError) {
51
-
return errorResponse(error.message, error.statusCode, error.details);
52
+
return errorResponse(error.message, error.statusCode, error.details, event);
52
53
}
53
54
54
55
return errorResponse(
55
56
"Internal server error",
56
57
500,
57
58
error instanceof Error ? error.message : "Unknown error",
59
+
event,
58
60
);
59
61
}
60
62
};
+21
packages/functions/src/health.ts
+21
packages/functions/src/health.ts
···
1
+
import { SimpleHandler } from "./core/types/api.types";
2
+
import { successResponse } from "./utils";
3
+
import { withErrorHandling } from "./core/middleware";
4
+
5
+
/**
6
+
* Health check endpoint
7
+
* Returns 200 OK with server status
8
+
*/
9
+
const healthHandler: SimpleHandler = async (event) => {
10
+
return successResponse(
11
+
{
12
+
status: "ok",
13
+
timestamp: new Date().toISOString(),
14
+
},
15
+
200,
16
+
{},
17
+
event
18
+
);
19
+
};
20
+
21
+
export const handler = withErrorHandling(healthHandler);
+2
-2
packages/functions/src/session.ts
+2
-2
packages/functions/src/session.ts
···
30
30
return successResponse(cached, 200, {
31
31
"Cache-Control": "private, max-age=300",
32
32
"X-Cache-Status": "HIT",
33
-
});
33
+
}, event);
34
34
}
35
35
36
36
const { agent } = await SessionService.getAgentForSession(sessionId, event);
···
50
50
return successResponse(profileData, 200, {
51
51
"Cache-Control": "private, max-age=300",
52
52
"X-Cache-Status": "MISS",
53
-
});
53
+
}, event);
54
54
};
55
55
56
56
export const handler = withErrorHandling(sessionHandler);
+42
-3
packages/functions/src/utils/response.utils.ts
+42
-3
packages/functions/src/utils/response.utils.ts
···
1
-
import { HandlerResponse } from "@netlify/functions";
1
+
import { HandlerResponse, HandlerEvent } from "@netlify/functions";
2
2
import { ApiResponse } from "../core/types";
3
3
4
+
/**
5
+
* Get CORS headers based on request origin
6
+
* Supports credentialed requests from extensions and localhost
7
+
*/
8
+
function getCorsHeaders(event?: HandlerEvent): Record<string, string> {
9
+
const origin = event?.headers?.origin || event?.headers?.Origin;
10
+
11
+
// Allow all origins for non-credentialed requests (backward compatibility)
12
+
if (!origin) {
13
+
return {
14
+
"Access-Control-Allow-Origin": "*",
15
+
};
16
+
}
17
+
18
+
// Check if origin is allowed for credentialed requests
19
+
const allowedOrigins = [
20
+
'http://localhost:8888',
21
+
'http://127.0.0.1:8888',
22
+
'https://atlast.byarielm.fyi',
23
+
];
24
+
25
+
const isExtension = origin.startsWith('chrome-extension://') || origin.startsWith('moz-extension://');
26
+
const isAllowedOrigin = allowedOrigins.includes(origin);
27
+
28
+
if (isExtension || isAllowedOrigin) {
29
+
return {
30
+
"Access-Control-Allow-Origin": origin,
31
+
"Access-Control-Allow-Credentials": "true",
32
+
};
33
+
}
34
+
35
+
// Default to wildcard for unknown origins
36
+
return {
37
+
"Access-Control-Allow-Origin": "*",
38
+
};
39
+
}
40
+
4
41
export function successResponse<T>(
5
42
data: T,
6
43
statusCode: number = 200,
7
44
additionalHeaders: Record<string, string> = {},
45
+
event?: HandlerEvent,
8
46
): HandlerResponse {
9
47
const response: ApiResponse<T> = {
10
48
success: true,
···
15
53
statusCode,
16
54
headers: {
17
55
"Content-Type": "application/json",
18
-
"Access-Control-Allow-Origin": "*",
56
+
...getCorsHeaders(event),
19
57
...additionalHeaders,
20
58
},
21
59
body: JSON.stringify(response),
···
26
64
error: string,
27
65
statusCode: number = 500,
28
66
details?: string,
67
+
event?: HandlerEvent,
29
68
): HandlerResponse {
30
69
const response: ApiResponse = {
31
70
success: false,
···
37
76
statusCode,
38
77
headers: {
39
78
"Content-Type": "application/json",
40
-
"Access-Control-Allow-Origin": "*",
79
+
...getCorsHeaders(event),
41
80
},
42
81
body: JSON.stringify(response),
43
82
};
+16
pnpm-lock.yaml
+16
pnpm-lock.yaml
···
114
114
'@atlast/shared':
115
115
specifier: workspace:*
116
116
version: link:../shared
117
+
webextension-polyfill:
118
+
specifier: ^0.12.0
119
+
version: 0.12.0
117
120
devDependencies:
118
121
'@types/chrome':
119
122
specifier: ^0.0.256
120
123
version: 0.0.256
124
+
'@types/webextension-polyfill':
125
+
specifier: ^0.12.4
126
+
version: 0.12.4
121
127
autoprefixer:
122
128
specifier: ^10.4.23
123
129
version: 10.4.23(postcss@8.5.6)
···
1245
1251
'@types/triple-beam@1.3.5':
1246
1252
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
1247
1253
1254
+
'@types/webextension-polyfill@0.12.4':
1255
+
resolution: {integrity: sha512-wK8YdSI0pDiaehSLDIvtvonYmLwUUivg4Z6JCJO8rkyssMAG82cFJgwPK/V7NO61mJBLg/tXeoXQL8AFzpXZmQ==}
1256
+
1248
1257
'@types/yauzl@2.10.3':
1249
1258
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
1250
1259
···
3144
3153
optional: true
3145
3154
terser:
3146
3155
optional: true
3156
+
3157
+
webextension-polyfill@0.12.0:
3158
+
resolution: {integrity: sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==}
3147
3159
3148
3160
webidl-conversions@3.0.1:
3149
3161
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
···
4172
4184
4173
4185
'@types/triple-beam@1.3.5': {}
4174
4186
4187
+
'@types/webextension-polyfill@0.12.4': {}
4188
+
4175
4189
'@types/yauzl@2.10.3':
4176
4190
dependencies:
4177
4191
'@types/node': 24.10.4
···
6108
6122
optionalDependencies:
6109
6123
'@types/node': 24.10.4
6110
6124
fsevents: 2.3.3
6125
+
6126
+
webextension-polyfill@0.12.0: {}
6111
6127
6112
6128
webidl-conversions@3.0.1: {}
6113
6129