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()) function calendarTime(dayOffset, hour, minute = 0) { const value = new Date() value.setDate(value.getDate() + dayOffset) value.setHours(hour, minute, 0, 0) return value.getTime() } function isoTime(offsetMilliseconds) { return new Date(Date.now() + offsetMilliseconds).toISOString() } function unixTime(offsetSeconds = 0) { return Math.floor(Date.now() / 1000) + offsetSeconds } let authenticated = true let draft = null const radioData = { badge: '231', badgeEnabled: true, badgeMaxLength: 8, connected: false, displayName: 'Unit 21', displayNameAllowed: true, displayNameEnabled: true, displayNameMaxLength: 32, frequency: 0, frequencyMax: 999.9, frequencyMin: 0.1, frequencyStep: 0.1, history: [ { primary: 120.5, secondary: 130.7 }, { primary: 42.1, secondary: 0 }, ], members: [], provider: 'yaca', secondaryFrequency: 0, secondarySupported: true, settings: { autoRejoin: false, notifications: true }, volume: 50, } let musicSequence = 3 const musicServerTracks = [ { id: 'city-after-dark', source: 'server', title: 'City After Dark', artist: 'Sky Records', url: 'https://media.w3.org/2010/07/bunny/04-Death_Becomes_Fur.oga', artwork: 'https://picsum.photos/seed/sky-music-city/600/600', }, { id: 'pacific-drive', source: 'server', title: 'Pacific Drive', artist: 'Vespucci FM', url: 'https://media.w3.org/2010/07/bunny/04-Death_Becomes_Fur.oga', artwork: 'https://picsum.photos/seed/sky-music-pacific/600/600', }, { id: 'neon-rain', source: 'server', title: 'Neon Rain', artist: 'Mirror Park', url: 'https://media.w3.org/2010/07/bunny/04-Death_Becomes_Fur.oga', artwork: 'https://picsum.photos/seed/sky-music-neon/600/600', }, ] let musicYoutubeTracks = [ { id: 'music-youtube-1', source: 'youtube', videoId: 'dQw4w9WgXcQ', title: 'Never Gonna Give You Up', artist: 'Rick Astley', artwork: 'https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg', createdAt: Date.now() - 120000, }, ] let musicPlaylists = [ { id: 'music-playlist-1', name: 'Night Ride', createdAt: Date.now() - 86400000, entries: [ { source: 'server', songId: 'city-after-dark' }, { source: 'server', songId: 'neon-rain' }, { source: 'youtube', songId: 'music-youtube-1' }, ], }, ] function musicBootstrap() { return { serverTracks: musicServerTracks, youtubeTracks: musicYoutubeTracks, playlists: musicPlaylists, } } async function fetchYoutubeMetadata(videoId) { const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), 4500) try { const watchUrl = `https://www.youtube.com/watch?v=${videoId}` const response = await fetch( `https://www.youtube.com/oembed?format=json&url=${encodeURIComponent(watchUrl)}`, { headers: { Accept: 'application/json' }, signal: controller.signal, }, ) if (!response.ok) return null const data = await response.json() const title = typeof data.title === 'string' ? data.title.trim() : '' const artist = typeof data.author_name === 'string' ? data.author_name.trim() : '' return title && artist ? { artist, title } : null } catch { return null } finally { clearTimeout(timeout) } } let mockBankBalance = 24787 let mockCashBalance = 2350 let nextBankTransactionId = 7 let mockMapMarkers = [ { color: 'blue', coords: { x: -75.2, y: -818.9, z: 0 }, id: 'mock-map-marker-1', label: 'Meeting point', }, ] let crewLinkProfile = { activeGroupId: 'crewlink-group-night-shift', id: 'crewlink-profile-skyline', mapVisible: true, overheadVisible: false, username: 'Skyline', } let crewLinkGroups = [ { allowMemberPings: true, colour: 'cyan', id: 'crewlink-group-night-shift', inviteCode: 'N1GHT247', isOwner: true, memberCount: 6, name: 'Night Shift', overheadAllowed: true, role: 'owner', }, { allowMemberPings: false, colour: 'violet', id: 'crewlink-group-coastline', isOwner: false, memberCount: 4, name: 'Coastline Crew', overheadAllowed: true, role: 'member', }, ] const crewLinkMembers = { 'crewlink-group-night-shift': [ { coords: { x: -155.2, y: -1005.8, z: 28.4 }, id: 'crewlink-profile-skyline', joinedAt: Date.now() - 36 * 86400000, mapVisible: true, online: true, overheadVisible: false, role: 'owner', source: 1, username: 'Skyline', }, { coords: { x: -67.6, y: -818.1, z: 326.2 }, id: 'crewlink-profile-nova', joinedAt: Date.now() - 30 * 86400000, mapVisible: true, online: true, overheadVisible: true, role: 'coordinator', source: 22, username: 'Nova', }, { coords: { x: 214.8, y: -810.4, z: 30.7 }, id: 'crewlink-profile-ghost', joinedAt: Date.now() - 20 * 86400000, mapVisible: true, online: true, overheadVisible: true, role: 'moderator', source: 38, username: 'Ghost', }, { id: 'crewlink-profile-luna', joinedAt: Date.now() - 12 * 86400000, mapVisible: false, online: true, overheadVisible: false, role: 'member', source: 41, username: 'Luna', }, { id: 'crewlink-profile-mason', joinedAt: Date.now() - 8 * 86400000, mapVisible: true, online: false, overheadVisible: false, role: 'member', username: 'Mason', }, { id: 'crewlink-profile-raven', joinedAt: Date.now() - 2 * 86400000, mapVisible: true, online: false, overheadVisible: false, role: 'guest', username: 'Raven', }, ], 'crewlink-group-coastline': [ { coords: { x: -1204.3, y: -1488.9, z: 4.4 }, id: 'crewlink-profile-skyline', joinedAt: Date.now() - 5 * 86400000, mapVisible: true, online: true, overheadVisible: false, role: 'member', source: 1, username: 'Skyline', }, { coords: { x: -1315.5, y: -1520.2, z: 4.4 }, id: 'crewlink-profile-wave', joinedAt: Date.now() - 40 * 86400000, mapVisible: true, online: true, overheadVisible: true, role: 'owner', source: 18, username: 'Wave', }, { id: 'crewlink-profile-sunset', joinedAt: Date.now() - 20 * 86400000, mapVisible: true, online: false, overheadVisible: false, role: 'moderator', username: 'Sunset', }, { id: 'crewlink-profile-finn', joinedAt: Date.now() - 10 * 86400000, mapVisible: false, online: true, overheadVisible: false, role: 'guest', source: 56, username: 'Finn', }, ], } const crewLinkPings = { 'crewlink-group-night-shift': [ { coords: { x: 233.1, y: -876.4, z: 30.5 }, createdAt: Date.now() - 45000, creatorProfileId: 'crewlink-profile-ghost', creatorUsername: 'Ghost', expiresAt: Date.now() + 255000, id: 'crewlink-ping-meeting', label: 'Meet behind the garage', type: 'meeting', }, { coords: { x: 452.6, y: -980.2, z: 30.7 }, createdAt: Date.now() - 110000, creatorProfileId: 'crewlink-profile-nova', creatorUsername: 'Nova', expiresAt: Date.now() + 190000, id: 'crewlink-ping-danger', label: 'Avoid Mission Row', type: 'danger', }, { coords: { x: -1034.2, y: -2732.1, z: 20.1 }, createdAt: Date.now() - 25000, creatorProfileId: null, creatorUsername: 'sky_mission', expiresAt: Date.now() + 275000, id: 'crewlink-ping-target', label: 'Airport pickup', sourceResource: 'sky_mission', type: 'target', }, ], 'crewlink-group-coastline': [ { coords: { x: -1302.2, y: -1541.4, z: 4.2 }, createdAt: Date.now() - 80000, creatorProfileId: 'crewlink-profile-wave', creatorUsername: 'Wave', expiresAt: Date.now() + 220000, id: 'crewlink-ping-coast', label: 'Boardwalk meeting', type: 'info', }, ], } let crewLinkInvitations = [ { colour: 'orange', expiresAt: Date.now() + 25000, groupId: 'crewlink-group-sandy', groupName: 'Sandy Trails', id: 'crewlink-invite-sandy', inviterUsername: 'Dusty', }, ] const crewLinkNearby = [ { distance: 2.4, source: 72, username: 'Echo' }, { distance: 4.7, source: 83, username: 'Atlas' }, ] const crewLinkLimits = { maximumGroups: 5, maximumMembers: 16, nearbyDistance: 5, overheadDistance: 50, pingLifetimeSeconds: 300, } function crewLinkBootstrap(testScenario = '') { if (testScenario === 'crewlink-onboarding') { return { groups: [], invitations: [], profile: null } } if (testScenario === 'crewlink-empty') { return { activeGroup: null, groups: [], invitations: crewLinkInvitations, limits: crewLinkLimits, profile: { ...crewLinkProfile, activeGroupId: null }, } } const activeSummary = crewLinkGroups.find( (group) => group.id === crewLinkProfile.activeGroupId, ) return { activeGroup: activeSummary ? { ...activeSummary, members: crewLinkMembers[activeSummary.id] ?? [], pings: crewLinkPings[activeSummary.id] ?? [], } : null, groups: crewLinkGroups, invitations: crewLinkInvitations, limits: crewLinkLimits, profile: crewLinkProfile, } } const flipTokProfile = { id: 1, handle: 'skyline', display_name: 'Skyline', bio: 'Life around Los Santos.', account_type: 'media', verified: true, is_following: false, is_owner: true, followers: 18400, following: 128, video_count: 2, } let flipTokAuthenticated = true const flipTokMusicTracks = [ { id: 'night-drive', title: 'Night Drive', artist: 'Los Santos Radio', url: 'https://media.w3.org/2010/07/bunny/04-Death_Becomes_Fur.oga', }, ] let flipTokVideos = [ { id: 'fliptok-1', profile_id: 2, handle: 'novals', display_name: 'Nova', verified: true, caption: 'A quiet minute above Vinewood. #LosSantos', location: 'Vinewood Hills', trim_start_ms: 0, trim_end_ms: null, cover_time_ms: 1200, original_volume: 100, music_volume: 0, music_track: '', music_title: '', music_artist: '', music_url: '', url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4', comments_enabled: true, is_liked: false, is_saved: false, is_following: false, is_owner: false, like_count: 12840, comment_count: 384, view_count: 245100, share_count: 932, created_at: Date.now() - 3600000, }, { id: 'fliptok-2', profile_id: 1, handle: 'skyline', display_name: 'Skyline', verified: true, caption: 'Tonight in the city.', location: 'Downtown Los Santos', trim_start_ms: 800, trim_end_ms: 12000, cover_time_ms: 2200, original_volume: 70, music_volume: 25, music_track: 'night-drive', music_title: 'Night Drive', music_artist: 'Los Santos Radio', music_url: flipTokMusicTracks[0].url, url: 'https://media.w3.org/2010/05/sintel/trailer.mp4', comments_enabled: true, is_liked: true, is_saved: true, is_following: false, is_owner: true, like_count: 4921, comment_count: 97, view_count: 88300, share_count: 220, created_at: Date.now() - 7200000, }, ] let flipTokComments = [ { id: 'comment-1', profile_id: 2, handle: 'nova', display_name: 'Nova', verified: true, body: 'This view is perfect.', created_at: Date.now() - 300000, }, ] let flipTokActivities = [ { id: 'activity-1', profile_id: 2, handle: 'nova', display_name: 'Nova', verified: true, kind: 'like', video_id: 'fliptok-2', read_at: null, created_at: Date.now() - 240000, }, ] let flipTokReports = [ { id: 'report-1', video_id: 'fliptok-1', reason: 'dangerous', details: 'Please review the driving shown in this clip.', created_at: Date.now() - 600000, caption: flipTokVideos[0].caption, url: flipTokVideos[0].url, reporter_handle: 'skyline', reporter_display_name: 'Skyline', creator_handle: 'novals', creator_display_name: 'Nova', }, ] let picstagramAuthenticated = true const picstagramProfiles = [ { avatar_media_id: null, avatar_url: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=240&q=80', bio: 'Street light, rain, and the quiet side of Los Santos.', display_name: 'Skyline', follow_status: null, followers: 1842, following: 128, handle: 'skyline', id: 'pic-profile-1', is_following: false, is_owner: true, is_requested: false, locked: false, post_count: 1, private: false, status: 'active', verified: true, }, { avatar_media_id: null, avatar_url: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=240&q=80', bio: 'Golden-hour wanderer.', display_name: 'Nova', follow_status: 'accepted', followers: 9214, following: 91, handle: 'nova.ls', id: 'pic-profile-2', is_following: true, is_owner: false, is_requested: false, locked: false, post_count: 2, private: false, status: 'active', verified: true, }, { avatar_media_id: null, avatar_url: null, bio: 'Private film diary.', display_name: 'Milo Reed', follow_status: null, followers: 318, following: 44, handle: 'milo.reed', id: 'pic-profile-3', is_following: false, is_owner: false, is_requested: false, locked: true, post_count: 0, private: true, status: 'active', verified: false, }, ] let picstagramPosts = [ { avatar_url: picstagramProfiles[1].avatar_url, caption: 'The city holds its breath right before sunrise. #LosSantos', comment_count: 2, comments_enabled: true, created_at: Date.now() - 32 * 60 * 1000, display_name: picstagramProfiles[1].display_name, handle: picstagramProfiles[1].handle, id: 'pic-post-1', is_liked: false, is_owner: false, is_saved: true, like_count: 842, location: 'Vinewood Hills', media: [ { id: 8101, position: 0, url: 'https://images.unsplash.com/photo-1518005020951-eccb494ad742?auto=format&fit=crop&w=900&q=85', }, { id: 8102, position: 1, url: 'https://images.unsplash.com/photo-1444723121867-7a241cacace9?auto=format&fit=crop&w=900&q=85', }, ], private: false, profile_id: picstagramProfiles[1].id, verified: true, }, { avatar_url: picstagramProfiles[0].avatar_url, caption: 'Neon after rain.', comment_count: 1, comments_enabled: true, created_at: Date.now() - 3 * 60 * 60 * 1000, display_name: picstagramProfiles[0].display_name, handle: picstagramProfiles[0].handle, id: 'pic-post-2', is_liked: true, is_owner: true, is_saved: false, like_count: 216, location: 'Downtown Los Santos', media: [ { id: 8103, position: 0, url: 'https://images.unsplash.com/photo-1519608487953-e999c86e7455?auto=format&fit=crop&w=900&q=85', }, ], private: false, profile_id: picstagramProfiles[0].id, verified: true, }, ] let picstagramComments = [ { avatar_url: picstagramProfiles[1].avatar_url, body: 'That reflection is unreal.', created_at: Date.now() - 18 * 60 * 1000, display_name: picstagramProfiles[1].display_name, handle: picstagramProfiles[1].handle, id: 'pic-comment-1', is_owner: false, parent_id: null, profile_id: picstagramProfiles[1].id, verified: true, }, ] let picstagramStories = [ { avatar_url: picstagramProfiles[1].avatar_url, body: 'First light over Vinewood.', created_at: Date.now() - 12 * 60 * 1000, display_name: picstagramProfiles[1].display_name, expires_at: Date.now() + 22 * 60 * 60 * 1000, handle: picstagramProfiles[1].handle, id: 'pic-story-1', is_owner: false, profile_id: picstagramProfiles[1].id, seen: false, url: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=900&q=85', verified: true, view_count: 42, }, ] let picstagramActivities = [ { avatar_url: picstagramProfiles[1].avatar_url, created_at: Date.now() - 9 * 60 * 1000, display_name: picstagramProfiles[1].display_name, handle: picstagramProfiles[1].handle, id: 'pic-activity-1', kind: 'like', post_id: 'pic-post-2', post_url: picstagramPosts[1].media[0].url, profile_id: picstagramProfiles[1].id, read_at: null, verified: true, }, ] let picstagramReports = [ { created_at: Date.now() - 45 * 60 * 1000, details: 'Please review the caption and location.', id: 'pic-report-1', reason: 'spam', reporter_display_name: 'Skyline', reporter_handle: 'skyline', target_id: 'pic-post-1', target_type: 'post', }, ] const mockBankTransactions = [ { id: 1, kind: 'transfer_in', amount: 3200, label: 'Sofia Turner', reference: 'mock-1', createdAt: Date.now() - 45 * 60 * 1000, }, { id: 2, kind: 'transfer_out', amount: 680, label: 'Vincent Cole', reference: 'mock-2', createdAt: Date.now() - 5 * 60 * 60 * 1000, }, { id: 3, kind: 'deposit', amount: 1250, label: '', reference: 'mock-3', createdAt: Date.now() - 25 * 60 * 60 * 1000, }, { id: 4, kind: 'transfer_out', amount: 420, label: 'Maya Brooks', reference: 'mock-4', createdAt: Date.now() - 50 * 60 * 60 * 1000, }, { id: 5, kind: 'withdrawal', amount: 300, label: '', reference: 'mock-5', createdAt: Date.now() - 76 * 60 * 60 * 1000, }, { id: 6, kind: 'transfer_in', amount: 950, label: 'Noah Bennett', reference: 'mock-6', createdAt: Date.now() - 120 * 60 * 60 * 1000, }, ] let mockBillingInvoices = [ { id: '31cc4342-1abd-4c34-a283-fc653632e54f', amount: 1300, currency: '$', description: 'Emergency treatment and medication.', direction: 'inbox', dueAt: Date.now() + 2 * 86400000, issuedAt: Date.now() - 2 * 3600000, issuerAccount: 'ambulance', issuerLabel: 'Los Santos Medical', isUnread: true, paidAt: null, paymentReference: '', status: 'open', title: 'Medical treatment', }, { id: '1098d704-a8e7-4050-99c9-a496399669ae', amount: 999, currency: '$', description: 'Vehicle repair and replacement parts.', direction: 'inbox', dueAt: Date.now() - 86400000, issuedAt: Date.now() - 4 * 86400000, issuerAccount: 'mechanic', issuerLabel: 'Benny’s Motorworks', isUnread: true, paidAt: null, paymentReference: '', status: 'open', title: 'Vehicle repair', }, { id: '666f23e1-747e-4df9-b3bb-603340e0af98', amount: 480, currency: '$', description: 'Tow service from Vespucci Boulevard.', direction: 'inbox', dueAt: Date.now() - 10 * 86400000, issuedAt: Date.now() - 14 * 86400000, issuerAccount: 'mechanic', issuerLabel: 'Los Santos Customs', isUnread: false, paidAt: Date.now() - 9 * 86400000, paymentReference: 'mock-payment-1', status: 'paid', title: 'Tow service', }, { id: '6c6817ae-c859-459c-9934-7ae0ff3b55fb', amount: 750, currency: '$', description: 'Consulting services.', direction: 'sent', dueAt: Date.now() + 5 * 86400000, issuedAt: Date.now() - 86400000, issuerAccount: 'consulting', issuerLabel: 'Alex Morgan', isUnread: false, paidAt: null, paymentReference: '', status: 'open', title: 'Consulting', }, ] const mockGarageVehicles = [ { id: 'vehicle-1', plate: 'SKY 204', vin: '1S9SKY204LS000001', nickname: 'Midnight', model: 'sultanrs', name: 'Karin Sultan RS', kind: 'car', status: 'garaged', location: 'Legion Square', fuel: 82, engine: 96, body: 91, }, { id: 'vehicle-2', plate: 'NOVA 77', vin: '1S9NOVA77LS000002', nickname: '', model: 'comet6', name: 'Pfister Comet S2', kind: 'car', status: 'out', location: '', fuel: 46, engine: 88, body: 73, }, { id: 'vehicle-3', plate: 'SEA 911', vin: '1S9SEA911LS000003', nickname: 'Blue Current', model: 'speeder', name: 'Pegassi Speeder', kind: 'boat', status: 'garaged', location: 'La Puerta Pier', fuel: 67, engine: 84, body: 79, }, { id: 'vehicle-4', plate: 'AIR 404', vin: '1S9AIR404LS000004', nickname: '', model: 'maverick', name: 'Buckingham Maverick', kind: 'helicopter', status: 'impounded', location: 'Impound Heli', fuel: 23, engine: 58, body: 44, }, ] let mockGarageValet = null const skyRideProfile = { acceptanceRate: 96, avatarUrl: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=240&q=80', cancelledRides: 3, completedRides: 146, currency: '$', defaultPaymentMethod: 'bank', earningsToday: 284, id: 'skyride-profile-demo', memberSince: unixTime(-214 * 24 * 60 * 60), name: 'Alex Morgan', rating: 4.92, } const skyRideDriver = { avatarUrl: 'https://images.unsplash.com/photo-1531123897727-8f129e1688ce?auto=format&fit=crop&w=240&q=80', id: 'skyride-driver-nova', location: { x: -121.8, y: -904.6, z: 29.3 }, name: 'Nova Hayes', phoneNumber: '5550199', rating: 4.97, trips: 842, vehicle: { color: 'Obsidian Black', model: 'Enus Deity', plate: 'RIDE 01', }, } const skyRidePassenger = { avatarUrl: skyRideProfile.avatarUrl, id: skyRideProfile.id, name: skyRideProfile.name, phoneNumber: '5550142', rating: skyRideProfile.rating, trips: skyRideProfile.completedRides, } function skyRideWithoutLiveContact(ride) { const passenger = { ...ride.passenger } delete passenger.phoneNumber let driver = null if (ride.driver) { driver = { ...ride.driver } delete driver.location delete driver.phoneNumber } return { ...ride, driver, passenger } } const skyRideQuickLocations = [ { coords: { x: -265.1, y: -960.2, z: 31.2 }, id: 'legion-square', label: 'Legion Square', }, { coords: { x: 925.2, y: 46.4, z: 81.1 }, id: 'diamond-casino', label: 'Diamond Casino', }, { coords: { x: -1037.7, y: -2737.8, z: 20.2 }, id: 'airport', label: 'Los Santos Airport', }, { coords: { x: -594.4, y: -929.9, z: 23.9 }, id: 'vinewood', label: 'Vinewood', }, ] let skyRideSequence = 4 let skyRideDriverOnline = false let skyRideActiveRide = null let skyRidePendingRating = null let skyRideAvailableRequests = [ { createdAt: unixTime(-90), currency: '$', destination: skyRideQuickLocations[1], driver: null, id: 'skyride-request-demo', passenger: { avatarUrl: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=240&q=80', id: 'skyride-passenger-jordan', name: 'Jordan Lee', rating: 4.84, trips: 38, }, pickup: skyRideQuickLocations[0], price: 42, serviceClass: 'comfort', status: 'searching', updatedAt: unixTime(-90), }, ] let skyRideHistory = [ skyRideWithoutLiveContact({ createdAt: unixTime(-22 * 60 * 60), currency: '$', destination: skyRideQuickLocations[3], driver: skyRideDriver, finalPrice: 31, id: 'skyride-history-1', passenger: skyRidePassenger, pickup: skyRideQuickLocations[0], price: 31, serviceClass: 'taxi', status: 'completed', updatedAt: unixTime(-21 * 60 * 60), }), skyRideWithoutLiveContact({ createdAt: unixTime(-3 * 24 * 60 * 60), currency: '$', destination: skyRideQuickLocations[2], driver: skyRideDriver, finalPrice: 68, id: 'skyride-history-2', passenger: skyRidePassenger, pickup: skyRideQuickLocations[1], price: 68, serviceClass: 'premium', status: 'completed', updatedAt: unixTime(-3 * 24 * 60 * 60 + 18 * 60), }), ] const skyRideQuotes = new Map() function skyRideBootstrap() { return { activeRide: skyRideActiveRide, availableRequests: skyRideAvailableRequests, driverEligible: true, driverOnline: skyRideDriverOnline, history: skyRideHistory, pendingRating: skyRidePendingRating, profile: skyRideProfile, quickLocations: skyRideQuickLocations, } } function skyRideUpdate(fields) { return Object.fromEntries( fields.map((field) => [field, skyRideBootstrap()[field]]), ) } const mockHousingOverview = { available: true, provider: 'esx_property', properties: [ { id: 'esx_property:1', providerId: '1', name: 'Alta Street Apartment', access: 'owner', locked: true, entrance: { x: -268.9, y: -962.3, z: 31.2 }, capabilities: { lock: true, keys: true, waypoint: true, cctv: true, garageStatus: true, }, cctv: { enabled: true }, garage: { enabled: true, storedVehicles: 2 }, keys: [{ identifier: 'char2:mock', name: 'Jamie Rivera', online: true }], }, { id: 'esx_property:2', providerId: '2', name: 'Vespucci Beach House', access: 'keyholder', locked: false, entrance: { x: -1150.1, y: -1520.8, z: 10.6 }, capabilities: { lock: true, keys: false, waypoint: true, cctv: false, garageStatus: false, }, cctv: { enabled: false }, garage: null, }, ], } let contactSequence = 40 const contacts = [ { avatar_media_id: 1, avatar_url: 'https://picsum.photos/seed/sky-phone-1/600/800', created_at: isoTime(-42 * 86_400_000), favorite: true, id: 'contact-alex', name: 'Alex Rivera', notes: 'Meeting on Friday at 18:00 near the bank.', organization: 'Maze Bank', phone_number: '5551110001', updated_at: isoTime(-6 * 86_400_000), }, { created_at: isoTime(-38 * 86_400_000), id: 'contact-alexander', name: 'Alexander Stone', phone_number: '5551110002', updated_at: isoTime(-8 * 86_400_000), }, { created_at: isoTime(-21 * 86_400_000), id: 'contact-andre', name: 'Andre Heinicke', phone_number: '5551110003', updated_at: isoTime(-4 * 86_400_000), }, { created_at: isoTime(-34 * 86_400_000), favorite: true, id: 'contact-benni', name: 'Benni Parker', notes: 'Call about the Sultan RS repair estimate.', organization: "Benny's Motor Works", phone_number: '5551110004', updated_at: isoTime(-12 * 86_400_000), }, { avatar_media_id: 3, avatar_url: 'https://picsum.photos/seed/sky-phone-3/800/600', created_at: isoTime(-18 * 86_400_000), id: 'contact-bryce', name: 'Bryce Walker', phone_number: '5551110005', updated_at: isoTime(-7 * 86_400_000), }, { created_at: isoTime(-27 * 86_400_000), id: 'contact-charlie', name: 'Charlie Davis', phone_number: '5551110006', updated_at: isoTime(-11 * 86_400_000), }, { created_at: '2026-08-04 12:00:00', id: 'contact-1', name: 'Jenica Chong', phone_number: '5558675309', updated_at: '2026-08-04 12:00:00', }, { created_at: isoTime(-14 * 86_400_000), id: 'contact-morgan', name: 'Morgan Reed', phone_number: '5550192847', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-30 * 86_400_000), id: 'contact-jamie', name: 'Jamie Chen', phone_number: '5559876543', updated_at: isoTime(-30 * 86_400_000), }, { created_at: isoTime(-5 * 86_400_000), id: 'contact-mechanic', name: 'Downtown Customs', phone_number: '5550100101', updated_at: isoTime(-5 * 86_400_000), }, { avatar_media_id: 4, avatar_url: 'https://picsum.photos/seed/sky-phone-4/800/600', created_at: isoTime(-3 * 86_400_000), favorite: true, id: 'contact-taxi', name: 'Los Santos Taxi', organization: 'Los Santos Taxi Co.', phone_number: '5552222222', updated_at: isoTime(-3 * 86_400_000), }, { created_at: isoTime(-22 * 86_400_000), id: 'contact-daniel', name: 'Daniel Price', phone_number: '5551110007', updated_at: isoTime(-3 * 86_400_000), }, { created_at: isoTime(-20 * 86_400_000), id: 'contact-emily', name: 'Emily Hart', phone_number: '5551110008', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-31 * 86_400_000), id: 'contact-franklin', name: 'Franklin Miles', phone_number: '5551110009', updated_at: isoTime(-9 * 86_400_000), }, { created_at: isoTime(-25 * 86_400_000), id: 'contact-grace', name: 'Grace Carter', phone_number: '5551110010', updated_at: isoTime(-4 * 86_400_000), }, { created_at: isoTime(-19 * 86_400_000), id: 'contact-hannah', name: 'Hannah Brooks', phone_number: '5551110011', updated_at: isoTime(-5 * 86_400_000), }, { created_at: isoTime(-17 * 86_400_000), id: 'contact-ivan', name: 'Ivan Petrov', phone_number: '5551110012', updated_at: isoTime(-6 * 86_400_000), }, { created_at: isoTime(-16 * 86_400_000), id: 'contact-kevin', name: 'Kevin Adams', phone_number: '5551110013', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-15 * 86_400_000), id: 'contact-naomi', name: 'Naomi King', phone_number: '5551110014', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-13 * 86_400_000), id: 'contact-olivia', name: 'Olivia Moore', phone_number: '5551110015', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-12 * 86_400_000), id: 'contact-paul', name: 'Paul Walker', phone_number: '5551110016', updated_at: isoTime(-4 * 86_400_000), }, { created_at: isoTime(-11 * 86_400_000), id: 'contact-quinn', name: 'Quinn Bailey', phone_number: '5551110017', updated_at: isoTime(-3 * 86_400_000), }, { created_at: isoTime(-10 * 86_400_000), id: 'contact-riley', name: 'Riley Cooper', phone_number: '5551110018', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-9 * 86_400_000), id: 'contact-sofia', name: 'Sofia Bennett', phone_number: '5551110019', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-8 * 86_400_000), id: 'contact-thomas', name: 'Thomas Reed', phone_number: '5551110020', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-7 * 86_400_000), id: 'contact-ursula', name: 'Ursula Grant', phone_number: '5551110021', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-6 * 86_400_000), id: 'contact-victor', name: 'Victor Young', phone_number: '5551110022', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-5 * 86_400_000), id: 'contact-wendy', name: 'Wendy Clark', phone_number: '5551110023', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-4 * 86_400_000), id: 'contact-xavier', name: 'Xavier Cole', phone_number: '5551110024', updated_at: isoTime(-2 * 86_400_000), }, { created_at: isoTime(-3 * 86_400_000), id: 'contact-yakup', name: 'Yakup Broooooo', phone_number: '5551110025', updated_at: isoTime(-86_400_000), }, { created_at: isoTime(-2 * 86_400_000), id: 'contact-zoe', name: 'Zoe Martinez', phone_number: '5551110026', updated_at: isoTime(-86_400_000), }, { created_at: isoTime(-86_400_000), id: 'contact-market', name: '24/7 Supermarket', phone_number: '5552470000', updated_at: isoTime(-3_600_000), }, ] const attachmentAssets = { gif: new Set(['celebrate', 'hearts', 'party', 'thumbs_up', 'wow']), image: new Set([ 'camera-1', 'camera-2', 'camera-3', 'city-lights', 'desert-road', 'ocean-air', 'sunset-drive', ]), video: new Set(['city-loop', 'ocean-loop', 'sunset-loop']), } const gifMocks = [ ['ICOgUNjpvO0PC', 'Cat reaction'], ['MDJ9IbxxvDUQM', 'Happy dog'], ['l0HlPystfePnAI3G8', 'Celebrate'], ['26ufdipQqU2lhNA4g', 'Wow'], ['3o7abKhOpu0NwenH3O', 'Perfect'], ['xT0xeJpnrWC4XWblEk', 'Party'], ['111ebonMs90YLu', 'Thumbs up'], ['5GoVLqeAOo6PK', 'Excited'], ['TdfyKrN7HGTIY', 'Happy dance'], ['14udF3WUwwGMaA', 'Surprised'], ['3o6Zt6ML6BklcajjsA', 'Applause'], ['13CoXDiaCcCoyk', 'Let us go'], ['R6gvnAxj2ISzJdbA63', 'Yes'], ['xUPGcEliCc7bETyfO8', 'Laughing'], ['26BRuo6sLetdllPAQ', 'Dancing'], ['l46CyJmS9KUbokzsI', 'Amazing'], ['g9582DNuQppxC', 'Celebration'], ['artj92V8o75VPL7AeQ', 'High five'], ] 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\n\nYour shared mailbox is **ready to use**.\n\n- Send formatted messages\n- Keep drafts on every linked device\n- Reply without losing the conversation\n\n> Tip: use the small formatting bar while composing.', created_at: '2026-08-04 11:30:00', folder: 'inbox', id: 1, is_read: false, message_id: 'b73f3872-54cc-4d74-a058-6caa24f0dfff', preview: 'Your shared mailbox is **ready to use**. Send formatted messages and keep drafts on every linked device.', 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', }, ] const smsMessages = [ { body: 'Bin gleich am Würfelpark. Kommst du auch?', created_at: '2026-08-06 13:04:00', direction: 'received', id: 'sms-1', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: null, recipient_number: '5551234567', sender_number: '5558675309', }, { body: 'Ja, gib mir fünf Minuten.', created_at: '2026-08-06 13:05:00', direction: 'sent', id: 'sms-2', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: null, recipient_number: '5558675309', sender_number: '5551234567', }, { body: 'Das Fahrzeug ist fertig. Du kannst es jederzeit abholen.', created_at: isoTime(-26 * 60 * 60_000), direction: 'received', id: 'sms-customs-1', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: isoTime(-25 * 60 * 60_000), recipient_number: '5551234567', sender_number: '5550100101', }, { body: 'Perfekt, ich komme heute Abend vorbei.', created_at: isoTime(-25 * 60 * 60_000), direction: 'sent', id: 'sms-customs-2', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: isoTime(-25 * 60 * 60_000), recipient_number: '5550100101', sender_number: '5551234567', }, { body: 'Treffen wir uns um 20 Uhr am Casino?', created_at: isoTime(-7 * 60 * 60_000), direction: 'received', id: 'sms-morgan-1', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: isoTime(-6 * 60 * 60_000), recipient_number: '5551234567', sender_number: '5550192847', }, { body: 'Ja, passt. Ich bin puenktlich da.', created_at: isoTime(-6 * 60 * 60_000), direction: 'sent', id: 'sms-morgan-2', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: isoTime(-6 * 60 * 60_000), recipient_number: '5550192847', sender_number: '5551234567', }, { body: 'Bin in zehn Minuten bei dir.', created_at: isoTime(-95 * 60_000), direction: 'received', id: 'sms-jamie-1', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: null, recipient_number: '5551234567', sender_number: '5559876543', }, { body: 'Dein Taxi wartet vor dem Haupteingang.', created_at: isoTime(-38 * 60_000), direction: 'received', id: 'sms-taxi-1', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: null, recipient_number: '5551234567', sender_number: '5552222222', }, { body: 'Danke, ich komme sofort raus.', created_at: isoTime(-36 * 60_000), direction: 'sent', id: 'sms-taxi-2', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: isoTime(-35 * 60_000), recipient_number: '5552222222', sender_number: '5551234567', }, { body: 'Denk bitte an die Unterlagen fuer morgen.', created_at: isoTime(-12 * 60_000), direction: 'received', id: 'sms-alex-1', media_duration_ms: null, media_mime: null, media_payload: null, media_waveform: null, message_type: 'text', media_asset_id: null, read_at: null, recipient_number: '5551234567', sender_number: '5551110001', }, { body: 'Samantha Cole', contact: { avatar_url: 'https://picsum.photos/seed/shared-samantha/240/240', name: 'Samantha Cole', organization: 'Downtown Cab Co.', phone_number: '5553330044', }, created_at: isoTime(-8 * 60_000), direction: 'received', id: 'sms-contact-1', media_duration_ms: null, media_mime: null, media_payload: { avatar_url: 'https://picsum.photos/seed/shared-samantha/240/240', name: 'Samantha Cole', organization: 'Downtown Cab Co.', phone_number: '5553330044', }, media_waveform: null, message_type: 'contact', media_asset_id: null, read_at: null, recipient_number: '5551234567', sender_number: '5551110001', }, { body: 'Neon nights in Vinewood', created_at: isoTime(-4 * 60_000), direction: 'received', id: 'sms-share-1', media_duration_ms: null, media_mime: null, media_payload: { appId: 'picstagram', copyText: 'Die besten Lichter der Stadt – direkt aus Vinewood.', id: 'picstagram-post-neon-nights', imageUrl: 'https://picsum.photos/seed/easyshare-neon/720/720', kind: 'post', link: 'skyphone://picstagram/post/picstagram-post-neon-nights', subtitle: '@morgan', title: 'Neon nights in Vinewood', }, media_waveform: null, message_type: 'share', media_asset_id: null, read_at: null, recipient_number: '5551234567', sender_number: '5551110001', }, ] const darkChatProfile = { id: 1, darkId: 'dark:7X4K-P92D', inviteCode: 'DC-7X4K-NOVA', alias: 'Nightshade', avatarSeed: 267, notificationMode: 'private', activityVisible: false, createdAt: '2026-08-01 21:20:00', } const darkChatPeers = [ { id: 2, darkId: 'dark:N0VA-41KQ', alias: 'Nova', originalAlias: 'Nova', avatarSeed: 142, activityVisible: true, isContact: true, blocked: false, }, { id: 3, darkId: 'dark:ECH0-77LM', alias: 'Echo', originalAlias: 'Echo', avatarSeed: 311, activityVisible: false, isContact: true, blocked: false, }, ] const darkChatConversations = [ { id: 'dc-conversation-nova-0000-000000000001', peer: darkChatPeers[0], disappearingSeconds: 3600, notificationsEnabled: true, readReceipts: true, blockedByPeer: false, createdAt: '2026-08-03 22:12:00', }, ] const darkChatMessages = [ { id: 'dc-message-00000000-0000-000000000001', conversationId: darkChatConversations[0].id, direction: 'received', senderProfileId: 2, messageType: 'text', body: 'The east gate is clear. Are you close?', reactions: {}, createdAt: '2026-08-06 22:42:00', readAt: '2026-08-06 22:43:00', }, { id: 'dc-message-00000000-0000-000000000002', conversationId: darkChatConversations[0].id, direction: 'sent', senderProfileId: 1, messageType: 'text', body: 'Two minutes. Keep this channel quiet. 🟣', reactions: { 2: '👍' }, createdAt: '2026-08-06 22:43:00', readAt: '2026-08-06 22:43:30', }, { id: 'dc-message-00000000-0000-000000000003', conversationId: darkChatConversations[0].id, direction: 'received', senderProfileId: 2, messageType: 'gif', body: '', mediaPayload: 'https://media.giphy.com/media/ICOgUNjpvO0PC/giphy.gif', reactions: {}, createdAt: '2026-08-06 22:44:00', readAt: null, }, { id: 'dc-message-00000000-0000-000000000004', conversationId: darkChatConversations[0].id, direction: 'received', senderProfileId: 2, messageType: 'share', body: 'Downtown is awake', reactions: {}, sharePayload: { appId: 'feather', copyText: 'Vinewood after midnight. No filters, just city light.', id: 'feather-post-downtown-awake', imageUrl: 'https://picsum.photos/seed/easyshare-downtown/900/600', kind: 'post', link: 'skyphone://feather/post/feather-post-downtown-awake', subtitle: '@nightowl', title: 'Downtown is awake', }, createdAt: isoTime(-3 * 60_000), readAt: null, }, ] function darkChatBootstrap() { return { profile: darkChatProfile, contacts: darkChatPeers .filter((peer) => peer.isContact) .map((peer) => ({ ...peer, createdAt: '2026-08-03 22:12:00' })), conversations: darkChatConversations.map((conversation) => { const thread = darkChatMessages.filter( (message) => message.conversationId === conversation.id, ) const last = thread.at(-1) return { id: conversation.id, peer: conversation.peer, disappearingSeconds: conversation.disappearingSeconds, blocked: conversation.peer.blocked, lastMessage: last?.body ?? '', lastMessageType: last?.messageType ?? 'system', lastMessageAt: last?.createdAt ?? conversation.createdAt, unread: thread.filter( (message) => message.direction === 'received' && !message.readAt, ).length, } }), } } const marketplaceListings = [ { id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf01', seller_account_id: 2, seller_name: 'morgan', seller_since: '2026-07-02 10:00:00', seller_active: 2, title: 'Sultan RS in excellent condition', description: 'Clean Sultan RS with fresh service, tidy interior and no known damage. Viewing and test drive available in Vinewood.', category: 'vehicles', item_condition: 'very_good', price_type: 'negotiable', price: 185000, district: 'vinewood', status: 'active', revision: 1, show_phone: 0, phone_number: null, created_at: '2026-08-06 09:20:00', updated_at: '2026-08-06 09:20:00', expires_at: '2026-08-13 09:20:00', image: 'linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)', is_favorite: false, images: [ { media_id: 'capture-car', gradient: 'linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)', sort_order: 1, }, { media_id: 'city-lights', gradient: 'linear-gradient(135deg, #fbc2eb, #a6c1ee 48%, #302b63)', sort_order: 2, }, { media_id: 'ocean-air', gradient: 'linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)', sort_order: 3, }, ], }, { id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf02', seller_account_id: 3, seller_name: 'jamie', seller_since: '2026-06-14 10:00:00', seller_active: 1, title: 'Modern apartment near Vespucci', description: 'Bright apartment with a city view, underground parking and modern furniture. Available immediately after viewing.', category: 'property', item_condition: 'very_good', price_type: 'fixed', price: 420000, district: 'vespucci', status: 'active', revision: 1, show_phone: 0, phone_number: null, created_at: '2026-08-05 17:10:00', updated_at: '2026-08-05 17:10:00', expires_at: '2026-08-12 17:10:00', image: null, is_favorite: true, images: [], }, { id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf03', seller_account_id: 4, seller_name: 'citytech', seller_since: '2026-05-22 10:00:00', seller_active: 3, title: 'Nearly new gaming laptop', description: 'Fast gaming laptop including charger and carrying bag. Runs quietly and can be tested before purchase.', category: 'electronics', item_condition: 'very_good', price_type: 'fixed', price: 3500, district: 'los_santos', status: 'reserved', revision: 2, show_phone: 0, phone_number: null, created_at: '2026-08-05 12:30:00', updated_at: '2026-08-06 08:00:00', expires_at: '2026-08-12 12:30:00', image: 'linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)', is_favorite: false, images: [ { media_id: 'ocean-air', gradient: 'linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)', sort_order: 1, }, ], }, { id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf04', seller_account_id: 1, seller_name: 'demo', seller_since: '2026-08-04 12:00:00', seller_active: 1, title: 'Complete mechanic tool set', description: 'Complete mechanic tool set with trolley, sockets and diagnostic equipment. Everything is clean and ready for work.', category: 'tools', item_condition: 'very_good', price_type: 'fixed', price: 7800, district: 'south_los_santos', status: 'active', revision: 1, show_phone: 1, phone_number: '5551234567', created_at: '2026-08-06 10:45:00', updated_at: '2026-08-06 10:45:00', expires_at: '2026-08-13 10:45:00', image: 'linear-gradient(135deg, #ffc75f, #f96d80 48%, #4b4453)', is_favorite: false, images: [ { media_id: 'capture-tools', gradient: 'linear-gradient(135deg, #ffc75f, #f96d80 48%, #4b4453)', sort_order: 1, }, ], }, ] let linkedAccount = { devices: accountDevices, email: 'demo@ifruit.com', id: 1, } let mockNotes = [ { body: 'Meet Morgan in Vinewood and inspect the Sultan RS.', createdAt: Date.now() - 3_600_000, id: 'demo-note-marketplace', pinned: true, revision: 1, title: 'CityMarkt viewing', updatedAt: Date.now() - 3_600_000, }, { body: 'Repair kit\nRadio\nFlashlight\nTwo bottles of water', createdAt: Date.now() - 7_200_000, id: 'demo-note-packing-list', pinned: true, revision: 2, title: 'Patrol equipment', updatedAt: Date.now() - 1_800_000, }, { body: 'Call Jamie after the garage inspection and confirm the final price.', createdAt: Date.now() - 86_400_000, id: 'demo-note-call-jamie', pinned: false, revision: 1, title: 'Call Jamie', updatedAt: Date.now() - 86_400_000, }, { body: 'Observatory, Vespucci Pier, Paleto lookout, Sandy airfield.', createdAt: Date.now() - 172_800_000, id: 'demo-note-photo-spots', pinned: false, revision: 1, title: 'Photo locations', updatedAt: Date.now() - 172_800_000, }, ] let calendarEvents = [ { endsAt: calendarTime(0, 10), id: 'calendar-team-briefing', note: 'Meet in the Mission Row briefing room. Bring the updated patrol plan.', remindedAt: null, reminderMinutes: 30, revision: 1, startsAt: calendarTime(0, 9), title: 'Team briefing', }, { endsAt: calendarTime(0, 21, 30), id: 'calendar-citymarkt-viewing', note: 'Inspect the blue Sultan RS at the Vinewood garage and take a short test drive.', remindedAt: null, reminderMinutes: 60, revision: 2, startsAt: calendarTime(0, 20), title: 'CityMarkt vehicle viewing', }, { endsAt: calendarTime(1, 15, 30), id: 'calendar-garage-service', note: 'Routine service, wheel alignment and diagnostics.', remindedAt: null, reminderMinutes: 10, revision: 1, startsAt: calendarTime(1, 14), title: 'Garage appointment', }, { endsAt: calendarTime(3, 22), id: 'calendar-birthday-dinner', note: 'Table is booked on the terrace. Pick up the cake first.', remindedAt: null, reminderMinutes: 1440, revision: 1, startsAt: calendarTime(3, 19, 30), title: "Jamie's birthday dinner", }, { endsAt: calendarTime(-2, 18), id: 'calendar-past-beach-cruise', note: 'Vespucci Pier to the Paleto lookout.', remindedAt: calendarTime(-2, 16), reminderMinutes: 60, revision: 1, startsAt: calendarTime(-2, 17), title: 'Beach cruise', }, ] const deviceData = { alarms: { payload: [ { enabled: true, id: 'demo-weekday-alarm', lastTriggeredMinute: null, note: 'Morning patrol', sound: 'radar', time: '07:15', weekdays: [1, 2, 3, 4, 5], }, { enabled: true, id: 'demo-garage-alarm', lastTriggeredMinute: null, note: 'Garage appointment', sound: 'chimes', time: '13:30', weekdays: [], }, { enabled: false, id: 'demo-weekend-alarm', lastTriggeredMinute: null, note: 'Weekend drive', sound: 'apex', time: '09:30', weekdays: [0, 6], }, ], revision: 3, }, apps: { payload: { claimedApps: [], }, revision: 2, }, games: { payload: { memory: { best: { small: { moves: 12, timeMs: 38_400 }, medium: { moves: 29, timeMs: 92_100 }, }, soundEnabled: true, }, minesweeper: { best: { quick: { timeMs: 44_200 }, classic: { timeMs: 128_700 }, }, elapsedMs: 0, game: null, soundEnabled: true, }, 'neon-drop': { bestLines: 18, bestScore: 12_450, soundEnabled: true }, 'number-merge': { bestScore: 8_920, game: null, highestTile: 1024, soundEnabled: true, }, 'sky-flappy': { design: 'neon', highScore: 23, soundEnabled: true }, snake: { highScore: 17, speed: 'normal' }, 'tower-stack': { highHeight: 21, highScore: 4_850, soundEnabled: true }, }, revision: 4, }, media: { payload: { captures: [ { capturedAt: Date.now() - 1_200_000, gradient: 'linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)', id: 'capture-car', titleKey: 'Apps.photos.samples.capture', }, { capturedAt: Date.now() - 5_400_000, gradient: 'linear-gradient(135deg, #ffc75f, #f96d80 48%, #4b4453)', id: 'capture-tools', titleKey: 'Apps.photos.samples.capture', }, ], claimedApps: [], }, revision: 2, }, settings: { payload: { settings: { airplaneMode: false, appearanceMode: 'automatic', frame: 'black', notificationDurationSeconds: 10, notificationSound: 'chime', notificationVolume: 70, phoneScale: 100, ringtone: 'skyline', ringtoneVolume: 80, streamerMode: false, wallpaper: 'midnight', }, version: 1, }, revision: 1, }, } let mockPasscode = '' let mockSecurity = { enabled: false, length: null, lockedUntil: 0 } const blockedCallNumbers = new Set() let recentCalls = [ { call_id: 'call-alex-incoming', created_at: isoTime(-8 * 60_000), direction: 'incoming', duration_seconds: 184, id: 1, other_number: '5551110001', status: 'completed', }, { call_id: 'call-morgan-incoming', created_at: isoTime(-18 * 60_000), direction: 'incoming', duration_seconds: 246, id: 2, other_number: '5550192847', status: 'completed', }, { call_id: 'call-taxi-outgoing', created_at: isoTime(-42 * 60_000), direction: 'outgoing', duration_seconds: 39, id: 3, other_number: '5552222222', status: 'completed', }, { call_id: 'call-jamie-missed', created_at: isoTime(-95 * 60_000), direction: 'incoming', duration_seconds: 0, id: 4, other_number: '5559876543', status: 'missed', }, { call_id: 'call-emily-no-answer', created_at: isoTime(-4 * 60 * 60_000), direction: 'outgoing', duration_seconds: 0, id: 5, other_number: '5551110008', status: 'no_answer', }, { call_id: 'call-yakup-incoming', created_at: isoTime(-8 * 60 * 60_000), direction: 'incoming', duration_seconds: 521, id: 6, other_number: '5551110025', status: 'completed', }, { call_id: 'call-customs-outgoing', created_at: isoTime(-25 * 60 * 60_000), direction: 'outgoing', duration_seconds: 83, id: 7, other_number: '5550100101', status: 'completed', }, { call_id: 'call-unknown-declined', created_at: isoTime(-27 * 60 * 60_000), direction: 'incoming', duration_seconds: 0, id: 8, other_number: '5554040404', status: 'declined', }, { call_id: 'call-morgan-outgoing', created_at: isoTime(-2 * 86_400_000), direction: 'outgoing', duration_seconds: 72, id: 9, other_number: '5550192847', status: 'completed', }, { call_id: 'call-alex-missed', created_at: isoTime(-3 * 86_400_000), direction: 'incoming', duration_seconds: 0, id: 10, other_number: '5551110001', status: 'missed', }, { call_id: 'call-benni-busy', created_at: isoTime(-4 * 86_400_000), direction: 'outgoing', duration_seconds: 0, id: 11, other_number: '5551110004', status: 'busy', }, { call_id: 'call-sofia-incoming', created_at: isoTime(-5 * 86_400_000), direction: 'incoming', duration_seconds: 116, id: 12, other_number: '5551110019', status: 'completed', }, { call_id: 'call-xavier-unavailable', created_at: isoTime(-6 * 86_400_000), direction: 'outgoing', duration_seconds: 0, id: 13, other_number: '5551110024', status: 'unavailable', }, { call_id: 'call-market-outgoing', created_at: isoTime(-7 * 86_400_000), direction: 'outgoing', duration_seconds: 51, id: 14, other_number: '5552470000', status: 'completed', }, { call_id: 'call-unknown-missed', created_at: isoTime(-8 * 86_400_000), direction: 'incoming', duration_seconds: 0, id: 15, other_number: '5559090909', status: 'missed', }, ] let mockMedia = [ { createdAt: Date.now() - 60_000, id: 1, mediaType: 'photo', url: 'https://picsum.photos/seed/sky-phone-1/600/800', }, { createdAt: Date.now() - 120_000, id: 2, mediaType: 'video', url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4', }, { createdAt: Date.now() - 180_000, id: 3, mediaType: 'photo', url: 'https://picsum.photos/seed/sky-phone-3/800/600', }, { createdAt: Date.now() - 240_000, id: 4, mediaType: 'photo', url: 'https://picsum.photos/seed/sky-phone-4/800/600', }, { createdAt: Date.now() - 300_000, id: 5, mediaType: 'photo', url: 'https://picsum.photos/seed/sky-phone-5/800/600', }, ] const marketplaceInquiries = [ { id: '4903b923-409a-437e-971f-b7a2b10e9e31', listing_id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf01', seller_account_id: 2, buyer_account_id: 1, seller_name: 'morgan', buyer_name: 'demo', title: 'Sultan RS in excellent condition', price: 185000, price_type: 'negotiable', status: 'active', image: 'linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)', other_name: 'morgan', last_message: 'Sure, come by the Vinewood garage around 8 PM.', offer_id: 2, offer_amount: 178000, offer_proposer_account_id: 2, offer_status: 'pending', offer_revision: 2, unread: 2, updated_at: '2026-08-06 11:42:00', }, { id: '4903b923-409a-437e-971f-b7a2b10e9e32', listing_id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf04', seller_account_id: 1, buyer_account_id: 3, seller_name: 'demo', buyer_name: 'jamie', title: 'Complete mechanic tool set', price: 7800, price_type: 'fixed', status: 'active', image: 'linear-gradient(135deg, #ffc75f, #f96d80 48%, #4b4453)', other_name: 'jamie', last_message: 'I can collect it today and pay the full price.', offer_id: 3, offer_amount: 7000, offer_proposer_account_id: 3, offer_status: 'pending', offer_revision: 1, unread: 3, updated_at: '2026-08-06 11:55:00', }, ] const marketplaceMessages = [ { id: 1, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e31', sender_account_id: 1, body: 'Hi, is the Sultan still available?', created_at: '2026-08-06 11:35:00', read_at: '2026-08-06 11:36:00', }, { id: 2, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e31', sender_account_id: 2, body: 'Yes, it is. You can also take it for a short test drive.', created_at: '2026-08-06 11:38:00', read_at: '2026-08-06 11:39:00', }, { id: 3, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e31', sender_account_id: 2, body: 'Sure, come by the Vinewood garage around 8 PM.', created_at: '2026-08-06 11:42:00', read_at: null, }, { id: 4, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e32', sender_account_id: 3, body: 'Hello, does the diagnostic equipment work with every vehicle?', created_at: '2026-08-06 11:51:00', read_at: null, }, { id: 5, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e32', sender_account_id: 3, body: 'I can collect it today and pay the full price.', created_at: '2026-08-06 11:55:00', read_at: null, }, ] const marketplaceOffers = [ { id: 1, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e31', proposer_account_id: 1, amount: 170000, status: 'countered', read_at: '2026-08-06 11:43:00', response_read_at: null, created_at: '2026-08-06 11:40:00', updated_at: '2026-08-06 11:44:00', }, { id: 2, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e31', proposer_account_id: 2, amount: 178000, status: 'pending', read_at: null, response_read_at: null, created_at: '2026-08-06 11:44:00', updated_at: '2026-08-06 11:44:00', }, { id: 3, inquiry_id: '4903b923-409a-437e-971f-b7a2b10e9e32', proposer_account_id: 3, amount: 7000, status: 'pending', read_at: null, response_read_at: null, created_at: '2026-08-06 11:56:00', updated_at: '2026-08-06 11:56:00', }, ] const pagesPosts = [ { id: 'pages-citymarkt-1', account_id: 2, author_name: 'morgan', source_type: 'citymarkt', citymarkt_listing_id: '81bc9d37-20e1-4d8a-82f8-f4b85f77cf01', title: 'Sultan RS in excellent condition', body: 'Clean Sultan RS with fresh service, tidy interior and no known damage. Viewing and test drive available in Vinewood.', category: 'citymarkt', district: 'vinewood', created_at: Date.parse('2026-08-06T12:50:00Z'), like_count: 24, images: [ { media_id: 'capture-car', gradient: 'linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)', sort_order: 1, }, ], }, { id: 'pages-1', account_id: 2, author_name: 'morgan', source_type: 'personal', citymarkt_listing_id: null, title: 'Best sunset view above Vinewood', body: 'Take the small trail behind the observatory shortly before sunset. The view across Los Santos is incredible and there is enough space to park two cars.', category: 'recommendation', district: 'vinewood', created_at: Date.parse('2026-08-06T12:20:00Z'), like_count: 18, images: [ { media_id: 'sunset-drive', gradient: 'linear-gradient(145deg, #ff9a62, #5f2c82 58%, #141e30)', sort_order: 1, }, ], }, { id: 'pages-2', account_id: 1, author_name: 'demo', source_type: 'personal', citymarkt_listing_id: null, title: 'Looking for people for a beach cruise', body: 'Meeting at Vespucci Pier tonight. Bring a clean car and a good playlist. Everyone is welcome.', category: 'community', district: 'vespucci', created_at: Date.parse('2026-08-06T11:10:00Z'), like_count: 7, images: [ { media_id: 'ocean-air', gradient: 'linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)', sort_order: 1, }, ], }, { id: 'pages-3', account_id: 3, author_name: 'jamie', source_type: 'personal', citymarkt_listing_id: null, title: 'Mobile repair help around Sandy Shores', body: 'I can help with small repairs and jump starts around Sandy Shores this afternoon. Send me a message when you see me nearby.', category: 'service', district: 'sandy_shores', created_at: Date.parse('2026-08-05T19:40:00Z'), like_count: 12, images: [], }, ] const pagesReactions = [ { post_id: 'pages-1', account_id: 1, kind: 'like' }, { post_id: 'pages-3', account_id: 1, kind: 'save' }, ] function pageView(post) { const listing = marketplaceListings.find( (item) => item.id === post.citymarkt_listing_id, ) return { ...post, citymarkt_price: listing?.price ?? null, image: post.images[0]?.gradient ?? null, is_liked: pagesReactions.some( (item) => item.post_id === post.id && item.account_id === 1 && item.kind === 'like', ), is_owner: authenticated && post.account_id === 1, is_saved: pagesReactions.some( (item) => item.post_id === post.id && item.account_id === 1 && item.kind === 'save', ), } } 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, } } let flareProfile = { id: 1, name: 'Alex', age: 27, bio: 'Late-night drives, good coffee, and plans that turn into stories.', gender: 'nonbinary', interestedIn: 'everyone', minAge: 21, maxAge: 39, avatar: 5, interests: ['Night drives', 'Music', 'Coffee'], lookingFor: 'dates', discoverable: true, photoMediaIds: [], photoUrls: [], } let flareSuggestions = [ { id: 11, name: 'Maya', age: 26, bio: 'Ocean air, rooftop sunsets and always up for finding the best tacos in the city.', gender: 'woman', avatar: 0, interests: ['Beach days', 'Food spots', 'Art'], lookingFor: 'longTerm', photoUrls: [], }, { id: 12, name: 'Noah', age: 29, bio: 'Architect by day, terrible karaoke singer by night.', gender: 'man', avatar: 1, interests: ['Architecture', 'Karaoke', 'Travel'], lookingFor: 'dates', photoUrls: [], }, { id: 13, name: 'Sofia', age: 25, bio: 'Tell me your favorite hidden corner of Los Santos.', gender: 'woman', avatar: 2, interests: ['Coffee', 'Photography', 'Dogs'], lookingFor: 'friends', photoUrls: [], }, { id: 14, name: 'Leo', age: 28, bio: 'Desert roads, classic cars and a playlist for every mood.', gender: 'man', avatar: 3, interests: ['Cars', 'Road trips', 'Vinyl'], lookingFor: 'longTerm', photoUrls: [], }, { id: 15, name: 'Jade', age: 24, bio: 'Usually somewhere near the water with an iced coffee.', gender: 'woman', avatar: 4, interests: ['Sailing', 'Fitness', 'Brunch'], lookingFor: 'dates', photoUrls: [], }, ] const flareMatches = [ { id: 'flare-match-demo-0000-0000-0000000001', profile: { id: 16, name: 'Marcus', age: 30, bio: 'Live music and late dinners.', gender: 'man', avatar: 5, interests: ['Live music', 'Cooking'], lookingFor: 'dates', photoUrls: [], }, lastMessage: 'Friday night jazz', lastMessageAt: isoTime(-18 * 60 * 1000), lastMessageType: 'share', unread: 1, }, ] let flareLikes = [{ ...flareSuggestions[0], superLiked: true }] let flareLastSwipe = null const flareMessages = { 'flare-match-demo-0000-0000-0000000001': [ { id: 'flare-message-1', direction: 'sent', body: 'Have you tried the little jazz bar in Vinewood?', createdAt: isoTime(-55 * 60 * 1000), mediaDurationMs: null, mediaUrl: null, messageType: 'text', }, { id: 'flare-message-2', direction: 'received', body: 'That place sounds perfect. Friday?', createdAt: isoTime(-38 * 60 * 1000), mediaDurationMs: null, mediaUrl: null, messageType: 'text', }, { id: 'flare-message-3', direction: 'received', body: 'Friday night jazz', createdAt: isoTime(-18 * 60 * 1000), mediaDurationMs: null, mediaUrl: null, messageType: 'share', sharePayload: { appId: 'music', copyText: 'A late-night playlist for the drive to Vinewood.', id: 'music-playlist-friday-jazz', imageUrl: 'https://picsum.photos/seed/easyshare-jazz/720/720', kind: 'playlist', link: 'skyphone://music/playlist/music-playlist-friday-jazz', subtitle: '12 tracks · 48 min', title: 'Friday night jazz', }, }, ], } function flareBootstrap() { return { profile: flareProfile, suggestions: flareProfile.discoverable ? flareSuggestions : [], likes: flareLikes, matches: flareMatches, } } app.post('/api/:endpoint', async (request, response, next) => { console.log(`[NUI] ${request.params.endpoint}`, request.body) const endpoint = request.params.endpoint if (endpoint === 'music:bootstrap') { response.json({ success: true, data: musicBootstrap() }) return } if (endpoint === 'music:add-youtube') { const value = String(request.body.url ?? '') const customTitle = String(request.body.title ?? '').trim() const customArtist = String(request.body.artist ?? '').trim() const videoId = value.match(/[?&]v=([\w-]{11})/)?.[1] ?? value.match(/youtu\.be\/([\w-]{11})/)?.[1] if (!videoId) { response.json({ success: false, error: 'invalid_youtube_url' }) return } if (customTitle.length > 160 || customArtist.length > 120) { response.json({ success: false, error: 'invalid_song_metadata' }) return } if (musicYoutubeTracks.some((track) => track.videoId === videoId)) { response.json({ success: false, error: 'song_exists' }) return } const metadata = !customTitle || !customArtist ? await fetchYoutubeMetadata(videoId) : null musicYoutubeTracks.unshift({ id: `music-youtube-${musicSequence++}`, source: 'youtube', videoId, title: customTitle || metadata?.title || `YouTube ${videoId}`, artist: customArtist || metadata?.artist || 'YouTube', artwork: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`, createdAt: Date.now(), }) response.json({ success: true, data: musicBootstrap() }) return } if (endpoint === 'music:remove-youtube') { musicYoutubeTracks = musicYoutubeTracks.filter( (track) => track.id !== request.body.id, ) musicPlaylists.forEach((playlist) => { playlist.entries = playlist.entries.filter( (entry) => !(entry.source === 'youtube' && entry.songId === request.body.id), ) }) response.json({ success: true, data: musicBootstrap() }) return } if (endpoint === 'music:create-playlist') { const name = String(request.body.name ?? '').trim() if (!name) { response.json({ success: false, error: 'invalid_playlist' }) return } musicPlaylists.unshift({ id: `music-playlist-${musicSequence++}`, name, createdAt: Date.now(), entries: [], }) response.json({ success: true, data: musicBootstrap() }) return } if (endpoint === 'music:rename-playlist') { const playlist = musicPlaylists.find((item) => item.id === request.body.id) if (!playlist) { response.json({ success: false, error: 'playlist_not_found' }) return } playlist.name = String(request.body.name ?? '').trim() response.json({ success: true, data: musicBootstrap() }) return } if (endpoint === 'music:delete-playlist') { musicPlaylists = musicPlaylists.filter( (playlist) => playlist.id !== request.body.id, ) response.json({ success: true, data: musicBootstrap() }) return } if (endpoint === 'music:add-to-playlist') { const playlist = musicPlaylists.find( (item) => item.id === request.body.playlistId, ) if (!playlist) { response.json({ success: false, error: 'playlist_not_found' }) return } if ( playlist.entries.some( (entry) => entry.source === request.body.source && entry.songId === request.body.songId, ) ) { response.json({ success: false, error: 'song_already_in_playlist', }) return } playlist.entries.push({ source: request.body.source, songId: request.body.songId, }) response.json({ success: true, data: musicBootstrap() }) return } if (endpoint === 'music:remove-from-playlist') { const playlist = musicPlaylists.find( (item) => item.id === request.body.playlistId, ) if (playlist) { playlist.entries = playlist.entries.filter( (entry) => entry.source !== request.body.source || entry.songId !== request.body.songId, ) } response.json({ success: true, data: musicBootstrap() }) return } next() }) const featherProfiles = [ { id: 1, handle: 'alexmorgan', display_name: 'Alex Morgan', bio: 'City stories, night drives and good coffee.', avatar_url: 'https://picsum.photos/seed/feather-alex/160/160', verified: false, is_owner: true, is_following: false, followers: 128, following: 84, post_count: 2, }, { id: 2, handle: 'lsdaily', display_name: 'Los Santos Daily', bio: 'Independent updates from across the city.', avatar_url: 'https://picsum.photos/seed/feather-daily/160/160', verified: true, is_owner: false, is_following: true, followers: 12400, following: 92, post_count: 842, }, { id: 3, handle: 'miasantos', display_name: 'Mia Santos', bio: 'Music, art and the west side.', avatar_url: 'https://picsum.photos/seed/feather-mia/160/160', verified: false, is_owner: false, is_following: false, followers: 834, following: 310, post_count: 67, }, { id: 4, handle: 'bennysmotorworks', display_name: "Benny's Motor Works", bio: 'Custom builds, workshop stories and open appointments.', avatar_url: 'https://picsum.photos/seed/feather-bennys/160/160', verified: true, is_owner: false, is_following: true, followers: 4821, following: 146, post_count: 318, }, { id: 5, handle: 'lspd', display_name: 'Los Santos Police Department', bio: 'Official public safety updates for Los Santos.', avatar_url: 'https://picsum.photos/seed/feather-lspd/160/160', verified: true, is_owner: false, is_following: false, followers: 31700, following: 41, post_count: 1294, }, { id: 6, handle: 'nightshiftls', display_name: 'Night Shift LS', bio: 'Neon, street photography and stories after midnight.', avatar_url: 'https://picsum.photos/seed/feather-night/160/160', verified: false, is_owner: false, is_following: false, followers: 2140, following: 502, post_count: 154, }, { id: 7, handle: 'downtowneats', display_name: 'Downtown Eats', bio: 'Finding the best food trucks, diners and late-night menus.', avatar_url: 'https://picsum.photos/seed/feather-food/160/160', verified: false, is_owner: false, is_following: true, followers: 7602, following: 288, post_count: 604, }, { id: 8, handle: 'vinewoodsocial', display_name: 'Vinewood Social', bio: 'Events, premieres and everything happening in Vinewood.', avatar_url: 'https://picsum.photos/seed/feather-vinewood/160/160', verified: true, is_owner: false, is_following: false, followers: 18900, following: 73, post_count: 911, }, ] let featherFollowingIds = [2, 4, 7] let featherFollowerIds = [3, 6, 8] let featherBlockedProfileIds = [] const featherReports = [] let featherPosts = [ { id: 'feather-post-1', profile_id: 2, handle: 'lsdaily', display_name: 'Los Santos Daily', verified: true, avatar_url: featherProfiles[1].avatar_url, body: 'Golden hour over Downtown. Traffic is building near Legion Square — take the eastern route if you can.', created_at: Date.now() - 12 * 60 * 1000, reply_to_id: null, quote_id: null, is_owner: false, is_following: true, is_liked: true, is_bookmarked: false, like_count: 241, reply_count: 18, media: [ { id: 901, media_type: 'photo', url: 'https://picsum.photos/seed/feather-city/900/600', }, ], }, { id: 'feather-post-2', profile_id: 3, handle: 'miasantos', display_name: 'Mia Santos', verified: false, avatar_url: featherProfiles[2].avatar_url, body: 'Small reminder: the best corners of this city are the ones you find by accident. What is your favorite late-night spot?', created_at: Date.now() - 48 * 60 * 1000, reply_to_id: null, quote_id: null, is_owner: false, is_following: false, is_liked: false, is_bookmarked: true, like_count: 73, reply_count: 9, media: [], }, { id: 'feather-post-3', profile_id: 1, handle: 'alexmorgan', display_name: 'Alex Morgan', verified: false, avatar_url: featherProfiles[0].avatar_url, body: 'First feather. The city feels unusually calm tonight.', created_at: Date.now() - 3 * 60 * 60 * 1000, reply_to_id: null, quote_id: null, is_owner: true, is_following: false, is_liked: false, is_bookmarked: false, like_count: 12, reply_count: 2, media: [], }, { id: 'feather-post-4', profile_id: 4, handle: 'bennysmotorworks', display_name: "Benny's Motor Works", verified: true, avatar_url: featherProfiles[3].avatar_url, body: 'The Elegy is finally ready. New paint, cleaner fitment and a setup made for city nights. #Cars #Bennys', created_at: Date.now() - 5 * 60 * 60 * 1000, reply_to_id: null, quote_id: null, is_owner: false, is_following: true, is_liked: false, is_bookmarked: false, like_count: 812, reply_count: 46, media: [ { id: 904, media_type: 'photo', url: 'https://picsum.photos/seed/feather-car/900/600', }, { id: 905, media_type: 'photo', url: 'https://picsum.photos/seed/feather-garage/900/600', }, ], }, { id: 'feather-post-5', profile_id: 5, handle: 'lspd', display_name: 'Los Santos Police Department', verified: true, avatar_url: featherProfiles[4].avatar_url, body: 'Road closure: Power Street between Vespucci Boulevard and San Andreas Avenue. Please use an alternate route. #LosSantos #Traffic', created_at: Date.now() - 7 * 60 * 60 * 1000, reply_to_id: null, quote_id: null, is_owner: false, is_following: false, is_liked: false, is_bookmarked: true, like_count: 391, reply_count: 61, media: [], }, { id: 'feather-post-6', profile_id: 6, handle: 'nightshiftls', display_name: 'Night Shift LS', verified: false, avatar_url: featherProfiles[5].avatar_url, body: 'Three quiet frames from tonight. Downtown really changes after the rain. #Photography #NightLife', created_at: Date.now() - 11 * 60 * 60 * 1000, reply_to_id: null, quote_id: null, is_owner: false, is_following: false, is_liked: true, is_bookmarked: true, like_count: 1540, reply_count: 83, media: [ { id: 906, media_type: 'photo', url: 'https://picsum.photos/seed/feather-neon/900/600', }, { id: 907, media_type: 'photo', url: 'https://picsum.photos/seed/feather-rain/900/600', }, { id: 908, media_type: 'photo', url: 'https://picsum.photos/seed/feather-downtown/900/600', }, ], }, { id: 'feather-post-7', profile_id: 7, handle: 'downtowneats', display_name: 'Downtown Eats', verified: false, avatar_url: featherProfiles[6].avatar_url, body: 'New taco truck at Legion Square until 2 AM. The hot sauce is not joking around. #Food #LosSantos', created_at: Date.now() - 16 * 60 * 60 * 1000, reply_to_id: null, quote_id: 'feather-post-2', is_owner: false, is_following: true, is_liked: false, is_bookmarked: false, like_count: 628, reply_count: 37, media: [ { id: 909, media_type: 'photo', url: 'https://picsum.photos/seed/feather-tacos/900/600', }, ], }, { id: 'feather-post-8', profile_id: 8, handle: 'vinewoodsocial', display_name: 'Vinewood Social', verified: true, avatar_url: featherProfiles[7].avatar_url, body: 'Outdoor cinema returns to the Vinewood Bowl this weekend. Gates open at 8 PM. #Events #Vinewood', created_at: Date.now() - 22 * 60 * 60 * 1000, reply_to_id: null, quote_id: null, is_owner: false, is_following: false, is_liked: false, is_bookmarked: false, like_count: 2203, reply_count: 104, media: [ { id: 910, media_type: 'photo', url: 'https://picsum.photos/seed/feather-cinema/900/600', }, ], }, { id: 'feather-post-9', profile_id: 1, handle: 'alexmorgan', display_name: 'Alex Morgan', verified: false, avatar_url: featherProfiles[0].avatar_url, body: 'Traffic update for everyone heading downtown later. Drive safe.', created_at: Date.now() - 26 * 60 * 60 * 1000, reply_to_id: null, quote_id: 'feather-post-5', is_owner: true, is_following: false, is_liked: true, is_bookmarked: false, like_count: 34, reply_count: 4, media: [], }, { id: 'feather-reply-1', profile_id: 1, handle: 'alexmorgan', display_name: 'Alex Morgan', verified: false, avatar_url: featherProfiles[0].avatar_url, body: 'Thanks for the warning. The eastern route was much faster.', created_at: Date.now() - 8 * 60 * 1000, reply_to_id: 'feather-post-1', quote_id: null, is_owner: true, is_following: false, is_liked: false, is_bookmarked: false, like_count: 5, reply_count: 0, media: [], }, { id: 'feather-reply-2', profile_id: 3, handle: 'miasantos', display_name: 'Mia Santos', verified: false, avatar_url: featherProfiles[2].avatar_url, body: 'That skyline photo is perfect.', created_at: Date.now() - 5 * 60 * 1000, reply_to_id: 'feather-post-1', quote_id: null, is_owner: false, is_following: false, is_liked: true, is_bookmarked: false, like_count: 12, reply_count: 1, media: [], }, ] const featherActivities = [ { id: 'feather-activity-1', kind: 'like', post_id: 'feather-post-3', profile_id: 3, handle: 'miasantos', display_name: 'Mia Santos', verified: false, avatar_url: featherProfiles[2].avatar_url, read: false, created_at: Date.now() - 15 * 60 * 1000, }, { id: 'feather-activity-2', kind: 'follow', post_id: null, profile_id: 2, handle: 'lsdaily', display_name: 'Los Santos Daily', verified: true, avatar_url: featherProfiles[1].avatar_url, read: true, created_at: Date.now() - 90 * 60 * 1000, }, { id: 'feather-activity-3', kind: 'reply', post_id: 'feather-post-3', profile_id: 4, handle: 'bennysmotorworks', display_name: "Benny's Motor Works", verified: true, avatar_url: featherProfiles[3].avatar_url, read: false, created_at: Date.now() - 2 * 60 * 60 * 1000, }, { id: 'feather-activity-5', kind: 'quote', post_id: 'feather-post-3', profile_id: 6, handle: 'nightshiftls', display_name: 'Night Shift LS', verified: false, avatar_url: featherProfiles[5].avatar_url, read: false, created_at: Date.now() - 9 * 60 * 60 * 1000, }, ] const featherTopics = [ { tag: '#LosSantos', count: 8421 }, { tag: '#Cars', count: 5398 }, { tag: '#News', count: 4127 }, { tag: '#NightLife', count: 2944 }, { tag: '#Food', count: 2186 }, { tag: '#Vinewood', count: 1773 }, { tag: '#Photography', count: 1328 }, { tag: '#Events', count: 986 }, ] let featherOnboarded = true const easyShareTargets = [ { distance: 2.4, id: 41, name: 'Mia Santos' }, { distance: 7.8, id: 72, name: 'Noah Walker' }, { distance: 14.6, id: 105, name: 'Jamie Rivera' }, ] const easyShareHistory = [ { createdAt: Date.now() - 2 * 60 * 1000, direction: 'incoming', id: 'easyshare-incoming-pending', otherName: 'Mia Santos', payload: { appId: 'notes', copyText: 'Meet at Mission Row at 20:30.', id: 'note-easyshare-meeting', kind: 'note', title: 'Mission Row meeting', }, progress: 0, status: 'pending', }, { createdAt: Date.now() - 8 * 60 * 1000, direction: 'outgoing', id: 'easyshare-outgoing-transferring', otherName: 'Noah Walker', payload: { appId: 'gallery', copyText: 'Sunset over Los Santos.', id: 3, imageUrl: 'https://images.unsplash.com/photo-1519501025264-65ba15a82390?w=900', kind: 'photo', title: 'Los Santos sunset', }, progress: 58, status: 'transferring', }, { createdAt: Date.now() - 22 * 60 * 1000, direction: 'incoming', id: 'easyshare-completed', otherName: 'Jamie Rivera', payload: { appId: 'map', copyText: 'Legion Square', kind: 'location', link: 'https://maps.sky/legion-square', title: 'Legion Square', }, progress: 100, status: 'completed', }, { createdAt: Date.now() - 48 * 60 * 1000, direction: 'outgoing', id: 'easyshare-accepted', otherName: 'Mia Santos', payload: { appId: 'music', copyText: 'Night Drive by Neon Coast', kind: 'track', title: 'Night Drive', }, progress: 15, status: 'accepted', }, { createdAt: Date.now() - 2 * 60 * 60 * 1000, direction: 'outgoing', id: 'easyshare-declined', otherName: 'Noah Walker', payload: { appId: 'feather', copyText: 'Road closure near Alta Street.', id: 'feather-post-3', kind: 'post', title: 'Road closure', }, progress: 0, status: 'declined', }, { createdAt: Date.now() - 4 * 60 * 60 * 1000, direction: 'outgoing', id: 'easyshare-cancelled', otherName: 'Jamie Rivera', payload: { appId: 'phone', copyText: 'Mia Santos\n5550142', kind: 'contact', title: 'Mia Santos', }, progress: 31, status: 'cancelled', }, { createdAt: Date.now() - 7 * 60 * 60 * 1000, direction: 'incoming', id: 'easyshare-expired', otherName: 'Mia Santos', payload: { appId: 'picstagram', copyText: 'New post from @mia.santos', id: 'picstagram-post-1', kind: 'post', title: 'Vespucci evening', }, progress: 0, status: 'expired', }, { createdAt: Date.now() - 24 * 60 * 60 * 1000, direction: 'outgoing', id: 'easyshare-failed', otherName: 'Noah Walker', payload: { appId: 'gallery', copyText: 'Vehicle walkaround video.', id: 7, kind: 'video', title: 'Vehicle walkaround', }, progress: 73, status: 'failed', }, ] let easyShareVisibility = 'everyone' function easyShareHistoryForScenario(testScenario) { if (testScenario === 'easyshare-empty') return [] if (testScenario === 'easyshare-incoming') { return easyShareHistory.filter( (transfer) => transfer.id === 'easyshare-incoming-pending', ) } if (testScenario === 'easyshare-history') { return easyShareHistory.filter( (transfer) => !['pending', 'transferring'].includes(transfer.status), ) } return easyShareHistory } app.post('/api/:endpoint', (request, response) => { console.log(`[NUI] ${request.params.endpoint}`, request.body) const endpoint = request.params.endpoint const testScenario = String(request.body._testScenario ?? '') if (endpoint === 'crewlink:bootstrap') { response.json({ success: true, data: crewLinkBootstrap(testScenario) }) return } if (endpoint === 'crewlink:create-profile') { const username = String(request.body.username ?? '').trim() if (!/^[A-Za-z0-9][A-Za-z0-9._]{1,18}[A-Za-z0-9]$/.test(username)) { response.json({ success: false, error: 'invalid_username' }) return } crewLinkProfile = { activeGroupId: null, id: `crewlink-profile-${Date.now()}`, mapVisible: true, overheadVisible: false, username, } response.json({ success: true, data: crewLinkBootstrap() }) return } if (endpoint === 'crewlink:update-profile') { crewLinkProfile = { ...crewLinkProfile, mapVisible: request.body.mapVisible === true, overheadVisible: request.body.overheadVisible === true, username: String(request.body.username ?? crewLinkProfile.username), } for (const members of Object.values(crewLinkMembers)) { const own = members.find((member) => member.id === crewLinkProfile.id) if (own) { own.mapVisible = crewLinkProfile.mapVisible own.overheadVisible = crewLinkProfile.overheadVisible own.username = crewLinkProfile.username } } response.json({ success: true, data: crewLinkBootstrap() }) return } if (endpoint === 'crewlink:create-group') { const name = String(request.body.name ?? '').trim() if (name.length < 3) { response.json({ success: false, error: 'invalid_group' }) return } const id = `crewlink-group-${Date.now()}` crewLinkGroups.unshift({ allowMemberPings: true, colour: request.body.colour ?? 'cyan', id, inviteCode: Math.random().toString(36).slice(2, 10).toUpperCase(), isOwner: true, memberCount: 1, name, overheadAllowed: true, role: 'owner', }) crewLinkMembers[id] = [ { coords: { x: -155.2, y: -1005.8, z: 28.4 }, id: crewLinkProfile.id, joinedAt: Date.now(), mapVisible: crewLinkProfile.mapVisible, online: true, overheadVisible: crewLinkProfile.overheadVisible, role: 'owner', source: 1, username: crewLinkProfile.username, }, ] crewLinkPings[id] = [] crewLinkProfile.activeGroupId = id response.json({ success: true, data: crewLinkBootstrap() }) return } if (endpoint === 'crewlink:update-group') { const group = crewLinkGroups.find((item) => item.id === request.body.groupId) if (!group) { response.json({ success: false, error: 'group_not_found' }) return } Object.assign(group, { allowMemberPings: request.body.allowMemberPings, colour: request.body.colour, name: request.body.name, overheadAllowed: request.body.overheadAllowed, }) response.json({ success: true }) return } if (endpoint === 'crewlink:delete-group') { crewLinkGroups = crewLinkGroups.filter((item) => item.id !== request.body.groupId) delete crewLinkMembers[request.body.groupId] delete crewLinkPings[request.body.groupId] crewLinkProfile.activeGroupId = crewLinkGroups[0]?.id ?? null response.json({ success: true }) return } if (endpoint === 'crewlink:set-active') { const group = crewLinkGroups.find((item) => item.id === request.body.groupId) if (!group) { response.json({ success: false, error: 'group_not_found' }) return } crewLinkProfile.activeGroupId = group.id response.json({ success: true, data: crewLinkBootstrap() }) return } if (endpoint === 'crewlink:join-code') { if (String(request.body.code).toUpperCase() !== 'SANDY247') { response.json({ success: false, error: 'invalid_code' }) return } const id = 'crewlink-group-sandy' if (!crewLinkGroups.some((group) => group.id === id)) { crewLinkGroups.push({ allowMemberPings: true, colour: 'orange', id, isOwner: false, memberCount: 3, name: 'Sandy Trails', overheadAllowed: false, role: 'member', }) crewLinkMembers[id] = [ { coords: { x: 1702.1, y: 3591.4, z: 35.4 }, id: crewLinkProfile.id, joinedAt: Date.now(), mapVisible: true, online: true, overheadVisible: false, role: 'member', username: crewLinkProfile.username, }, ] crewLinkPings[id] = [] } crewLinkProfile.activeGroupId = id response.json({ success: true, data: crewLinkBootstrap() }) return } if (endpoint === 'crewlink:rotate-code') { const group = crewLinkGroups.find((item) => item.id === request.body.groupId) if (group) group.inviteCode = 'FRESH247' response.json({ success: true, data: { inviteCode: 'FRESH247' } }) return } if (endpoint === 'crewlink:nearby') { response.json({ success: true, data: crewLinkNearby }) return } if (endpoint === 'crewlink:invite-nearby') { response.json({ success: true }) return } if (endpoint === 'crewlink:respond-invite') { const invitation = crewLinkInvitations.find( (item) => item.id === request.body.invitationId, ) crewLinkInvitations = crewLinkInvitations.filter( (item) => item.id !== request.body.invitationId, ) if (request.body.accepted && invitation) { crewLinkGroups.push({ allowMemberPings: true, colour: invitation.colour, id: invitation.groupId, isOwner: false, memberCount: 4, name: invitation.groupName, overheadAllowed: true, role: 'member', }) crewLinkMembers[invitation.groupId] = [] crewLinkPings[invitation.groupId] = [] crewLinkProfile.activeGroupId = invitation.groupId } response.json({ success: true, data: crewLinkBootstrap() }) return } if (endpoint === 'crewlink:update-member') { const members = crewLinkMembers[request.body.groupId] ?? [] const member = members.find((item) => item.id === request.body.profileId) if (member) member.role = request.body.role response.json({ success: true }) return } if (endpoint === 'crewlink:transfer-owner') { const group = crewLinkGroups.find((item) => item.id === request.body.groupId) const members = crewLinkMembers[request.body.groupId] ?? [] const current = members.find((item) => item.id === crewLinkProfile.id) const next = members.find((item) => item.id === request.body.profileId) if (group) { group.isOwner = false group.role = 'coordinator' delete group.inviteCode } if (current) current.role = 'coordinator' if (next) next.role = 'owner' response.json({ success: true }) return } if (endpoint === 'crewlink:remove-member') { const members = crewLinkMembers[request.body.groupId] ?? [] crewLinkMembers[request.body.groupId] = members.filter( (item) => item.id !== request.body.profileId, ) const group = crewLinkGroups.find((item) => item.id === request.body.groupId) if (group) group.memberCount = crewLinkMembers[request.body.groupId].length response.json({ success: true }) return } if (endpoint === 'crewlink:leave') { crewLinkGroups = crewLinkGroups.filter((item) => item.id !== request.body.groupId) crewLinkProfile.activeGroupId = crewLinkGroups[0]?.id ?? null response.json({ success: true, data: crewLinkBootstrap() }) return } if (endpoint === 'crewlink:create-ping') { const groupId = crewLinkProfile.activeGroupId const ping = { coords: request.body.coords ?? { x: -155.2, y: -1005.8, z: 28.4 }, createdAt: Date.now(), creatorProfileId: crewLinkProfile.id, creatorUsername: crewLinkProfile.username, expiresAt: Date.now() + 300000, id: `crewlink-ping-${Date.now()}`, label: request.body.label, type: request.body.type, } crewLinkPings[groupId] = [ping, ...(crewLinkPings[groupId] ?? [])] response.json({ success: true, data: ping }) return } if (endpoint === 'crewlink:remove-ping') { const groupId = crewLinkProfile.activeGroupId crewLinkPings[groupId] = (crewLinkPings[groupId] ?? []).filter( (item) => item.id !== request.body.pingId, ) response.json({ success: true }) return } if (endpoint === 'crewlink:live') { const groupId = crewLinkProfile.activeGroupId response.json({ success: true, data: { members: crewLinkMembers[groupId] ?? [], pings: crewLinkPings[groupId] ?? [], }, }) return } if (endpoint === 'feather:bootstrap') { const empty = testScenario === 'feather-empty' const visibleProfiles = featherProfiles.filter( (profile) => !featherBlockedProfileIds.includes(profile.id), ) response.json({ success: true, data: { onboarded: featherOnboarded, profile: featherProfiles[0], feed: { items: empty ? [] : featherPosts.filter( (post) => !post.reply_to_id && !featherBlockedProfileIds.includes(post.profile_id), ), offset: 0, hasMore: false, }, suggestions: empty ? [] : visibleProfiles.filter((profile) => !profile.is_owner), topics: empty ? [] : featherTopics, }, }) return } if (endpoint === 'feather:create-profile') { const displayName = String(request.body.displayName ?? '').trim() const handle = String(request.body.handle ?? '') .trim() .toLowerCase() const bio = String(request.body.bio ?? '').trim() if ( !displayName || displayName.length > 50 || !/^[a-z0-9][a-z0-9_]{1,28}[a-z0-9]$/.test(handle) || bio.length > 160 ) { response.json({ success: false, error: 'invalid_handle' }) return } if ( featherProfiles.some( (profile) => profile.id !== 1 && profile.handle === handle, ) ) { response.json({ success: false, error: 'handle_taken' }) return } featherProfiles[0].display_name = displayName featherProfiles[0].handle = handle featherProfiles[0].bio = bio featherOnboarded = true response.json({ success: true }) return } if (endpoint === 'feather:update-profile') { const displayName = String(request.body.displayName ?? '').trim() const bio = String(request.body.bio ?? '').trim() if (!displayName || displayName.length > 50 || bio.length > 160) { response.json({ success: false, error: 'invalid_request' }) return } featherProfiles[0].display_name = displayName featherProfiles[0].bio = bio featherPosts .filter((post) => post.profile_id === featherProfiles[0].id) .forEach((post) => { post.display_name = displayName }) response.json({ success: true }) return } if (endpoint === 'feather:feed') { const visiblePosts = featherPosts.filter( (post) => !featherBlockedProfileIds.includes(post.profile_id), ) const items = request.body.mode === 'following' ? visiblePosts.filter( (post) => !post.reply_to_id && (post.is_following || post.is_owner), ) : visiblePosts.filter((post) => !post.reply_to_id) response.json({ success: true, data: { items, offset: 0, hasMore: false } }) return } if (endpoint === 'feather:explore') { const search = String(request.body.search ?? '') .trim() .toLowerCase() const normalizedSearch = search.replace(/^#/, '') response.json({ success: true, data: { posts: featherPosts.filter((post) => { if (post.reply_to_id) return false if (featherBlockedProfileIds.includes(post.profile_id)) return false if (!normalizedSearch) return true return `${post.body} ${post.display_name} ${post.handle}` .toLowerCase() .includes(normalizedSearch) }), profiles: featherProfiles .slice(1) .filter((profile) => !featherBlockedProfileIds.includes(profile.id)) .filter((profile) => normalizedSearch ? `${profile.display_name} ${profile.handle}` .toLowerCase() .includes(normalizedSearch) : true, ), topics: featherTopics.filter((topic) => normalizedSearch ? topic.tag.toLowerCase().includes(normalizedSearch) : true, ), }, }) return } if (endpoint === 'feather:network') { const search = String(request.body.search ?? '') .trim() .replace(/^@/, '') .toLowerCase() const profiles = featherProfiles.filter( (profile) => !profile.is_owner && !featherBlockedProfileIds.includes(profile.id), ) response.json({ success: true, data: { results: search ? profiles.filter((profile) => `${profile.display_name} ${profile.handle}` .toLowerCase() .includes(search), ) : [], suggestions: profiles.filter((profile) => !profile.is_following), }, }) return } if (endpoint === 'feather:create-post') { const body = String(request.body.body ?? '').trim() const mediaIds = Array.isArray(request.body.mediaIds) ? request.body.mediaIds.slice(0, 4) : [] const id = `feather-post-${Date.now()}` const selectedMedia = mediaIds .map((mediaId) => mockMedia.find((item) => item.id === Number(mediaId))) .filter((item) => item?.mediaType === 'photo') .map((item) => ({ id: item.id, media_type: 'photo', url: item.url, })) if ((!body && selectedMedia.length === 0) || body.length > 360) { response.json({ success: false, error: 'invalid_post' }) return } const replyToId = request.body.replyToId ? String(request.body.replyToId) : null const parent = replyToId ? featherPosts.find((post) => post.id === replyToId) : null if (replyToId && !parent) { response.json({ success: false, error: 'post_not_found' }) return } featherPosts.unshift({ id, profile_id: 1, handle: featherProfiles[0].handle, display_name: featherProfiles[0].display_name, verified: false, avatar_url: featherProfiles[0].avatar_url, body, created_at: Date.now(), reply_to_id: replyToId, quote_id: request.body.quoteId ?? null, is_owner: true, is_following: false, is_liked: false, is_bookmarked: false, like_count: 0, reply_count: 0, media: selectedMedia, }) if (!replyToId) featherProfiles[0].post_count += 1 if (parent) parent.reply_count += 1 response.json({ success: true, data: { id } }) return } if (endpoint === 'feather:react') { if (!['like', 'bookmark'].includes(request.body.kind)) { response.json({ success: false, error: 'invalid_request' }) return } const post = featherPosts.find((item) => item.id === request.body.id) if (!post) { response.json({ success: false, error: 'post_not_found' }) return } const key = request.body.kind === 'like' ? 'is_liked' : 'is_bookmarked' const active = request.body.active === true if (request.body.kind === 'like' && post.is_liked !== active) post.like_count = Math.max(0, post.like_count + (active ? 1 : -1)) post[key] = active response.json({ success: true }) return } if (endpoint === 'feather:follow') { const profileId = Number(request.body.profileId) const profile = featherProfiles.find((item) => item.id === profileId) if (!profile || profile.is_owner) { response.json({ success: false, error: 'profile_not_found' }) return } const wasFollowing = featherFollowingIds.includes(profileId) const active = request.body.active === true if (active) { if (!wasFollowing) featherFollowingIds.push(profileId) } else { featherFollowingIds = featherFollowingIds.filter((id) => id !== profileId) } if (wasFollowing !== active) { profile.followers = Math.max(0, profile.followers + (active ? 1 : -1)) featherProfiles[0].following = Math.max( 0, featherProfiles[0].following + (active ? 1 : -1), ) } featherProfiles .filter((item) => item.id === profileId) .forEach((item) => { item.is_following = active }) featherPosts .filter((item) => item.profile_id === profileId) .forEach((item) => { item.is_following = active }) response.json({ success: true }) return } if (endpoint === 'feather:connections') { const profileId = Number(request.body.profileId ?? 1) const mode = request.body.mode let ids = [] if (profileId === 1) { ids = mode === 'followers' ? featherFollowerIds : featherFollowingIds } else { ids = featherProfiles .filter((profile) => profile.id !== profileId && !profile.is_owner) .slice(0, 4) .map((profile) => profile.id) } response.json({ success: true, data: { items: ids .map((id) => featherProfiles.find((profile) => profile.id === id)) .filter(Boolean), }, }) return } if (endpoint === 'feather:remove-connection') { const targetId = Number(request.body.profileId) if (request.body.mode === 'followers') { featherFollowerIds = featherFollowerIds.filter((id) => id !== targetId) featherProfiles[0].followers = Math.max( 0, featherProfiles[0].followers - 1, ) } else { featherFollowingIds = featherFollowingIds.filter((id) => id !== targetId) featherProfiles[0].following = Math.max( 0, featherProfiles[0].following - 1, ) const target = featherProfiles.find((profile) => profile.id === targetId) if (target) target.is_following = false featherPosts .filter((post) => post.profile_id === targetId) .forEach((post) => { post.is_following = false }) } response.json({ success: true }) return } if (endpoint === 'feather:profile') { const profile = featherProfiles.find( (item) => item.id === Number(request.body.profileId), ) ?? featherProfiles[0] if (featherBlockedProfileIds.includes(profile.id)) { response.json({ success: false, error: 'profile_not_found' }) return } response.json({ success: true, data: { profile, posts: featherPosts.filter((post) => post.profile_id === profile.id), }, }) return } if (endpoint === 'feather:bookmarks') { response.json({ success: true, data: { items: featherPosts.filter((post) => post.is_bookmarked), offset: 0, hasMore: false, }, }) return } if (endpoint === 'feather:thread') { const post = featherPosts.find((item) => item.id === request.body.id) response.json( post ? { success: true, data: { post, replies: featherPosts.filter( (item) => item.reply_to_id === post.id && !featherBlockedProfileIds.includes(item.profile_id), ), }, } : { success: false, error: 'post_not_found' }, ) return } if (endpoint === 'feather:activities') { response.json({ success: true, data: testScenario === 'feather-empty' ? [] : featherActivities, }) return } if (endpoint === 'feather:mark-activities') { featherActivities.forEach((item) => { item.read = true }) response.json({ success: true }) return } if (endpoint === 'feather:delete') { const post = featherPosts.find((item) => item.id === request.body.id) if (!post || !post.is_owner) { response.json({ success: false, error: 'post_not_found' }) return } if (post.reply_to_id) { const parent = featherPosts.find((item) => item.id === post.reply_to_id) if (parent) parent.reply_count = Math.max(0, parent.reply_count - 1) } const deletedIds = new Set( featherPosts .filter((item) => item.id === post.id || item.reply_to_id === post.id) .map((item) => item.id), ) featherPosts = featherPosts.filter((item) => !deletedIds.has(item.id)) if (!post.reply_to_id) featherProfiles[0].post_count = Math.max( 0, featherProfiles[0].post_count - 1, ) response.json({ success: true }) return } if (endpoint === 'feather:block') { const profileId = Number(request.body.profileId) const profile = featherProfiles.find((item) => item.id === profileId) if (!profile || profile.is_owner) { response.json({ success: false, error: 'profile_not_found' }) return } if (!featherBlockedProfileIds.includes(profileId)) featherBlockedProfileIds.push(profileId) if (featherFollowingIds.includes(profileId)) { featherFollowingIds = featherFollowingIds.filter((id) => id !== profileId) featherProfiles[0].following = Math.max( 0, featherProfiles[0].following - 1, ) } profile.is_following = false response.json({ success: true }) return } if (endpoint === 'feather:report') { const post = featherPosts.find((item) => item.id === request.body.id) const reason = String(request.body.reason ?? '') if ( !post || !['spam', 'harassment', 'dangerous', 'illegal', 'other'].includes(reason) ) { response.json({ success: false, error: 'invalid_request' }) return } featherReports.push({ id: `feather-report-${Date.now()}`, post_id: post.id, reason, details: String(request.body.details ?? '').slice(0, 500), created_at: Date.now(), }) response.json({ success: true }) return } if (endpoint === 'radio:get') { response.json({ success: true, data: radioData }) return } if (endpoint === 'radio:connect') { radioData.connected = true radioData.frequency = Number(request.body.frequency) radioData.secondaryFrequency = Number(request.body.secondaryFrequency) || 0 radioData.members = [ { id: 12, joinTime: 248, name: 'Alex Morgan', rank: 'Sergeant', rankNumber: 3, }, { id: 27, joinTime: 42, name: 'Jamie Rivera', rank: 'Officer', rankNumber: 1, }, ] response.json({ success: true, data: radioData }) return } if (endpoint === 'radio:disconnect') { radioData.connected = false radioData.frequency = 0 radioData.secondaryFrequency = 0 radioData.members = [] response.json({ success: true }) return } if (endpoint === 'radio:set-volume') { radioData.volume = Math.max(0, Math.min(100, Number(request.body.volume))) response.json({ success: true, data: { volume: radioData.volume } }) return } if (endpoint === 'radio:save-settings') { radioData.settings[request.body.key] = request.body.value === true response.json({ success: true, data: radioData.settings }) return } if (endpoint === 'radio:save-badge') { radioData.badge = String(request.body.badge ?? '') response.json({ success: true, data: { badge: radioData.badge } }) return } if (endpoint === 'radio:save-display-name') { radioData.displayName = String(request.body.displayName ?? '') response.json({ success: true, data: { displayName: radioData.displayName }, }) return } const bankingOverview = () => ({ bank: mockBankBalance, cash: mockCashBalance, currency: '$', playerId: 42, playerName: 'Alex Morgan', transactions: mockBankTransactions, }) const billingInvoice = (invoice) => ({ ...invoice, canDispute: invoice.direction === 'inbox' && invoice.status === 'open', canPay: invoice.direction === 'inbox' && invoice.status === 'open', isOverdue: invoice.status === 'open' && invoice.dueAt !== null && invoice.dueAt < Date.now(), }) const billingOverview = (direction) => { const visible = mockBillingInvoices.filter( (invoice) => invoice.direction === direction, ) const open = visible.filter((invoice) => invoice.status === 'open') return { currency: '$', openCount: open.length, openTotal: open.reduce((total, invoice) => total + invoice.amount, 0), overdueCount: open.filter( (invoice) => invoice.dueAt && invoice.dueAt < Date.now(), ).length, supportsDisputes: true, supportsSent: true, unreadCount: mockBillingInvoices.filter( (invoice) => invoice.direction === 'inbox' && invoice.isUnread, ).length, urgentInvoices: open .sort((left, right) => left.dueAt - right.dueAt) .slice(0, 5) .map(billingInvoice), } } if (endpoint === 'flare:bootstrap') { response.json({ success: true, data: flareBootstrap() }) return } if (endpoint === 'flare:save-profile') { const requestedPhotoIds = request.body.photoMediaIds let photoUpdate = {} if (requestedPhotoIds !== undefined) { const validIds = Array.isArray(requestedPhotoIds) && requestedPhotoIds.length <= 6 && new Set(requestedPhotoIds).size === requestedPhotoIds.length && requestedPhotoIds.every((id) => Number.isInteger(id) && id > 0) const photos = validIds ? requestedPhotoIds.map((id) => mockMedia.find( (item) => item.id === id && item.mediaType === 'photo', ), ) : [] if (!validIds || photos.some((photo) => !photo)) { response.json({ success: false, error: 'invalid_profile_photos' }) return } photoUpdate = { photoMediaIds: [...requestedPhotoIds], photoUrls: photos.map((photo) => photo.url), } } flareProfile = { ...flareProfile, ...request.body, discoverable: flareProfile.discoverable, ...photoUpdate, } response.json({ success: true, data: flareBootstrap() }) return } if (endpoint === 'flare:set-discovery') { if (typeof request.body.enabled !== 'boolean') { response.json({ success: false, error: 'invalid_discovery' }) return } flareProfile.discoverable = request.body.enabled response.json({ success: true, data: flareBootstrap() }) return } if (endpoint === 'flare:swipe') { if (!flareProfile.discoverable) { response.json({ success: false, error: 'discovery_disabled' }) return } const target = flareSuggestions.find( (profile) => profile.id === Number(request.body.targetId), ) flareLastSwipe = target ? { choice: request.body.choice, profile: target } : null flareSuggestions = flareSuggestions.filter( (profile) => profile.id !== Number(request.body.targetId), ) flareLikes = flareLikes.filter( (profile) => profile.id !== Number(request.body.targetId), ) let match = null if ( target && ['like', 'superlike'].includes(request.body.choice) && target.id === 11 ) { match = { id: 'flare-match-maya-0000-0000-0000000001', profile: target, lastMessage: '', lastMessageAt: null, lastMessageType: null, unread: 0, } flareMatches.unshift(match) flareMessages[match.id] = [] } response.json({ success: true, data: { match } }) return } if (endpoint === 'flare:rewind') { if (!flareLastSwipe) { response.json({ success: false, error: 'nothing_to_rewind' }) return } const hasMatch = flareMatches.some( (match) => match.profile.id === flareLastSwipe.profile.id, ) if (hasMatch) { response.json({ success: false, error: 'cannot_rewind_match' }) return } flareSuggestions.unshift(flareLastSwipe.profile) flareLastSwipe = null response.json({ success: true, data: flareBootstrap() }) return } if (endpoint === 'flare:unmatch') { const index = flareMatches.findIndex( (match) => match.id === request.body.matchId, ) if (index < 0) { response.json({ success: false, error: 'match_not_found' }) return } flareMatches.splice(index, 1) delete flareMessages[request.body.matchId] response.json({ success: true, data: { matches: flareMatches } }) return } if (endpoint === 'flare:thread') { const match = flareMatches.find((item) => item.id === request.body.matchId) if (!match) { response.json({ success: false, error: 'match_not_found' }) return } match.unread = 0 response.json({ success: true, data: { messages: flareMessages[match.id] ?? [] }, }) return } if (endpoint === 'flare:send') { const match = flareMatches.find((item) => item.id === request.body.matchId) const messageType = request.body.messageType ?? 'text' const body = String(request.body.body ?? '').trim() const mediaUrl = String(request.body.mediaAssetId ?? '') if ( !match || (messageType === 'text' ? !body : messageType === 'share' ? !request.body.sharePayload : !['image', 'gif', 'video'].includes(messageType) || !mediaUrl) ) { response.json({ success: false, error: 'invalid_message' }) return } const message = { id: `flare-message-${Date.now()}`, direction: 'sent', body: messageType === 'share' ? body || request.body.sharePayload.title : messageType === 'text' ? body : '', createdAt: Date.now(), mediaDurationMs: request.body.mediaDurationMs ?? null, mediaUrl: messageType === 'text' ? null : mediaUrl, messageType, sharePayload: messageType === 'share' ? request.body.sharePayload : null, } flareMessages[match.id] ??= [] flareMessages[match.id].push(message) match.lastMessage = message.body match.lastMessageAt = message.createdAt match.lastMessageType = messageType response.json({ success: true, data: message }) return } if (endpoint === 'picstagram:bootstrap') { response.json({ success: true, data: picstagramAuthenticated ? { authenticated: true, feed: { hasMore: false, items: picstagramPosts, nextCursor: null, }, isAdmin: true, profile: picstagramProfiles[0], } : { authenticated: false, isAdmin: true }, }) return } if (endpoint === 'picstagram:login' || endpoint === 'picstagram:register') { picstagramAuthenticated = true response.json({ success: true }) return } if (endpoint === 'picstagram:logout') { picstagramAuthenticated = false response.json({ success: true }) return } if (endpoint === 'picstagram:feed') { response.json({ success: true, data: { hasMore: false, items: picstagramPosts, nextCursor: null }, }) return } if (endpoint === 'picstagram:explore') { response.json({ success: true, data: { hasMore: false, items: picstagramPosts.filter((post) => !post.is_owner), nextCursor: null, }, }) return } if (endpoint === 'picstagram:saved') { response.json({ success: true, data: { hasMore: false, items: picstagramPosts.filter((post) => post.is_saved), nextCursor: null, }, }) return } if (endpoint === 'picstagram:search') { const search = String(request.body.search ?? '').toLowerCase() response.json({ success: true, data: { posts: picstagramPosts.filter((post) => `${post.handle} ${post.display_name} ${post.caption} ${post.location}` .toLowerCase() .includes(search), ), profiles: picstagramProfiles.filter((profile) => `${profile.handle} ${profile.display_name}` .toLowerCase() .includes(search), ), }, }) return } if (endpoint === 'picstagram:profile') { const profile = picstagramProfiles.find( (item) => item.id === request.body.profileId || item.handle === String(request.body.handle ?? '').toLowerCase(), ) if (!profile) { response.json({ success: false, error: 'profile_not_found' }) return } response.json({ success: true, data: { posts: { hasMore: false, items: picstagramPosts.filter( (post) => post.profile_id === profile.id, ), nextCursor: null, }, profile, }, }) return } if (endpoint === 'picstagram:update-profile') { Object.assign(picstagramProfiles[0], { bio: request.body.bio, display_name: request.body.displayName, handle: request.body.handle, private: request.body.private, }) response.json({ success: true, data: picstagramProfiles[0] }) return } if (endpoint === 'picstagram:publish-post') { const media = request.body.mediaIds .map((id) => mockMedia.find((item) => item.id === id)) .filter((item) => item?.mediaType === 'photo') if (!media.length) { response.json({ success: false, error: 'invalid_media' }) return } const post = { avatar_url: picstagramProfiles[0].avatar_url, caption: request.body.caption, comment_count: 0, comments_enabled: request.body.commentsEnabled, created_at: Date.now(), display_name: picstagramProfiles[0].display_name, handle: picstagramProfiles[0].handle, id: `pic-post-${Date.now()}`, is_liked: false, is_owner: true, is_saved: false, like_count: 0, location: request.body.location, media: media.map((item, position) => ({ id: item.id, position, url: item.url, })), private: picstagramProfiles[0].private, profile_id: picstagramProfiles[0].id, verified: picstagramProfiles[0].verified, } picstagramPosts.unshift(post) response.json({ success: true, data: { id: post.id } }) return } if (endpoint === 'picstagram:update-post') { const post = picstagramPosts.find((item) => item.id === request.body.id) if (post) Object.assign(post, { caption: request.body.caption, comments_enabled: request.body.commentsEnabled, location: request.body.location, }) response.json({ success: Boolean(post), error: post ? undefined : 'post_not_found', }) return } if (endpoint === 'picstagram:set-post-status') { if (request.body.status !== 'published') picstagramPosts = picstagramPosts.filter( (post) => post.id !== request.body.id, ) response.json({ success: true }) return } if (endpoint === 'picstagram:react') { const post = picstagramPosts.find((item) => item.id === request.body.id) if (post) { const key = request.body.kind === 'like' ? 'is_liked' : 'is_saved' const changed = post[key] !== request.body.active post[key] = request.body.active if (changed && request.body.kind === 'like') post.like_count += request.body.active ? 1 : -1 } response.json({ success: Boolean(post), error: post ? undefined : 'post_not_found', }) return } if (endpoint === 'picstagram:follow') { const profile = picstagramProfiles.find( (item) => item.id === request.body.profileId, ) if (!profile) { response.json({ success: false, error: 'profile_not_found' }) return } profile.follow_status = request.body.active ? profile.private ? 'pending' : 'accepted' : null profile.is_following = profile.follow_status === 'accepted' profile.is_requested = profile.follow_status === 'pending' profile.locked = profile.private && !profile.is_following response.json({ success: true, data: { status: profile.follow_status } }) return } if (endpoint === 'picstagram:respond-follow') { picstagramActivities = picstagramActivities.filter( (activity) => activity.kind !== 'follow_request' || activity.profile_id !== request.body.profileId, ) response.json({ success: true }) return } if (endpoint === 'picstagram:comments') { response.json({ success: true, data: picstagramComments }) return } if (endpoint === 'picstagram:comment') { const comment = { avatar_url: picstagramProfiles[0].avatar_url, body: request.body.body, created_at: Date.now(), display_name: picstagramProfiles[0].display_name, handle: picstagramProfiles[0].handle, id: `pic-comment-${Date.now()}`, is_owner: true, parent_id: request.body.parentId ?? null, profile_id: picstagramProfiles[0].id, verified: picstagramProfiles[0].verified, } picstagramComments.push(comment) response.json({ success: true, data: { id: comment.id } }) return } if (endpoint === 'picstagram:remove-comment') { picstagramComments = picstagramComments.filter( (comment) => comment.id !== request.body.id, ) response.json({ success: true }) return } if (endpoint === 'picstagram:stories') { response.json({ success: true, data: picstagramStories }) return } if (endpoint === 'picstagram:publish-story') { const media = mockMedia.find((item) => item.id === request.body.mediaId) if (!media || media.mediaType !== 'photo') { response.json({ success: false, error: 'invalid_media' }) return } const story = { avatar_url: picstagramProfiles[0].avatar_url, body: request.body.body, created_at: Date.now(), display_name: picstagramProfiles[0].display_name, expires_at: Date.now() + 24 * 60 * 60 * 1000, handle: picstagramProfiles[0].handle, id: `pic-story-${Date.now()}`, is_owner: true, profile_id: picstagramProfiles[0].id, seen: true, url: media.url, verified: picstagramProfiles[0].verified, view_count: 0, } picstagramStories.unshift(story) response.json({ success: true, data: { id: story.id } }) return } if (endpoint === 'picstagram:view-story') { const story = picstagramStories.find((item) => item.id === request.body.id) if (story) story.seen = true response.json({ success: Boolean(story) }) return } if (endpoint === 'picstagram:story-viewers') { response.json({ success: true, data: [ { avatar_url: picstagramProfiles[1].avatar_url, created_at: Date.now() - 60000, display_name: picstagramProfiles[1].display_name, handle: picstagramProfiles[1].handle, id: picstagramProfiles[1].id, verified: true, }, ], }) return } if (endpoint === 'picstagram:remove-story') { picstagramStories = picstagramStories.filter( (story) => story.id !== request.body.id, ) response.json({ success: true }) return } if (endpoint === 'picstagram:activities') { response.json({ success: true, data: picstagramActivities }) return } if (endpoint === 'picstagram:mark-activities') { picstagramActivities.forEach((activity) => { activity.read_at = new Date().toISOString() }) response.json({ success: true }) return } if (endpoint === 'picstagram:block') { const profileId = request.body.profileId picstagramPosts = picstagramPosts.filter( (post) => post.profile_id !== profileId, ) picstagramComments = picstagramComments.filter( (comment) => comment.profile_id !== profileId, ) picstagramStories = picstagramStories.filter( (story) => story.profile_id !== profileId, ) picstagramActivities = picstagramActivities.filter( (activity) => activity.profile_id !== profileId, ) response.json({ success: true }) return } if (endpoint === 'picstagram:report') { picstagramReports.unshift({ created_at: Date.now(), details: request.body.details ?? '', id: `pic-report-${Date.now()}`, reason: request.body.reason, reporter_display_name: picstagramProfiles[0].display_name, reporter_handle: picstagramProfiles[0].handle, target_id: request.body.targetId, target_type: request.body.targetType, }) response.json({ success: true }) return } if (endpoint === 'picstagram:admin-reports') { response.json({ success: true, data: picstagramReports }) return } if (endpoint === 'picstagram:admin-resolve-report') { picstagramReports = picstagramReports.filter( (report) => report.id !== request.body.id, ) response.json({ success: true }) return } if (endpoint.startsWith('picstagram:')) { response.json({ success: true }) return } if (endpoint === 'fliptok:bootstrap') { if (!flipTokAuthenticated) { response.json({ success: true, data: { authenticated: false, musicTracks: flipTokMusicTracks, }, }) return } response.json({ success: true, data: { authenticated: true, profile: flipTokProfile, feed: { items: flipTokVideos, offset: 0, hasMore: false }, isAdmin: true, musicTracks: flipTokMusicTracks, }, }) return } if (endpoint === 'fliptok:login' || endpoint === 'fliptok:register') { flipTokAuthenticated = true response.json({ success: true }) return } if (endpoint === 'fliptok:logout') { flipTokAuthenticated = false response.json({ success: true }) return } if (endpoint === 'fliptok:feed') { const items = request.body.mode === 'following' ? flipTokVideos.filter((video) => video.is_following) : flipTokVideos response.json({ success: true, data: { items, offset: 0, hasMore: false } }) return } if (endpoint === 'fliptok:discover') { const search = String(request.body.search ?? '').toLowerCase() response.json({ success: true, data: flipTokVideos.filter((video) => `${video.handle} ${video.display_name} ${video.caption}` .toLowerCase() .includes(search), ), }) return } if (endpoint === 'fliptok:react') { const video = flipTokVideos.find((item) => item.id === request.body.id) if (video) { const key = request.body.kind === 'like' ? 'is_liked' : 'is_saved' video[key] = request.body.active } response.json({ success: true }) return } if (endpoint === 'fliptok:follow') { flipTokVideos .filter((video) => video.profile_id === request.body.profileId) .forEach((video) => { video.is_following = request.body.active }) response.json({ success: true }) return } if (endpoint === 'fliptok:share') { const video = flipTokVideos.find((item) => item.id === request.body.id) if (video) video.share_count += 1 response.json({ success: true }) return } if (endpoint === 'fliptok:comments') { response.json({ success: true, data: flipTokComments }) return } if (endpoint === 'fliptok:comment') { flipTokComments.unshift({ id: `comment-${Date.now()}`, profile_id: 1, handle: flipTokProfile.handle, display_name: flipTokProfile.display_name, verified: flipTokProfile.verified, body: request.body.body, created_at: Date.now(), }) response.json({ success: true }) return } if (endpoint === 'fliptok:activities') { response.json({ success: true, data: flipTokActivities }) return } if (endpoint === 'fliptok:profile') { const profileId = Number(request.body.profileId || 0) const handle = String(request.body.handle || '').toLowerCase() const own = profileId === flipTokProfile.id || handle === flipTokProfile.handle const videos = own ? flipTokVideos.filter((video) => video.profile_id === flipTokProfile.id) : flipTokVideos.filter((video) => profileId ? video.profile_id === profileId : video.handle === handle, ) const first = videos[0] if (!first && !own) { response.json({ success: false, error: 'profile_not_found' }) return } response.json({ success: true, data: { profile: own ? { ...flipTokProfile } : { id: first.profile_id, handle: first.handle, display_name: first.display_name, bio: 'Creator in Los Santos.', account_type: 'person', verified: first.verified, is_following: first.is_following, is_owner: false, followers: 12840, following: 91, video_count: videos.length, }, videos, }, }) return } if (endpoint === 'fliptok:block') { const profileId = Number(request.body.profileId) flipTokVideos = flipTokVideos.filter( (video) => video.profile_id !== profileId, ) flipTokComments = flipTokComments.filter( (comment) => comment.profile_id !== profileId, ) flipTokActivities = flipTokActivities.filter( (activity) => activity.profile_id !== profileId, ) response.json({ success: true }) return } if (endpoint === 'fliptok:admin-reports') { response.json({ success: true, data: flipTokReports }) return } if (endpoint === 'fliptok:admin-resolve-report') { const selected = flipTokReports.find( (report) => report.id === request.body.id, ) if (selected && request.body.action === 'remove') flipTokVideos = flipTokVideos.filter( (video) => video.id !== selected.video_id, ) flipTokReports = flipTokReports.filter((report) => request.body.action === 'remove' ? report.video_id !== selected?.video_id : report.id !== request.body.id, ) response.json({ success: true }) return } if (endpoint === 'fliptok:update-profile') { Object.assign(flipTokProfile, { handle: request.body.handle, display_name: request.body.displayName, bio: request.body.bio, account_type: request.body.accountType, }) response.json({ success: true, data: flipTokProfile }) return } if (endpoint === 'fliptok:publish') { const media = mockMedia.find( (item) => item.id === request.body.mediaId && item.mediaType === 'video', ) if (!media) { response.json({ success: false, error: 'invalid_media' }) return } flipTokVideos.unshift({ id: `fliptok-${Date.now()}`, profile_id: 1, handle: flipTokProfile.handle, display_name: flipTokProfile.display_name, verified: flipTokProfile.verified, caption: request.body.caption, location: request.body.location, trim_start_ms: request.body.trimStartMs || 0, trim_end_ms: request.body.trimEndMs || null, cover_time_ms: request.body.coverTimeMs || 0, original_volume: request.body.originalVolume ?? 100, music_volume: request.body.musicVolume || 0, music_track: request.body.musicTrack || '', music_title: flipTokMusicTracks.find((track) => track.id === request.body.musicTrack) ?.title || '', music_artist: flipTokMusicTracks.find((track) => track.id === request.body.musicTrack) ?.artist || '', music_url: flipTokMusicTracks.find((track) => track.id === request.body.musicTrack) ?.url || '', url: media.url, comments_enabled: request.body.commentsEnabled, is_liked: false, is_saved: false, is_following: false, is_owner: true, like_count: 0, comment_count: 0, view_count: 0, share_count: 0, created_at: Date.now(), }) response.json({ success: true, data: { id: flipTokVideos[0].id } }) return } if (endpoint.startsWith('fliptok:')) { response.json({ success: true }) return } if (endpoint === 'skyride:get-player-coords') { response.json({ success: true, data: { coords: { x: -265.1, y: -960.2, z: 31.2 } }, }) return } if (endpoint === 'skyride:bootstrap') { response.json({ success: true, data: skyRideBootstrap() }) return } if (endpoint === 'skyride:history') { response.json({ success: true, data: { items: skyRideHistory } }) return } if (endpoint === 'skyride:quote') { const pickup = request.body.pickup const destination = request.body.destination if (!pickup?.coords || !destination?.coords) { response.json({ success: false, error: 'invalid_location' }) return } const distanceMeters = Math.max( 750, Math.round( Math.hypot( Number(destination.coords.x) - Number(pickup.coords.x), Number(destination.coords.y) - Number(pickup.coords.y), ) * 1.25, ), ) const durationSeconds = Math.max(60, Math.round(distanceMeters / 12)) const distanceUnit = 'kilometer' const distance = distanceMeters / 1000 const expiresAt = unixTime(2 * 60) const definitions = [ { baseFare: 12, etaMinutes: 3, minimumFare: 15, pricePerKilometer: 18, pricePerMile: 29, pricePerMinute: 1, seats: 4, serviceClass: 'taxi', }, { baseFare: 16, etaMinutes: 5, minimumFare: 22, pricePerKilometer: 24, pricePerMile: 39, pricePerMinute: 2, seats: 4, serviceClass: 'comfort', }, { baseFare: 20, etaMinutes: 7, minimumFare: 28, pricePerKilometer: 30, pricePerMile: 48, pricePerMinute: 2, seats: 6, serviceClass: 'xl', }, { baseFare: 28, etaMinutes: 8, minimumFare: 38, pricePerKilometer: 40, pricePerMile: 64, pricePerMinute: 3, seats: 4, serviceClass: 'premium', }, ] const requestedCustomFare = request.body.customFare if ( requestedCustomFare !== undefined && (!definitions.some( (definition) => definition.serviceClass === requestedCustomFare?.serviceClass, ) || !Number.isInteger(requestedCustomFare?.price)) ) { response.json({ success: false, error: 'invalid_custom_fare' }) return } const options = definitions.map((definition) => { const pricePerDistanceUnit = distanceUnit === 'mile' ? definition.pricePerMile : definition.pricePerKilometer const calculatedPrice = Math.round( Math.max( definition.minimumFare, definition.baseFare + distance * pricePerDistanceUnit + (durationSeconds / 60) * definition.pricePerMinute, ), ) const minimumCustomPrice = Math.round( Math.max(5, definition.minimumFare, calculatedPrice * 0.5), ) const maximumCustomPrice = Math.round( Math.min(100000, calculatedPrice * 3), ) const usesCustomFare = requestedCustomFare?.serviceClass === definition.serviceClass const requestedPrice = Number(requestedCustomFare?.price) if ( usesCustomFare && (!Number.isInteger(requestedPrice) || requestedPrice < minimumCustomPrice || requestedPrice > maximumCustomPrice) ) { return null } const option = { available: true, calculatedPrice, currency: '$', etaMinutes: definition.etaMinutes, fareMode: usesCustomFare ? 'custom' : 'calculated', maximumCustomPrice, minimumCustomPrice, price: usesCustomFare ? requestedPrice : calculatedPrice, pricePerDistanceUnit, quoteId: 'skyride-quote-' + Date.now() + '-' + definition.serviceClass, seats: definition.seats, serviceClass: definition.serviceClass, } skyRideQuotes.set(option.quoteId, { destination, expiresAt, option, pickup, }) return option }) if (options.some((option) => option === null)) { response.json({ success: false, error: 'invalid_custom_fare' }) return } response.json({ success: true, data: { destination, distance, distanceMeters, distanceUnit, durationSeconds, expiresAt, options, pickup, }, }) return } if (endpoint === 'skyride:request') { const quote = skyRideQuotes.get(String(request.body.quoteId ?? '')) if (!quote) { response.json({ success: false, error: 'quote_not_found' }) return } if (quote.expiresAt <= unixTime()) { response.json({ success: false, error: 'quote_expired' }) return } if (skyRideActiveRide) { response.json({ success: false, error: 'active_ride_exists' }) return } const now = unixTime() const ride = { createdAt: now, currency: quote.option.currency, destination: quote.destination, driver: null, id: `skyride-ride-${skyRideSequence++}`, passenger: skyRidePassenger, pickup: quote.pickup, price: quote.option.price, serviceClass: quote.option.serviceClass, status: 'searching', updatedAt: now, } skyRideActiveRide = ride skyRideAvailableRequests = [ skyRideWithoutLiveContact(ride), ...skyRideAvailableRequests, ] response.json({ success: true, data: skyRideUpdate(['activeRide']), }) return } if (endpoint === 'skyride:set-driver-status') { if (typeof request.body.online !== 'boolean') { response.json({ success: false, error: 'invalid_driver_status' }) return } skyRideDriverOnline = request.body.online response.json({ success: true, data: skyRideUpdate(['driverOnline', 'availableRequests']), }) return } if (endpoint === 'skyride:accept') { if (!skyRideDriverOnline) { response.json({ success: false, error: 'driver_offline' }) return } const ride = skyRideAvailableRequests.find( (item) => item.id === request.body.rideId, ) if (!ride) { response.json({ success: false, error: 'ride_not_found' }) return } if (skyRideActiveRide && skyRideActiveRide.id !== ride.id) { response.json({ success: false, error: 'active_ride_exists' }) return } skyRideActiveRide = { ...ride, driver: skyRideDriver, status: 'accepted', updatedAt: unixTime(), } skyRideAvailableRequests = skyRideAvailableRequests.filter( (item) => item.id !== ride.id, ) response.json({ success: true, data: skyRideUpdate(['activeRide', 'availableRequests']), }) return } if ( endpoint === 'skyride:arrive' || endpoint === 'skyride:start' || endpoint === 'skyride:complete' ) { if (!skyRideActiveRide || skyRideActiveRide.id !== request.body.rideId) { response.json({ success: false, error: 'ride_not_found' }) return } const transition = { 'skyride:arrive': { from: ['accepted', 'driver_arriving'], to: 'arrived', }, 'skyride:start': { from: ['arrived'], to: 'in_progress' }, 'skyride:complete': { from: ['in_progress'], to: 'completed' }, }[endpoint] if (!transition.from.includes(skyRideActiveRide.status)) { response.json({ success: false, error: 'invalid_ride_status' }) return } const updatedRide = { ...skyRideActiveRide, ...(transition.to === 'completed' ? { finalPrice: skyRideActiveRide.price } : {}), status: transition.to, updatedAt: unixTime(), } if (transition.to === 'completed') { const passengerRide = updatedRide.passenger?.id === skyRideProfile.id skyRideProfile.completedRides += 1 if (!passengerRide) { skyRideProfile.earningsToday += Math.round( updatedRide.finalPrice * 0.85, ) } const historyRide = skyRideWithoutLiveContact(updatedRide) skyRideHistory = [historyRide, ...skyRideHistory] skyRidePendingRating = passengerRide ? historyRide : null skyRideActiveRide = null response.json({ success: true, data: skyRideUpdate([ 'activeRide', 'history', 'pendingRating', 'profile', ]), }) return } skyRideActiveRide = updatedRide response.json({ success: true, data: skyRideUpdate(['activeRide']), }) return } if (endpoint === 'skyride:cancel') { const ride = skyRideActiveRide?.id === request.body.rideId ? skyRideActiveRide : skyRideAvailableRequests.find( (item) => item.id === request.body.rideId, ) if (!ride) { response.json({ success: false, error: 'ride_not_found' }) return } const cancelledRide = { ...ride, status: 'cancelled', updatedAt: unixTime(), } skyRideAvailableRequests = skyRideAvailableRequests.filter( (item) => item.id !== ride.id, ) if (skyRideActiveRide?.id === ride.id) skyRideActiveRide = null skyRidePendingRating = null skyRideHistory = [ skyRideWithoutLiveContact(cancelledRide), ...skyRideHistory, ] response.json({ success: true, data: skyRideUpdate([ 'activeRide', 'availableRequests', 'history', 'pendingRating', ]), }) return } if (endpoint === 'skyride:rate') { const rating = Number(request.body.rating) const tip = Number(request.body.tip) if ( !skyRidePendingRating || skyRidePendingRating.id !== request.body.rideId ) { response.json({ success: false, error: 'ride_not_found' }) return } if (!Number.isInteger(rating) || rating < 1 || rating > 5) { response.json({ success: false, error: 'invalid_rating' }) return } if (!Number.isFinite(tip) || tip < 0 || tip > 10000) { response.json({ success: false, error: 'invalid_tip' }) return } skyRidePendingRating = null response.json({ success: true, data: skyRideUpdate(['history', 'pendingRating']), }) return } if (endpoint === 'banking:overview') { response.json({ success: true, data: bankingOverview() }) return } if (endpoint === 'billing:overview') { response.json({ success: true, data: billingOverview( request.body.direction === 'sent' ? 'sent' : 'inbox', ), }) return } if (endpoint === 'billing:list') { const direction = request.body.direction === 'sent' ? 'sent' : 'inbox' const filter = String(request.body.filter ?? 'all') const search = String(request.body.search ?? '').toLowerCase() const offset = Math.max(0, Number(request.body.offset) || 0) let invoices = mockBillingInvoices.filter( (invoice) => invoice.direction === direction, ) if (filter === 'open') invoices = invoices.filter((invoice) => invoice.status === 'open') if (filter === 'overdue') { invoices = invoices.filter( (invoice) => invoice.status === 'open' && invoice.dueAt < Date.now(), ) } if (filter === 'paid') invoices = invoices.filter((invoice) => invoice.status !== 'open') if (search) { invoices = invoices.filter((invoice) => `${invoice.title} ${invoice.issuerLabel} ${invoice.description}` .toLowerCase() .includes(search), ) } const page = invoices.slice(offset, offset + 30).map(billingInvoice) response.json({ success: true, data: { hasMore: offset + page.length < invoices.length, invoices: page, nextOffset: offset + page.length, }, }) return } if (endpoint === 'billing:detail') { const invoice = mockBillingInvoices.find( (item) => item.id === request.body.id, ) response.json( invoice ? { success: true, data: billingInvoice(invoice) } : { success: false, error: 'invoice_not_found' }, ) return } if (endpoint === 'billing:markRead') { const invoice = mockBillingInvoices.find( (item) => item.id === request.body.id, ) if (invoice) invoice.isUnread = false response.json({ success: true, data: { unreadCount: billingOverview('inbox').unreadCount }, }) return } if (endpoint === 'billing:pay') { const invoice = mockBillingInvoices.find( (item) => item.id === request.body.id, ) if ( !invoice || invoice.direction !== 'inbox' || invoice.status !== 'open' ) { response.json({ success: false, error: 'invoice_not_payable' }) return } if (mockBankBalance < invoice.amount) { response.json({ success: false, error: 'insufficient_funds' }) return } mockBankBalance -= invoice.amount invoice.status = 'paid' invoice.paidAt = Date.now() invoice.isUnread = false invoice.paymentReference = `mock-billing-${Date.now()}` response.json({ success: true, data: billingInvoice(invoice) }) return } if (endpoint === 'billing:dispute') { const invoice = mockBillingInvoices.find( (item) => item.id === request.body.id, ) if ( !invoice || invoice.direction !== 'inbox' || invoice.status !== 'open' ) { response.json({ success: false, error: 'dispute_unavailable' }) return } invoice.status = 'disputed' invoice.isUnread = false response.json({ success: true, data: billingInvoice(invoice) }) return } if (endpoint === 'garage:vehicles') { response.json({ success: true, data: { system: 'jg', valet: { account: 'bank', enabled: true, price: 750, vehicleTypes: { bike: true, boat: false, car: true, helicopter: false, plane: false, }, }, vehicles: mockGarageVehicles, }, }) return } if (endpoint === 'housing:overview') { response.json({ success: true, data: mockHousingOverview }) return } if (endpoint === 'housing:key-candidates') { response.json({ success: true, data: { candidates: [{ id: 27, name: 'Alex Morgan' }] }, }) return } if (endpoint === 'housing:command') { const property = mockHousingOverview.properties.find( (item) => item.id === request.body.propertyId, ) if (!property) { response.json({ success: false, error: 'property_not_found' }) return } if (request.body.action === 'toggle_lock') { property.locked = !property.locked } response.json({ success: true, data: { accepted: true } }) return } if (endpoint === 'garage:valet-state') { response.json({ success: true, data: mockGarageValet }) return } if (endpoint === 'garage:valet-request') { const vehicle = mockGarageVehicles.find( (item) => item.plate === request.body.plate, ) if (!vehicle || vehicle.status !== 'garaged') { response.json({ success: false, error: 'vehicle_not_garaged' }) return } if (!['car', 'bike'].includes(vehicle.kind)) { response.json({ success: false, error: 'valet_vehicle_type' }) return } mockGarageValet = { canCancel: true, cost: 750, distance: 1240, etaSeconds: 62, orderId: 'mock-valet-1', plate: vehicle.plate, status: 'en_route', vehicleName: vehicle.nickname || vehicle.name, } response.json({ success: true, data: mockGarageValet }) return } if (endpoint === 'garage:valet-cancel') { mockGarageValet = null response.json({ success: true, data: null }) return } if (endpoint === 'banking:transfer') { const amount = Number(request.body.amount) if (!Number.isSafeInteger(amount) || amount <= 0) { response.json({ success: false, error: 'invalid_request' }) return } if (mockBankBalance < amount) { response.json({ success: false, error: 'insufficient_funds' }) return } const kind = 'transfer_out' mockBankBalance -= amount mockBankTransactions.unshift({ amount, createdAt: Date.now(), id: nextBankTransactionId++, kind, label: kind === 'transfer_out' ? `Player #${request.body.target}` : '', reference: `mock-${Date.now()}`, }) response.json({ success: true, data: bankingOverview() }) return } if (endpoint === 'weather:get') { response.json({ success: true, data: { clock: { year: 2026, month: 8, day: 5, hour: 17, minute: 20 }, condition: 'partly_cloudy', nextCondition: 'rain', 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 === 'map:setWaypoint') { response.json({ success: true }) return } if (endpoint === 'map:markers') { response.json({ success: true, data: mockMapMarkers }) return } if (endpoint === 'map:create-marker') { const label = String(request.body.label ?? '').trim() const color = String(request.body.color ?? '') const coords = request.body.coords if (!label || label.length > 40 || !coords) { response.json({ success: false, error: 'invalid_marker' }) return } const marker = { color, coords: { x: Number(coords.x), y: Number(coords.y), z: Number(coords.z) || 0, }, id: `mock-map-marker-${Date.now()}`, label, } mockMapMarkers.push(marker) response.json({ success: true, data: marker }) return } if (endpoint === 'map:delete-marker') { const previousLength = mockMapMarkers.length mockMapMarkers = mockMapMarkers.filter( (marker) => marker.id !== request.body.id, ) response.json( mockMapMarkers.length === previousLength ? { success: false, error: 'marker_not_found' } : { success: true }, ) return } if (endpoint === 'darkchat:bootstrap') { response.json({ success: true, data: darkChatBootstrap() }) return } if (endpoint === 'darkchat:update-profile') { darkChatProfile.alias = String( request.body.alias ?? darkChatProfile.alias, ).trim() darkChatProfile.notificationMode = request.body.notificationMode ?? 'private' darkChatProfile.activityVisible = Boolean(request.body.activityVisible) response.json({ success: true, data: darkChatProfile }) return } if (endpoint === 'darkchat:start') { const identifier = String(request.body.identifier ?? '').toUpperCase() const peer = darkChatPeers.find((item) => item.darkId.toUpperCase() === identifier) ?? (identifier === 'DC-ECH0-77LM' ? darkChatPeers[1] : null) if (!peer) { response.json({ success: false, error: 'profile_not_found' }) return } let conversation = darkChatConversations.find( (item) => item.peer.id === peer.id, ) if (!conversation) { conversation = { id: `dc-conversation-${Date.now()}`, peer, disappearingSeconds: 0, notificationsEnabled: true, readReceipts: true, blockedByPeer: false, createdAt: new Date().toISOString().slice(0, 19).replace('T', ' '), } darkChatConversations.push(conversation) } response.json({ success: true, data: { conversationId: conversation.id } }) return } if (endpoint === 'darkchat:thread') { const conversation = darkChatConversations.find( (item) => item.id === request.body.conversationId, ) if (!conversation) { response.json({ success: false, error: 'conversation_not_found' }) return } const thread = darkChatMessages.filter( (message) => message.conversationId === conversation.id, ) for (const message of thread) { if (message.direction === 'received') message.readAt = message.readAt ?? new Date().toISOString() } response.json({ success: true, data: { conversation, messages: thread.map(({ mediaSecret, ...message }) => message), }, }) return } if (endpoint === 'darkchat:send') { const conversation = darkChatConversations.find( (item) => item.id === request.body.conversationId, ) if (!conversation || conversation.peer.blocked) { response.json({ success: false, error: conversation ? 'blocked' : 'conversation_not_found', }) return } const messageType = request.body.messageType ?? 'text' const body = String(request.body.body ?? '') if ( ((messageType === 'text' || messageType === 'emoji') && !body.trim()) || (messageType === 'share' && !request.body.sharePayload) ) { response.json({ success: false, error: 'invalid_message' }) return } const reply = darkChatMessages.find( (message) => message.id === request.body.replyToId, ) const message = { id: `dc-message-${Date.now()}`, conversationId: conversation.id, direction: 'sent', senderProfileId: darkChatProfile.id, messageType, body: messageType === 'share' ? body.trim() || request.body.sharePayload.title : body, mediaPayload: messageType === 'gif' ? request.body.mediaPayload : undefined, mediaSecret: messageType === 'voice' ? request.body.mediaPayload : undefined, mediaMime: request.body.mediaMime, mediaDurationMs: request.body.mediaDurationMs, mediaWaveform: request.body.mediaWaveform, replyToId: request.body.replyToId, replyBody: reply?.body, reactions: {}, sharePayload: messageType === 'share' ? request.body.sharePayload : null, createdAt: new Date().toISOString().slice(0, 19).replace('T', ' '), readAt: null, } darkChatMessages.push(message) const { mediaSecret, ...publicMessage } = message response.json({ success: true, data: publicMessage }) return } if (endpoint === 'darkchat:media') { const message = darkChatMessages.find( (item) => item.id === request.body.messageId && item.messageType === 'voice', ) response.json( message?.mediaSecret ? { success: true, data: { mime: message.mediaMime, payload: message.mediaSecret }, } : { success: false, error: 'message_not_found' }, ) return } if (endpoint === 'darkchat:react') { const message = darkChatMessages.find( (item) => item.id === request.body.messageId, ) if (message) { const current = message.reactions[String(darkChatProfile.id)] if (current === request.body.reaction) delete message.reactions[String(darkChatProfile.id)] else message.reactions[String(darkChatProfile.id)] = request.body.reaction } response.json({ success: Boolean(message), error: message ? undefined : 'message_not_found', }) return } if (endpoint === 'darkchat:message-action') { const index = darkChatMessages.findIndex( (item) => item.id === request.body.messageId, ) if (index >= 0 && request.body.action === 'delete_me') darkChatMessages.splice(index, 1) else if (index >= 0 && request.body.action === 'delete_all') { darkChatMessages[index].messageType = 'system' darkChatMessages[index].body = 'message_deleted' darkChatMessages[index].mediaPayload = undefined } response.json({ success: index >= 0 }) return } if (endpoint === 'darkchat:update-conversation') { const conversation = darkChatConversations.find( (item) => item.id === request.body.conversationId, ) if (conversation) { conversation.disappearingSeconds = Number( request.body.disappearingSeconds, ) conversation.notificationsEnabled = Boolean( request.body.notificationsEnabled, ) conversation.readReceipts = Boolean(request.body.readReceipts) darkChatMessages.push({ id: `dc-system-${Date.now()}`, conversationId: conversation.id, direction: 'received', messageType: 'system', body: `timer_changed:${conversation.disappearingSeconds}`, reactions: {}, createdAt: new Date().toISOString().slice(0, 19).replace('T', ' '), }) } response.json({ success: Boolean(conversation) }) return } if ( endpoint === 'darkchat:add-contact' || endpoint === 'darkchat:remove-contact' ) { const conversation = darkChatConversations.find( (item) => item.id === request.body.conversationId, ) if (conversation) { conversation.peer.isContact = endpoint === 'darkchat:add-contact' if (request.body.alias) conversation.peer.alias = String(request.body.alias) } response.json({ success: Boolean(conversation) }) return } if (endpoint === 'darkchat:block') { const conversation = darkChatConversations.find( (item) => item.id === request.body.conversationId, ) if (conversation) conversation.peer.blocked = Boolean(request.body.blocked) response.json({ success: Boolean(conversation) }) return } if (endpoint === 'darkchat:clear') { for (let index = darkChatMessages.length - 1; index >= 0; index -= 1) { if ( darkChatMessages[index].conversationId === request.body.conversationId ) darkChatMessages.splice(index, 1) } response.json({ success: true }) return } if (endpoint === 'darkchat:report') { response.json({ success: true }) return } if (endpoint === 'messages:conversations') { const grouped = new Map() for (const message of [...smsMessages].reverse()) { const phoneNumber = message.direction === 'sent' ? message.recipient_number : message.sender_number const conversation = grouped.get(phoneNumber) if (conversation) { if (message.direction === 'received' && !message.read_at) conversation.unread += 1 continue } grouped.set(phoneNumber, { lastMessage: message.body, lastMessageAt: message.created_at, lastMessageType: message.message_type, phoneNumber, unread: message.direction === 'received' && !message.read_at ? 1 : 0, }) } response.json({ success: true, data: [...grouped.values()] }) return } if (endpoint === 'easyshare:bootstrap') { const history = easyShareHistoryForScenario(testScenario) response.json({ success: true, data: { history, pending: history.filter((item) => ['pending', 'transferring'].includes(item.status), ), targets: testScenario === 'easyshare-empty' ? [] : easyShareTargets, visibility: easyShareVisibility, }, }) return } if (endpoint === 'easyshare:set-visibility') { easyShareVisibility = request.body.visibility response.json({ success: true, data: { visibility: easyShareVisibility } }) return } if (endpoint === 'easyshare:request') { const target = easyShareTargets.find( (candidate) => candidate.id === Number(request.body.targetId), ) const transfer = { createdAt: Date.now(), direction: 'outgoing', id: `easyshare-${Date.now()}`, otherName: target?.name ?? 'Unknown device', payload: request.body.payload, progress: target?.id === 72 ? 58 : 0, status: target?.id === 72 ? 'transferring' : 'pending', } easyShareHistory.unshift(transfer) response.json({ success: true, data: transfer }) return } if (endpoint === 'easyshare:respond' || endpoint === 'easyshare:cancel') { const transfer = easyShareHistory.find( (item) => item.id === request.body.id, ) if (!transfer) { response.json({ success: false, error: 'transfer_not_found' }) return } transfer.status = endpoint === 'easyshare:cancel' ? 'cancelled' : request.body.accepted ? 'completed' : 'declined' transfer.progress = transfer.status === 'completed' ? 100 : transfer.progress response.json({ success: true, data: transfer }) return } if (endpoint === 'messages:gifs') { const offset = Math.max(0, Number(request.body.offset ?? 0)) const pageSize = 6 const results = gifMocks .slice(offset, offset + pageSize) .map(([id, title]) => ({ height: 200, id, previewUrl: `https://media.giphy.com/media/${id}/200w.gif`, title, url: `https://media.giphy.com/media/${id}/giphy.gif`, width: 200, })) response.json({ success: true, data: { hasMore: offset + results.length < gifMocks.length, nextOffset: offset + results.length, results, }, }) return } if (endpoint === 'development:bootstrap') { if (testScenario === 'feather-onboarding') featherOnboarded = false else featherOnboarded = true response.json({ success: true, data: { account: testScenario === 'feather-login' ? null : linkedAccount, device: { data: deviceData, imei: '356938035643809', name: 'Personal iFruit Phone', sim: { id: 'development-sim', number: '5551234567', removable: true, registered: true, type: 'registered', }, }, notes: mockNotes, security: mockSecurity, token: 'development', }, }) return } if (endpoint === 'messages:thread') { const number = String(request.body.phoneNumber) const thread = smsMessages.filter( (message) => message.sender_number === number || message.recipient_number === number, ) for (const message of thread) { if (message.direction === 'received') message.read_at = message.read_at ?? '2026-08-06 13:06:00' } response.json({ success: true, data: thread.map(({ media_payload, ...message }) => ({ ...message, contact: message.message_type === 'contact' ? media_payload : null, share: message.message_type === 'share' ? media_payload : null, media_asset_id: ['image', 'gif', 'video'].includes(message.message_type) ? media_payload : null, })), }) return } if (endpoint === 'messages:media') { const message = smsMessages.find( (item) => item.id === request.body.id && item.message_type === 'voice', ) response.json( message ? { success: true, data: { mime: message.media_mime, payload: message.media_payload, }, } : { success: false, error: 'message_not_found' }, ) return } if (endpoint === 'messages:send') { const body = String(request.body.body ?? '').trim() const phoneNumber = String(request.body.phoneNumber ?? '') const messageType = request.body.messageType ?? 'text' const isAttachment = ['image', 'gif', 'video'].includes(messageType) const selectedContact = messageType === 'contact' ? contacts.find((contact) => contact.id === request.body.contactId) : null const requestedAttachmentId = String(request.body.mediaAssetId ?? '') const selectedMedia = /^\d+$/.test(requestedAttachmentId) ? mockMedia.find((item) => String(item.id) === requestedAttachmentId) : null const attachmentId = selectedMedia?.url ?? requestedAttachmentId if ( !phoneNumber || (messageType === 'text' && !body) || (messageType === 'voice' && !request.body.mediaPayload) || (messageType === 'contact' && !selectedContact) || (messageType === 'share' && !request.body.sharePayload) || (isAttachment && !attachmentAssets[messageType].has(attachmentId) && !attachmentId.startsWith('https://') && !attachmentId.startsWith('data:image/')) ) { response.json({ success: false, error: 'invalid_message' }) return } const message = { body: selectedContact?.name ?? (messageType === 'share' ? body || request.body.sharePayload.title : body), contact: selectedContact ? { avatar_url: selectedContact.avatar_url ?? null, name: selectedContact.name, organization: selectedContact.organization ?? null, phone_number: selectedContact.phone_number, } : null, created_at: new Date().toISOString().slice(0, 19).replace('T', ' '), direction: 'sent', id: `sms-${Date.now()}`, media_duration_ms: ['voice', 'video'].includes(messageType) ? (request.body.mediaDurationMs ?? null) : null, media_mime: messageType === 'voice' ? request.body.mediaMime : messageType === 'image' ? 'image/jpeg' : messageType === 'gif' ? 'image/gif' : messageType === 'video' ? 'video/mp4' : null, media_payload: messageType === 'voice' ? request.body.mediaPayload : selectedContact ? { avatar_url: selectedContact.avatar_url ?? null, name: selectedContact.name, organization: selectedContact.organization ?? null, phone_number: selectedContact.phone_number, } : messageType === 'share' ? request.body.sharePayload : isAttachment ? attachmentId : null, media_waveform: messageType === 'voice' ? request.body.mediaWaveform : null, message_type: messageType, media_asset_id: isAttachment ? attachmentId : null, read_at: null, recipient_number: phoneNumber, sender_number: '5551234567', share: messageType === 'share' ? request.body.sharePayload : null, } smsMessages.push(message) const { media_payload, ...publicMessage } = message response.json({ success: true, data: publicMessage }) return } if (endpoint === 'messages:delete') { const phoneNumbers = new Set( Array.isArray(request.body.phoneNumbers) ? request.body.phoneNumbers.map(String) : [], ) for (let index = smsMessages.length - 1; index >= 0; index -= 1) { const message = smsMessages[index] const otherNumber = message.direction === 'sent' ? message.recipient_number : message.sender_number if (phoneNumbers.has(otherNumber)) smsMessages.splice(index, 1) } response.json({ success: true }) return } if (endpoint === 'contacts:list') { response.json({ success: true, data: contacts }) return } if (endpoint === 'contacts:save') { const name = String(request.body.name ?? '').trim() const notes = String(request.body.notes ?? '').trim().slice(0, 500) const organization = String(request.body.organization ?? '').trim().slice(0, 80) const phoneNumber = String(request.body.phoneNumber ?? '').trim() const avatarMediaId = Number(request.body.avatarMediaId) || 0 const avatarMedia = avatarMediaId ? mockMedia.find( (item) => item.id === avatarMediaId && item.mediaType === 'photo', ) : null if (!name || !phoneNumber || (avatarMediaId && !avatarMedia)) { response.json({ success: false, error: 'invalid_contact' }) return } let contact = contacts.find((item) => item.id === request.body.id) if (contact) { contact.name = name contact.notes = notes || null contact.organization = organization || null contact.phone_number = phoneNumber contact.avatar_media_id = avatarMedia?.id ?? null contact.avatar_url = avatarMedia?.url ?? null contact.updated_at = new Date() .toISOString() .slice(0, 19) .replace('T', ' ') } else { const now = new Date().toISOString().slice(0, 19).replace('T', ' ') contact = { created_at: now, favorite: false, id: `contact-${contactSequence++}`, name, notes: notes || null, organization: organization || null, phone_number: phoneNumber, avatar_media_id: avatarMedia?.id ?? null, avatar_url: avatarMedia?.url ?? null, updated_at: now, } contacts.push(contact) } response.json({ success: true, data: contact }) return } if (endpoint === 'contacts:favorite') { const contact = contacts.find((item) => item.id === request.body.id) if (!contact || typeof request.body.favorite !== 'boolean') { response.json({ success: false, error: 'contact_not_found' }) return } contact.favorite = request.body.favorite contact.updated_at = new Date().toISOString().slice(0, 19).replace('T', ' ') response.json({ success: true, data: { favorite: contact.favorite, id: contact.id }, }) return } if (endpoint === 'contacts:delete') { const index = contacts.findIndex((item) => item.id === request.body.id) if (index >= 0) contacts.splice(index, 1) response.json({ success: true }) return } if (endpoint === 'media:config') { response.json({ success: true, data: { videoBitrateKbps: 1500 } }) return } if (endpoint === 'media:devCapture') { const mediaType = request.body.mediaType === 'video' ? 'video' : 'photo' const id = Math.max(0, ...mockMedia.map((item) => Number(item.id) || 0)) + 1 const fallbackVideo = mockMedia.find((item) => item.mediaType === 'video') const media = { createdAt: Date.now(), id, mediaType, url: mediaType === 'photo' ? `https://picsum.photos/seed/sky-camera-${id}/900/1600` : fallbackVideo?.url, } if (!media.url) { response.json({ success: false, error: 'unsupported' }) return } mockMedia.unshift(media) response.json({ success: true, data: media }) return } if (endpoint === 'gallery:list') { if (request.body.mockState === 'error') { response.json({ success: false, error: 'service_unavailable' }) return } if (request.body.mockState === 'empty') { response.json({ success: true, data: [] }) return } const filtered = request.body.mediaType ? mockMedia.filter((item) => item.mediaType === request.body.mediaType) : mockMedia const offset = Number(request.body.offset) || 0 const limit = Number(request.body.limit) || 30 response.json({ success: true, data: filtered.slice(offset, offset + limit), }) return } if ( endpoint === 'media:requestUpload' || endpoint === 'media:completeUpload' || endpoint === 'media:failUpload' || endpoint === 'media:cancelUpload' ) { response.json({ success: true }) return } if (endpoint === 'gallery:delete') { mockMedia = mockMedia.filter((item) => item.id !== Number(request.body.id)) response.json({ success: true }) 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`, id: 1, } 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 === 'security:unlock') { response.json( !mockSecurity.enabled || request.body.passcode === mockPasscode ? { success: true, data: { security: mockSecurity } } : { success: false, error: 'invalid_passcode' }, ) return } if (endpoint === 'security:set-passcode') { mockPasscode = String(request.body.passcode) mockSecurity = { enabled: true, length: mockPasscode.length, lockedUntil: 0, } response.json({ success: true, data: { security: mockSecurity } }) return } if (endpoint === 'security:change-passcode') { if (request.body.currentPasscode !== mockPasscode) { response.json({ success: false, error: 'invalid_passcode' }) return } mockPasscode = String(request.body.newPasscode) mockSecurity = { enabled: true, length: mockPasscode.length, lockedUntil: 0, } response.json({ success: true, data: { security: mockSecurity } }) return } if (endpoint === 'security:disable-passcode') { if (request.body.passcode !== mockPasscode) { response.json({ success: false, error: 'invalid_passcode' }) return } mockPasscode = '' mockSecurity = { enabled: false, length: null, lockedUntil: 0 } response.json({ success: true, data: { security: mockSecurity } }) return } if (endpoint === 'device:factory-reset') { authenticated = false linkedAccount = null mockNotes = [] mockMedia = [] calendarEvents = [] mockPasscode = '' mockSecurity = { enabled: false, length: null, lockedUntil: 0 } for (const key of Object.keys(deviceData)) delete deviceData[key] response.json({ success: true }) return } if (endpoint === 'calls:recents') { response.json({ success: true, data: recentCalls }) return } if (endpoint === 'calls:dial') { const phoneNumber = String(request.body.phoneNumber ?? '').replace(/\D/g, '') if (phoneNumber.length !== 10) { response.json({ success: false, error: 'invalid_number' }) return } if (!contacts.some((contact) => contact.phone_number === phoneNumber)) { response.json({ success: false, error: 'recipient_not_found' }) return } const id = `call-${Date.now()}` const startedAt = Date.now() recentCalls.unshift({ call_id: id, created_at: Date.now(), direction: 'outgoing', duration_seconds: 0, id: recentCalls.length + 1, other_number: phoneNumber, status: 'completed', }) response.json({ success: true, data: { direction: 'outgoing', id, otherNumber: phoneNumber, startedAt, state: 'ringing', }, }) return } if (endpoint === 'calls:block') { const phoneNumber = String(request.body.phoneNumber ?? '').replace(/\D/g, '') if (!phoneNumber) { response.json({ success: false, error: 'invalid_number' }) return } blockedCallNumbers.add(phoneNumber) response.json({ success: true, data: { blocked: true, phoneNumber } }) return } if ( endpoint === 'calls:answer' || endpoint === 'calls:decline' || endpoint === 'calls:hangup' ) { response.json({ success: true }) return } if (endpoint.startsWith('calendar:') && !authenticated) { response.json({ success: false, error: 'not_authenticated' }) return } if (endpoint === 'calendar:list') { const startsAt = Number(request.body.startsAt) * 1000 const endsAt = Number(request.body.endsAt) * 1000 response.json({ success: true, data: calendarEvents.filter( (event) => event.endsAt >= startsAt && event.startsAt < endsAt, ), }) return } if (endpoint === 'calendar:create') { const id = `calendar-${Date.now()}` calendarEvents.push({ ...request.body, endsAt: Number(request.body.endsAt) * 1000, id, remindedAt: null, revision: 1, startsAt: Number(request.body.startsAt) * 1000, }) response.json({ success: true, data: { id } }) return } if (endpoint === 'calendar:update') { const index = calendarEvents.findIndex( (event) => event.id === request.body.id, ) if (index < 0 || calendarEvents[index].revision !== request.body.revision) { response.json({ success: false, error: 'conflict' }) return } calendarEvents[index] = { ...calendarEvents[index], ...request.body, endsAt: Number(request.body.endsAt) * 1000, remindedAt: null, revision: calendarEvents[index].revision + 1, startsAt: Number(request.body.startsAt) * 1000, } response.json({ success: true }) return } if (endpoint === 'calendar:delete') { calendarEvents = calendarEvents.filter( (event) => event.id !== request.body.id, ) response.json({ success: true }) return } if (endpoint === 'pages:list') { const query = String(request.body.search ?? '').toLowerCase() let items = pagesPosts if (request.body.category && request.body.category !== 'all') items = items.filter((item) => item.category === request.body.category) if (query) items = items.filter((item) => `${item.title} ${item.body}`.toLowerCase().includes(query), ) if (request.body.saved) items = items.filter((post) => pagesReactions.some( (reaction) => reaction.post_id === post.id && reaction.account_id === 1 && reaction.kind === 'save', ), ) response.json({ success: true, data: { hasMore: false, items: items.map(pageView), offset: 0 }, }) return } if (endpoint === 'pages:get') { const post = pagesPosts.find((item) => item.id === request.body.id) response.json( post ? { success: true, data: pageView(post) } : { success: false, error: 'post_not_found' }, ) return } if (endpoint.startsWith('pages:') && !authenticated) { response.json({ success: false, error: 'not_authenticated' }) return } if (endpoint === 'pages:list-own') { response.json({ success: true, data: { hasMore: false, items: pagesPosts.filter((item) => item.account_id === 1).map(pageView), offset: 0, }, }) return } if (endpoint === 'pages:create') { const gradients = { 'sunset-drive': 'linear-gradient(145deg, #ff9a62, #5f2c82 58%, #141e30)', 'ocean-air': 'linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)', 'city-lights': 'linear-gradient(135deg, #fbc2eb, #a6c1ee 48%, #302b63)', 'desert-road': 'linear-gradient(150deg, #f6d365, #fda085 45%, #512b58)', } const id = `pages-${Date.now()}` const images = request.body.images.map((image, index) => ({ media_id: image.id, gradient: gradients[image.id] ?? 'linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)', sort_order: index + 1, })) pagesPosts.unshift({ ...request.body, id, account_id: 1, author_name: 'demo', source_type: 'personal', citymarkt_listing_id: null, created_at: new Date().toISOString(), like_count: 0, images, }) response.json({ success: true, data: { id } }) return } if (endpoint === 'pages:share-citymarkt') { const listing = marketplaceListings.find( (item) => item.id === request.body.listingId && item.seller_account_id === 1 && ['active', 'reserved'].includes(item.status), ) if (!listing) { response.json({ success: false, error: 'citymarkt_not_found' }) return } if (pagesPosts.some((item) => item.citymarkt_listing_id === listing.id)) { response.json({ success: false, error: 'citymarkt_already_shared' }) return } if ( pagesPosts.some( (item) => item.account_id === 1 && item.source_type === 'citymarkt' && item.created_at.slice(0, 10) === '2026-08-06', ) ) { response.json({ success: false, error: 'citymarkt_daily_limit' }) return } const id = `pages-${Date.now()}` pagesPosts.unshift({ id, account_id: 1, author_name: 'demo', source_type: 'citymarkt', citymarkt_listing_id: listing.id, title: listing.title, body: listing.description, category: 'citymarkt', district: listing.district, created_at: '2026-08-06 13:30:00', like_count: 0, images: listing.images, }) response.json({ success: true, data: { id } }) return } if (endpoint === 'pages:react') { const index = pagesReactions.findIndex( (item) => item.post_id === request.body.id && item.account_id === 1 && item.kind === request.body.kind, ) if (request.body.active && index < 0) pagesReactions.push({ post_id: request.body.id, account_id: 1, kind: request.body.kind, }) if (!request.body.active && index >= 0) pagesReactions.splice(index, 1) response.json({ success: true }) return } if (endpoint === 'pages:delete') { const index = pagesPosts.findIndex( (item) => item.id === request.body.id && item.account_id === 1, ) if (index < 0) { response.json({ success: false, error: 'post_not_found' }) return } pagesPosts.splice(index, 1) response.json({ success: true }) return } if (endpoint === 'marketplace:list') { const query = String(request.body.search ?? '').toLowerCase() let items = marketplaceListings.filter((item) => ['active', 'reserved'].includes(item.status), ) if (request.body.category && request.body.category !== 'all') items = items.filter((item) => item.category === request.body.category) if (request.body.district && request.body.district !== 'all') items = items.filter((item) => item.district === request.body.district) if (query) items = items.filter((item) => `${item.title} ${item.description}`.toLowerCase().includes(query), ) if (request.body.favorites) items = items.filter((item) => item.is_favorite) response.json({ success: true, data: { hasMore: false, items, offset: 0 } }) return } if (endpoint === 'marketplace:get') { const item = marketplaceListings.find( (listing) => listing.id === request.body.id, ) response.json( item ? { success: true, data: { ...item, is_owner: authenticated && item.seller_account_id === 1, }, } : { success: false, error: 'listing_not_found' }, ) return } if (endpoint.startsWith('marketplace:') && !authenticated) { response.json({ success: false, error: 'not_authenticated' }) return } if (endpoint === 'marketplace:counts') { response.json({ success: true, data: { active: marketplaceListings.filter( (item) => item.seller_account_id === 1 && ['active', 'reserved'].includes(item.status), ).length, unread: marketplaceInquiries.reduce( (total, item) => total + item.unread, 0, ), }, }) return } if (endpoint === 'marketplace:list-own') { response.json({ success: true, data: { hasMore: false, items: marketplaceListings.filter( (item) => item.seller_account_id === 1, ), offset: 0, }, }) return } if (endpoint === 'marketplace:create') { const id = `81bc9d37-20e1-4d8a-82f8-${String(Date.now()).slice(-12)}` const selected = request.body.images.map((image) => { const photo = { 'sunset-drive': 'linear-gradient(145deg, #ff9a62, #5f2c82 58%, #141e30)', 'ocean-air': 'linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)', 'city-lights': 'linear-gradient(135deg, #fbc2eb, #a6c1ee 48%, #302b63)', 'desert-road': 'linear-gradient(150deg, #f6d365, #fda085 45%, #512b58)', }[image.id] ?? 'linear-gradient(145deg, #ff6b6b, #845ec2 52%, #0f2027)' return { media_id: image.id, gradient: photo, sort_order: 1 } }) marketplaceListings.unshift({ ...request.body, id, seller_account_id: 1, seller_name: 'demo', seller_since: '2026-08-04 12:00:00', seller_active: 1, item_condition: request.body.condition, price_type: request.body.priceType, show_phone: request.body.showPhone ? 1 : 0, phone_number: null, status: 'active', revision: 1, created_at: '2026-08-06 11:00:00', updated_at: '2026-08-06 11:00:00', expires_at: '2026-08-13 11:00:00', images: selected, image: selected[0]?.gradient ?? null, is_favorite: false, }) response.json({ success: true, data: { id } }) return } if (endpoint === 'marketplace:update') { const item = marketplaceListings.find( (listing) => listing.id === request.body.id, ) if (!item) { response.json({ success: false, error: 'listing_not_found' }) return } const selected = request.body.images.map((image, index) => ({ media_id: image.id, gradient: { 'sunset-drive': 'linear-gradient(145deg, #ff9a62, #5f2c82 58%, #141e30)', 'ocean-air': 'linear-gradient(160deg, #67d5b5, #26648e 55%, #0b132b)', 'city-lights': 'linear-gradient(135deg, #fbc2eb, #a6c1ee 48%, #302b63)', 'desert-road': 'linear-gradient(150deg, #f6d365, #fda085 45%, #512b58)', }[image.id] ?? item.image, sort_order: index + 1, })) Object.assign(item, request.body, { image: selected[0]?.gradient ?? null, images: selected, item_condition: request.body.condition, price_type: request.body.priceType, revision: item.revision + 1, }) response.json({ success: true, data: { revision: item.revision } }) return } if (endpoint === 'marketplace:favorite') { const item = marketplaceListings.find( (listing) => listing.id === request.body.id, ) if (item) item.is_favorite = request.body.favorite response.json({ success: true }) return } if (endpoint === 'marketplace:set-status') { const item = marketplaceListings.find( (listing) => listing.id === request.body.id, ) if (item) item.status = request.body.status response.json({ success: true }) return } if (endpoint === 'marketplace:list-inquiries') { response.json({ success: true, data: marketplaceInquiries }) return } if (endpoint === 'marketplace:send-message') { let inquiry = marketplaceInquiries.find( (item) => item.id === request.body.inquiryId || item.listing_id === request.body.listingId, ) if (!inquiry) { const listing = marketplaceListings.find( (item) => item.id === request.body.listingId, ) inquiry = { id: '4903b923-409a-437e-971f-b7a2b10e9e31', listing_id: listing.id, seller_account_id: listing.seller_account_id, buyer_account_id: 1, title: listing.title, price: listing.price, price_type: listing.price_type, status: listing.status, image: listing.image, other_name: listing.seller_name, last_message: request.body.body, offer_id: null, offer_amount: null, offer_proposer_account_id: null, offer_status: null, offer_revision: 0, unread: 0, updated_at: '2026-08-06 11:30:00', } marketplaceInquiries.push(inquiry) } marketplaceMessages.push({ id: marketplaceMessages.length + 1, inquiry_id: inquiry.id, sender_account_id: 1, body: request.body.body, created_at: '2026-08-06 12:00:00', read_at: null, }) inquiry.last_message = request.body.body inquiry.unread = 0 inquiry.updated_at = '2026-08-06 12:00:00' response.json({ success: true, data: { id: inquiry.id } }) return } if (endpoint === 'marketplace:get-inquiry') { const inquiry = marketplaceInquiries.find( (item) => item.id === request.body.id, ) if (inquiry) { inquiry.unread = 0 for (const offer of marketplaceOffers) { if (offer.inquiry_id === inquiry.id && offer.proposer_account_id !== 1) offer.read_at = '2026-08-06 12:01:00' if ( offer.inquiry_id === inquiry.id && offer.proposer_account_id === 1 && ['accepted', 'rejected'].includes(offer.status) ) offer.response_read_at = '2026-08-06 12:01:00' } } const listing = inquiry && marketplaceListings.find((item) => item.id === inquiry.listing_id) response.json( inquiry ? { success: true, data: { accountId: 1, inquiry: { ...inquiry, reserved_account_id: listing.reserved_account_id ?? null, status: listing.status, }, messages: marketplaceMessages.filter( (message) => message.inquiry_id === inquiry.id, ), offers: marketplaceOffers.filter( (offer) => offer.inquiry_id === inquiry.id, ), }, } : { success: false, error: 'inquiry_not_found' }, ) return } if (endpoint === 'marketplace:make-offer') { const inquiry = marketplaceInquiries.find( (item) => item.id === request.body.inquiryId, ) const amount = Number(request.body.amount) if (!inquiry || !Number.isInteger(amount) || amount < 1) { response.json({ success: false, error: 'invalid_offer' }) return } if (inquiry.offer_status === 'accepted') { response.json({ success: false, error: 'offer_closed' }) return } if ( inquiry.offer_status === 'pending' && inquiry.offer_proposer_account_id === 1 ) { response.json({ success: false, error: 'offer_waiting' }) return } const previous = marketplaceOffers.find( (offer) => offer.id === inquiry.offer_id, ) if (previous?.status === 'pending') previous.status = 'countered' const offer = { id: marketplaceOffers.length + 1, inquiry_id: inquiry.id, proposer_account_id: 1, amount, status: 'pending', read_at: null, response_read_at: null, created_at: '2026-08-06 12:02:00', updated_at: '2026-08-06 12:02:00', } marketplaceOffers.push(offer) inquiry.offer_id = offer.id inquiry.offer_amount = amount inquiry.offer_proposer_account_id = 1 inquiry.offer_status = 'pending' inquiry.offer_revision += 1 inquiry.updated_at = offer.updated_at response.json({ success: true, data: { id: offer.id } }) return } if (endpoint === 'marketplace:respond-offer') { const inquiry = marketplaceInquiries.find( (item) => item.id === request.body.inquiryId, ) const offer = inquiry && marketplaceOffers.find((item) => item.id === inquiry.offer_id) if ( !inquiry || !offer || offer.status !== 'pending' || offer.proposer_account_id === 1 ) { response.json({ success: false, error: 'offer_not_actionable' }) return } if (!['accepted', 'rejected'].includes(request.body.action)) { response.json({ success: false, error: 'invalid_offer_response' }) return } offer.status = request.body.action offer.updated_at = '2026-08-06 12:03:00' inquiry.offer_status = request.body.action inquiry.offer_revision += 1 inquiry.updated_at = offer.updated_at if (request.body.action === 'accepted') { const listing = marketplaceListings.find( (item) => item.id === inquiry.listing_id, ) listing.status = 'reserved' listing.reserved_account_id = inquiry.buyer_account_id inquiry.status = 'reserved' } response.json({ success: true }) return } if (endpoint === 'marketplace:report' || endpoint === 'marketplace:block') { 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', id: 1, } 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}`) })