ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

fix extension import data sharing with shared store

Critical bug fix: extension-import and get-extension-import were using
separate in-memory Maps, causing 404 errors when fetching import data.

- Create shared utils/import-store.ts module
- Both functions now use same Map instance
- Add logging for debugging
- Note: In-memory storage only works for dev (single process)
Production needs database/Redis/Netlify Blobs

byarielm.fyi 53a16e47 c24f6c6a

verified
Changed files
+74 -44
packages
+1 -36
packages/functions/src/extension-import.ts
··· 1 1 import type { Handler, HandlerEvent } from '@netlify/functions'; 2 2 import type { ExtensionImportRequest, ExtensionImportResponse } from '@atlast/shared'; 3 3 import { z } from 'zod'; 4 - import crypto from 'crypto'; 4 + import { storeImport } from './utils/import-store.js'; 5 5 6 6 /** 7 7 * Validation schema for extension import request ··· 16 16 sourceUrl: z.string().url() 17 17 }) 18 18 }); 19 - 20 - /** 21 - * Simple in-memory store for extension imports 22 - * TODO: Move to database for production 23 - */ 24 - const importStore = new Map<string, ExtensionImportRequest>(); 25 - 26 - /** 27 - * Generate a random import ID 28 - */ 29 - function generateImportId(): string { 30 - return crypto.randomBytes(16).toString('hex'); 31 - } 32 - 33 - /** 34 - * Store import data and return import ID 35 - */ 36 - function storeImport(data: ExtensionImportRequest): string { 37 - const importId = generateImportId(); 38 - importStore.set(importId, data); 39 - 40 - // Auto-expire after 1 hour 41 - setTimeout(() => { 42 - importStore.delete(importId); 43 - }, 60 * 60 * 1000); 44 - 45 - return importId; 46 - } 47 19 48 20 /** 49 21 * Extension import endpoint ··· 137 109 } 138 110 }; 139 111 140 - /** 141 - * Get import endpoint (helper for import page) 142 - * GET /extension-import/:id 143 - */ 144 - export function getImport(importId: string): ExtensionImportRequest | null { 145 - return importStore.get(importId) || null; 146 - }
+3 -8
packages/functions/src/get-extension-import.ts
··· 1 1 import type { Handler, HandlerEvent } from '@netlify/functions'; 2 2 import type { ExtensionImportRequest } from '@atlast/shared'; 3 - 4 - /** 5 - * Import store (shared with extension-import.ts) 6 - * In production, this would be a database query 7 - */ 8 - const importStore = new Map<string, ExtensionImportRequest>(); 3 + import { getImport } from './utils/import-store.js'; 9 4 10 5 /** 11 6 * Get extension import by ID ··· 49 44 }; 50 45 } 51 46 52 - // Get import data 53 - const importData = importStore.get(importId); 47 + // Get import data from shared store 48 + const importData = getImport(importId); 54 49 55 50 if (!importData) { 56 51 return {
+70
packages/functions/src/utils/import-store.ts
··· 1 + import type { ExtensionImportRequest } from '@atlast/shared'; 2 + import crypto from 'crypto'; 3 + 4 + /** 5 + * Shared in-memory store for extension imports 6 + * This is shared between extension-import.ts and get-extension-import.ts 7 + * 8 + * NOTE: In-memory storage works for development but will NOT work reliably 9 + * in production serverless environments where functions are stateless and 10 + * can run on different instances. 11 + * 12 + * For production, replace this with: 13 + * - Database (PostgreSQL/Neon) 14 + * - Redis/Upstash 15 + * - Netlify Blobs 16 + */ 17 + const importStore = new Map<string, ExtensionImportRequest>(); 18 + 19 + /** 20 + * Generate a random import ID 21 + */ 22 + export function generateImportId(): string { 23 + return crypto.randomBytes(16).toString('hex'); 24 + } 25 + 26 + /** 27 + * Store import data and return import ID 28 + */ 29 + export function storeImport(data: ExtensionImportRequest): string { 30 + const importId = generateImportId(); 31 + importStore.set(importId, data); 32 + 33 + console.log(`[ImportStore] Stored import ${importId} with ${data.usernames.length} usernames`); 34 + 35 + // Auto-expire after 1 hour 36 + setTimeout(() => { 37 + importStore.delete(importId); 38 + console.log(`[ImportStore] Expired import ${importId}`); 39 + }, 60 * 60 * 1000); 40 + 41 + return importId; 42 + } 43 + 44 + /** 45 + * Get import data by ID 46 + */ 47 + export function getImport(importId: string): ExtensionImportRequest | null { 48 + const data = importStore.get(importId) || null; 49 + console.log(`[ImportStore] Get import ${importId}: ${data ? 'found' : 'not found'}`); 50 + return data; 51 + } 52 + 53 + /** 54 + * Delete import data by ID 55 + */ 56 + export function deleteImport(importId: string): boolean { 57 + const result = importStore.delete(importId); 58 + console.log(`[ImportStore] Delete import ${importId}: ${result ? 'success' : 'not found'}`); 59 + return result; 60 + } 61 + 62 + /** 63 + * Get store stats (for debugging) 64 + */ 65 + export function getStoreStats(): { count: number; ids: string[] } { 66 + return { 67 + count: importStore.size, 68 + ids: Array.from(importStore.keys()) 69 + }; 70 + }