const cors = require('cors') const express = require('express') const app = express() const port = Number(process.argv[2]) || 3001 app.use(cors()) app.use(express.json()) let authenticated = false let draft = null let linkedAccount = null let mockNotes = [] const deviceData = {} const accountDevices = [ { created_at: '2026-08-04 12:00:00', current: true, device_name: 'iFruit Phone', imei: '356938035643809', updated_at: '2026-08-04 12:00:00', }, ] const messages = [ { body: 'Welcome to iFruit Mail. Your shared mailbox is ready to use.', created_at: '2026-08-04 11:30:00', folder: 'inbox', id: 1, is_read: false, message_id: 'b73f3872-54cc-4d74-a058-6caa24f0dfff', preview: 'Welcome to iFruit Mail. Your shared mailbox is ready to use.', recipients: ['demo@ifruit.com'], sender: 'support@ifruit.com', subject: 'Welcome to iFruit Mail', trashed_at: null, }, { body: 'The meet is behind the casino at nine. Bring the blue car.', created_at: '2026-08-04 10:15:00', folder: 'inbox', id: 2, is_read: true, message_id: '79042d13-f86d-4418-a490-8b558539c20b', preview: 'The meet is behind the casino at nine. Bring the blue car.', recipients: ['demo@ifruit.com', 'jamie@ifruit.com'], sender: 'morgan@ifruit.com', subject: 'Tonight', trashed_at: null, }, { body: 'I will be there.', created_at: '2026-08-03 18:05:00', folder: 'sent', id: 3, is_read: true, message_id: 'c4414c1e-5fe3-46ea-bc0b-b5b9785c91bd', preview: 'I will be there.', recipients: ['morgan@ifruit.com'], sender: 'demo@ifruit.com', subject: 'Re: Tonight', trashed_at: null, }, { body: 'This is an old message.', created_at: '2026-08-02 09:20:00', folder: 'inbox', id: 4, is_read: true, message_id: 'd99669ab-c1ac-42d4-8ed4-3c4fe7fa37fb', preview: 'This is an old message.', recipients: ['demo@ifruit.com'], sender: 'news@ifruit.com', subject: 'Old mail', trashed_at: '2026-08-04 08:00:00', }, ] function counts() { return { drafts: draft ? 1 : 0, inbox: messages.filter( (item) => item.folder === 'inbox' && !item.trashed_at, ).length, sent: messages.filter((item) => item.folder === 'sent' && !item.trashed_at) .length, trash: messages.filter((item) => item.trashed_at).length, unread: messages.filter( (item) => item.folder === 'inbox' && !item.trashed_at && !item.is_read, ).length, } } app.post('/api/:endpoint', (request, response) => { console.log(`[NUI] ${request.params.endpoint}`, request.body) const endpoint = request.params.endpoint if (endpoint === 'weather:get') { response.json({ success: true, data: { clock: { year: 2026, month: 8, day: 5, hour: 17, minute: 20 }, condition: 'partly_cloudy', rainLevel: 0.08, region: 'los_santos', windSpeed: 3.2, }, }) return } if (endpoint === 'map:getPlayerCoords') { response.json({ success: true, data: { coords: { x: -75.2, y: -818.9, z: 326.2 } }, }) return } if (endpoint === 'account:login' || endpoint === 'account:register') { authenticated = true linkedAccount = { devices: accountDevices, email: request.body.email.includes('@') ? request.body.email : `${request.body.email}@ifruit.com`, } response.json({ success: true, data: linkedAccount }) return } if (endpoint === 'account:logout') { authenticated = false linkedAccount = null response.json({ success: true }) return } if (endpoint === 'account:devices') { response.json({ success: true, data: accountDevices }) return } if (endpoint === 'account:remove-device') { response.json({ success: true, data: accountDevices }) return } if (endpoint === 'device:save') { const current = deviceData[request.body.namespace] const revision = (current?.revision ?? 0) + 1 deviceData[request.body.namespace] = { payload: request.body.payload, revision, } response.json({ success: true, data: { revision } }) return } if (endpoint === 'device:factory-reset') { authenticated = false linkedAccount = null mockNotes = [] for (const key of Object.keys(deviceData)) delete deviceData[key] response.json({ success: true }) return } if ( endpoint === 'sim:insert' && request.body.imei === '356938035643810' && !request.body.confirmed ) { response.json({ success: false, error: 'confirmation_required' }) return } if (endpoint === 'notes:list') { response.json({ success: true, data: mockNotes }) return } if (endpoint === 'notes:create') { mockNotes.unshift({ ...request.body, revision: 1 }) response.json({ success: true, data: mockNotes }) return } if (endpoint === 'notes:update') { const index = mockNotes.findIndex((note) => note.id === request.body.id) if (index >= 0) { mockNotes[index] = { ...request.body, revision: mockNotes[index].revision + 1, updatedAt: Date.now(), } } response.json({ success: true, data: mockNotes }) return } if (endpoint === 'notes:delete') { mockNotes = mockNotes.filter((note) => note.id !== request.body.id) response.json({ success: true, data: mockNotes }) return } if (endpoint === 'mail:login' || endpoint === 'mail:register') { authenticated = true linkedAccount = { devices: accountDevices, email: 'demo@ifruit.com', } response.json({ success: true, data: linkedAccount }) return } if (endpoint === 'mail:logout') { authenticated = false response.json({ success: true }) return } if (endpoint.startsWith('mail:') && !authenticated) { response.json({ success: false, error: 'not_authenticated' }) return } if (endpoint === 'mail:counts') { response.json({ success: true, data: counts() }) return } if (endpoint === 'mail:list') { const { folder, search = '' } = request.body let items = folder === 'drafts' ? draft ? [{ ...draft, created_at: draft.updated_at, preview: draft.body }] : [] : messages.filter((item) => folder === 'trash' ? item.trashed_at : item.folder === folder && !item.trashed_at, ) const query = String(search).toLowerCase() if (query) { items = items.filter((item) => `${item.sender ?? ''} ${item.subject} ${item.preview}` .toLowerCase() .includes(query), ) } response.json({ success: true, data: { hasMore: false, items, offset: 0 } }) return } if (endpoint === 'mail:get') { const message = messages.find((item) => item.id === Number(request.body.id)) if (message) message.is_read = true response.json( message ? { success: true, data: message } : { success: false, error: 'message_not_found' }, ) return } if (endpoint === 'mail:get-draft') { response.json( draft ? { success: true, data: draft } : { success: false, error: 'draft_not_found' }, ) return } if (endpoint === 'mail:save-draft') { draft = { ...request.body, id: request.body.id ?? '748836d2-2308-4c21-a4d8-3af6df6dd7eb', created_at: '2026-08-04 11:40:00', updated_at: '2026-08-04 11:40:00', } response.json({ success: true, data: { id: draft.id } }) return } if (endpoint === 'mail:delete-draft') { draft = null response.json({ success: true }) return } if (endpoint === 'mail:send') { draft = null response.json({ success: true, data: { id: 'mock-sent-message' } }) return } if (endpoint === 'mail:trash' || endpoint === 'mail:restore') { const message = messages.find((item) => item.id === Number(request.body.id)) if (message) message.trashed_at = endpoint === 'mail:trash' ? '2026-08-04 12:00:00' : null response.json({ success: true }) return } if (endpoint === 'mail:delete-forever') { const index = messages.findIndex( (item) => item.id === Number(request.body.id), ) if (index >= 0) messages.splice(index, 1) response.json({ success: true }) return } if (endpoint === 'mail:empty-trash') { for (let index = messages.length - 1; index >= 0; index -= 1) { if (messages[index].trashed_at) messages.splice(index, 1) } response.json({ success: true }) return } if (endpoint === 'mail:set-read') { const message = messages.find((item) => item.id === Number(request.body.id)) if (message) message.is_read = request.body.read response.json({ success: true }) return } response.json({ success: true }) }) app.listen(port, () => { console.log(`Mock NUI server listening on http://localhost:${port}`) })