Skip to content
Eureka Phone: Duo

Exports and integrations

Server exports for your own resources, the service-message allowlist, and how a new phone design plugs into the core.

Everything is exported by the core, eureka_phone, never by a page resource. A page resource is client-only and cannot grant anything.

Server exports

Export Returns Notes
GetPhoneNumber(playerSource) string or nil The number of the active line of the phone the player has open, while that session passes the checks every request repeats (same character, phone still carried). Reads the in-memory session; cheap to call often.
IsPhoneAvailable(playerSource) boolean Whether the player has such a phone open.
SendServiceMessage(toNumber, body, options, callback) nothing Sends body from the server's service line to toNumber. See below.
GetMailAddress(playerSource) string or nil The player's in-game mail address (mail app).
SendServiceMail(...) nothing Sends mail from a configured sender (mail app).
SetCryptoPrice(price, callback) nothing Sets the fictional coin's price from a price feed resource (crypto app, duo_crypto_price_resources).
server.lua
-- Who is this player on the phone?
local number = exports.eureka_phone:GetPhoneNumber(source)
if not number then
    return -- no phone open, or it was taken away
end

-- A message from your resource's service line. The callback always runs exactly once.
exports.eureka_phone:SendServiceMessage(number, 'Your vehicle is ready at Benny’s.', { reference = 'repair-4812' },
    function(messageId, errorCode)
        if not messageId then
            print(('phone refused: %s'):format(errorCode)) -- NOT_ALLOWED, RATE_LIMITED, UNAVAILABLE, ...
        end
    end)

How service messages work

  • Allowlist. The core serves SendServiceMessage only to resources named in duo_service_resources (for example "my_bank,my_garage"); callers never choose the sender. Set duo_service_number to the number messages come from, and keep it in duo_reserved_numbers.
  • Idempotent retries. Pass options.reference: a retry with the same reference returns the first message instead of sending again. Keep it when the callback answers UNAVAILABLE (the core stopped mid-call) and retry after it is back.
  • Rate limit. duo_rate_limit_per_minute also caps how many service messages one resource sends to one number per minute (RATE_LIMITED).
  • Callback. Required; it runs on a later tick after the database answered, with (messageId, nil) or (nil, errorCode). Error codes are the phone's own (NOT_READY, NOT_ALLOWED, INVALID_OPTIONS, RATE_LIMITED, UNAVAILABLE, INTERNAL).

Mail works the same way: list sender resources in duo_mail_senders as resource:name.

Island activities

Other resources can show live activities on the Island (the pill at the top of the phone) once they are listed in duo_island_resources. The contract is in the archive's apps/island/shared/contract.lua.

Price feeds

A resource listed in duo_crypto_price_resources may call SetCryptoPrice to move the coin's price; duo_crypto_price is the starting price and duo_crypto_history_days how much history players see.

How a phone design plugs in

A phone design is a page resource: its built page (web/dist), a config/client.lua with its items and keys, and a manifest that runs the core's bridge in it. It has no server script and lists no app contract; the bridge reads the app events from the core at load (exports.eureka_phone:netEvents()), so an app added to the core needs no change in any page.

fxmanifest.lua (page resource)
fx_version 'cerulean'
game 'gta5'
name 'eureka_duo'
dependency 'eureka_phone'

client_scripts {
    '@eureka_phone/config/shared.lua',
    '@eureka_phone/shared/contracts.lua',
    '@eureka_phone/shared/errors.lua',
    'config/client.lua',
    '@eureka_phone/client/bridge.lua',
}

ui_page 'web/dist/index.html'
ui_page_preload 'yes'
nui_callback_strict_mode 'true'
files { 'web/dist/index.html', 'web/dist/**/*' }

What differs between designs, and must: the resource name (its NUI origin https://cfx-nui-<name>, its request IDs, its own key commands +<name>_<key>) and its items. What stays the same: the core's name in the manifest, the network events, the convars and the tables.

The core's router keeps one current page per player: the page of the item they opened last, else the page that was current, else the first page that started. The phone key shows that page. When the server opens a phone whose item another page lists, the first page hands over once and the other page shows and opens. See running both designs.

Last updated October 2, 2026