Documentation

Apps & Sites

Add your own apps and websites to the laptop from another resource.

Other resources add apps and sites to the laptop from a client script. An app opens in its own window on the desktop and is listed in the Applications menu. A site opens in the browser at the address you give it. Both are web pages in your own resource, shown in an iframe, so build them with anything you like. scrollIntoView() and focus() scroll only your page, never the window or browser around it.

The shipped example sites are complete starting points: a business site, a police application site with an app for officers, and two games with leaderboards.

General Information

RegisterApp, RegisterSite, AddBookmark, SendAppMessage and SendSiteMessage are client exports. A registration only adds the app or site on the client that made it, so a job-only app is registered on the clients of players who have that job.

Registrations belong to the resource that made them and are removed when it stops. Registering the same id again replaces the earlier registration.

Types / Classes

LaptopAppRegistration, LaptopSiteRegistration and LaptopBookmark are in types/laptop.lua inside zyke_laptop.

#Registering

The laptop forgets every registration when it restarts. Register on the client event zyke_laptop:Ready, which fires each time the laptop starts, and when your own resource starts:

Example:

lua
local function registerLaptopContent()
    exports["zyke_laptop"]:RegisterApp({
        id = "my_app",
        label = "My App",
        icon = "briefcase",
        category = "utilities",
        description = "Shown in the Applications menu and the app store",
        ui = "web/app.html",
        window = {width = 720, height = 480, minWidth = 480, minHeight = 320, resizable = true},
    })

    exports["zyke_laptop"]:RegisterSite({
        id = "my_market",
        url = "mymarket7xq2v5k3.onion",
        title = "My Market",
        description = "Shown in the search results and the directory",
        ui = "web/site.html",
        category = "shopping",
    })
end

AddEventHandler("zyke_laptop:Ready", registerLaptopContent)

CreateThread(registerLaptopContent)

Add the pages to your own fxmanifest.lua so the laptop can load them:

lua
files {
    "web/**/*",
}

A ui that is a path, such as web/app.html, is loaded from your resource at https://cfx-nui-<your_resource>/web/app.html. A full URL is used as it is.

#Client Sided Exports

#Register App

Adds an app to the laptop on this client. Returns false and prints a warning in the client console when id, label or ui is missing, when id belongs to a built-in app, or when os lists no known OS.

Example:

lua
---@param app LaptopAppRegistration
---@return boolean registered
local registered = exports["zyke_laptop"]:RegisterApp({
    id = "my_app",
    label = "My App",
    icon = "briefcase", -- Lucide icon name (https://lucide.dev/icons) or an image URL
    category = "utilities",
    description = "Shown in the Applications menu and the app store",
    ui = "web/app.html", -- A path inside your resource, or a full URL
    window = {width = 720, height = 480, minWidth = 480, minHeight = 320, resizable = true},
    favorite = false,
    requiresConnection = false,
    singleInstance = true,
    desktop = true,
    preinstalled = false,
    os = {"facade", "lumen"}, -- Leave it out to show the app on every OS
})
FieldDefaultDescription
idRequiredUnique app id. connection, browser, settings, terminal, editor, files, calculator, chat, messenger and store belong to built-in apps and are refused.
labelRequiredThe app's name.
uiRequiredThe page shown in the window: a path inside your resource or a full URL.
icon"box"A Lucide icon name or an image URL.
category"other"accessories, internet, office, system, utilities or other. The section of the Applications menu and the app store it is listed under. Any other value is other.
descriptionNoneShown in the Applications menu and the app store.
windowOS default{width?, height?, minWidth?, minHeight?, resizable?, maximized?}, in pixels. maximized = true opens the window maximised.
favoritefalseLists the app under Favorites in the Applications menu.
requiresConnectionfalseShows a "connect first" screen instead of the app while the Onion connection is down. The app store shows that the app needs it.
singleInstancetrueOpening the app again brings its open window forward and passes it the new params. false opens a new window each time.
desktoptruePuts a shortcut on the desktop of each account that gets the app. See Desktop Shortcuts.
preinstalledfalseInstalls the app for each account the first time it sees it, instead of offering it in the app store. See The App Store.
osEvery OSThe OSes the app shows on. See Apps for Some Operating Systems.

#Unregister App

Removes an app before your resource stops, for example when the player loses the job it belongs to.

Example:

lua
---@param appId string
exports["zyke_laptop"]:UnregisterApp("my_app")

#Register Site

Adds a site to the browser on this client. Returns false and prints a warning in the client console when id, url, title or ui is missing, or when another site already has the address.

Example:

lua
---@param site LaptopSiteRegistration
---@return boolean registered
local registered = exports["zyke_laptop"]:RegisterSite({
    id = "my_market",
    url = "mymarket7xq2v5k3.onion", -- What the player types into the address bar
    title = "My Market",
    description = "Shown in the search results and the directory",
    icon = "store",
    ui = "web/site.html",
    listed = true,
    category = "shopping",
    network = "onion",
    monitored = false,
})
FieldDefaultDescription
idRequiredUnique site id.
urlRequiredThe address the player types, such as lscustoms.com or mymarket7xq2v5k3.onion. It is compared in lower case, without a scheme or trailing slash.
titleRequiredThe site's name in tabs, search results and the directory.
uiRequiredThe page shown in the browser: a path inside your resource or a full URL.
descriptionNoneShown in search results and the directory.
iconGlobe or onionA Lucide icon name or an image URL, shown on the tab and the new tab page.
listedtruefalse keeps the site out of search and the directory. Players only reach it by its address.
categoryNoneThe section it is listed under. See Site Categories.
networkFrom the address"clearnet" or "onion". See Networks.
requiresConnectionNoneThe older way to set network: true is "onion" and false is "clearnet". network wins when both are set.
monitoredfalseRecords visits as traces while browsing.record is "monitored". See Listing and Monitoring.

#Unregister Site

Removes a site before your resource stops.

Example:

lua
---@param siteId string
exports["zyke_laptop"]:UnregisterSite("my_market")

#Add Bookmark

Adds a bookmark to the player's browser. Returns true once the laptop has the bookmark or keeps it for the next boot, even if the player already had it, and false only when id or url is missing. See Bookmarks.

Example:

lua
---@param bookmark LaptopBookmark
---@return boolean kept
local kept = exports["zyke_laptop"]:AddBookmark({
    id = "my_market", -- Unique within your resource, each id is added once
    url = "mymarket7xq2v5k3.onion",
    title = "My Market",
    force = false, -- true adds it back even if the player deleted it
})

#Send App Message

Sends data to your app's page, where the SDK raises it as the message event. Only an open page gets it. Returns false while no OS runs.

Example:

lua
---@param appId string
---@param data any
---@return boolean delivered
exports["zyke_laptop"]:SendAppMessage("my_app", {orders = orders})

#Send Site Message

Sends data to your site's page, where the SDK raises it as the message event. Only an open page gets it. Returns false while no OS runs.

Example:

lua
---@param siteId string
---@param data any
---@return boolean delivered
exports["zyke_laptop"]:SendSiteMessage("my_market", {listings = listings})

#The App Store

Facade, Lumen and Solace have an app store on the desktop. The system apps come with the OS. Registered apps are offered in the store and show nowhere else until the account downloads them, the same as Shroud, Natter, the Onion network app, Paint and Slides. The store lists each app with its label, icon and description, sorted into sections by category. Downloading needs the laptop to be online.

OpenApp, Laptop.openApp and a notification for an app the account has not downloaded open the store on that app's page instead.

Three settings skip the store:

  • preinstalled = true on the registration installs the app for each account the first time it sees it. An account that removes it in the store keeps it removed.
  • appStore.preinstalled in the config lists app ids that new accounts start with. It takes the ids of registered apps as well as connection, chat, messenger, paint and slides.
  • appStore.enabled = false removes the store and installs every app from the start.

Veil and the Crowbar recovery toolkit run live, so Veil has every registered app without the store. Crowbar shows no registered apps at all; sites still open in its browser. See Operating Systems for the store from the player's side.

#Desktop Shortcuts

The first time an account on Facade, Lumen or Solace gets an app, from the store or preinstalled, the app puts a shortcut on that account's desktop, the way an installer does. Set desktop = false to leave it off.

The shortcut is a file in the account's Desktop folder: My App.lnk on Facade, My App.desktop on Lumen and My App alias on Solace. The player can open, rename, move and delete it like any other file, and it stays on the drive with the account. The laptop keeps the ids of apps that already had a shortcut, so a shortcut the player deleted doesn't come back, and an app whose resource restarts keeps its one shortcut. Removing the app in the store removes its shortcuts; downloading it again adds one.

Veil keeps no home between boots, so it shows no shortcuts.

#Apps for Some Operating Systems

An app with an os list only shows on those OSes: "facade", "lumen", "solace" or "veil". On any other OS it is left out of the menus and the store, and OpenApp, Laptop.openApp and a notification for it don't open it.

Unknown ids are dropped with a warning in the client console. A list left without a known id refuses the registration, so a typo can't show the app on every OS. To change an app's OSes, register it again.

#Site Categories

A site's category is the section it is listed under: in LS Pages for a clearnet site, also shown next to it in Bling and QuackQuackGo, and in the Onion Directory for an onion site. Every search engine, Hush included, matches it in searches. Leave it out or use "other" when nothing fits. Any other value is listed as "other" with a warning in the client console.

CategoryFor
governmentThe city, the police, courts and other public offices.
servicesServices a player books or calls: towing, mechanics, parking, taxis, deliveries.
businessCompanies presenting themselves: opening hours, menus, locations, jobs.
shoppingStores that sell online.
financeBanks, loans, insurance and investing.
newsNews, radio and other media.
communityForums, social sites, clubs and events.
gamesGames and entertainment.
searchSearch engines and directories.
otherEverything else.

#Networks

A site's network decides whether it needs the Onion connection. Without one it is taken from the address: .onion addresses are onion sites and everything else is clearnet. Facade, Lumen and Solace open clearnet sites without the Onion connection. On Veil every site needs it.

Every site needs the laptop to be online through a hotspot or a phone. See Wi-Fi & Browsing.

#Listing and Monitoring

Listed sites show up in the browser's search engines and directories:

WhereLists
Bling, QuackQuackGo, LS PagesListed clearnet sites.
New tab page of Ledge and EmberfoxListed clearnet sites, with LS Pages for the rest.
HushEvery listed site.
Onion DirectoryListed onion sites.

listed = false keeps a site out of all of them. See Bookmarks for handing its address to players.

monitored = true records visits to the site as visit traces while browsing.record is "monitored". It comes from your client script, so for a site that matters, mark it from the server with the SetSiteMonitored export as well.

#Bookmarks

A site registered with listed = false stays out of search and the directory, so players only find it by its address. To hand the address out in the world, for example when a contact trusts the player, add it to their bookmarks with AddBookmark.

Ledge, Emberfox, Savanna and Veil Browser share the player's bookmarks, so the bookmark shows in whichever browser the player opens, also after they boot another OS. Bookmarks are kept on the player's PC, like the browsers' other data.

  • The laptop adds each id once and saves that with the bookmarks, so one the player deleted stays deleted.
  • The laptop doesn't have to be out. A bookmark given while no OS runs waits on the player's PC, through relogs, and the browser takes it the next time an OS boots.
  • Call it again when the player loads in, on zyke_laptop:Ready and when your resource starts. A call for an id the browser already added does nothing, and one that never reached the browser is added then.
  • When the player asks for the address again in the world, pass force = true. It adds the bookmark back even if they deleted it, but never twice if they still have it.
  • At most 50 bookmarks wait at once. The oldest are dropped first.

The bookmark is only a shortcut. Anyone who types the address still reaches the page, so decide on your server what the page shows them.

#Talking to the Page

Your page talks to its own resource the way any NUI page does: a fetch to https://<your_resource>/<callback> reaches your RegisterNUICallback. The SDK's Laptop.post does this for you. To push data to the page from Lua, use SendAppMessage or SendSiteMessage.

The OS runs while a laptop is at hand and for a few minutes after the player put it away or walked off. While no OS runs, messages are dropped and both exports return false.

Example:

lua
RegisterNUICallback("buyItem", function(data, cb)
    cb(Z.callback.await("my_resource:BuyItem", data.item))
end)

RegisterNetEvent("my_resource:OrdersChanged", function(orders)
    exports["zyke_laptop"]:SendAppMessage("my_app", {orders = orders})
end)

Include the JavaScript SDK in your pages to receive messages, follow the OS's theme, and type into fields on the laptop's screen.

#Server Callbacks

The page runs on the player's client, so anything it sends can be faked. Server callbacks behind a laptop page should check that the player is at a laptop with the IsAtLaptop server export, and check jobs and money again, rather than trusting the page:

Example:

lua
Z.callback.register("my_resource:GetOrders", function(plyId)
    if (GetResourceState("zyke_laptop") ~= "started" or not exports["zyke_laptop"]:IsAtLaptop(plyId)) then return nil end

    return getOrders(plyId)
end)

IsAtLaptop is true while the player holds a laptop or stands at a placed one.

#Casting

Only the laptop's built-in image viewer, Paint and Slides can be cast to a screen. Registered apps and sites can't. See Casting.