Browse docs

Events

Public Free Phone observer events, custom-app bridge messages, lifecycle hooks, and internal event boundaries with examples.

Events

Free Phone separates public observer events from internal transport events. Public observer events may be handled by another resource. Internal events coordinate Free Phone itself and must not be triggered as integration shortcuts.

Events are not mutation APIs. Use the documented Exports to register apps, create invoices, send notifications, or create CrewLink pings.

These events run locally on the server. They can be observed with AddEventHandler and are safe for logging, dispatch synchronization, or read-only reactions.

EventParametersFired when
sky_phone:crewlink:memberJoined(group_id, profile_id)A profile joins a group.
sky_phone:crewlink:memberLeft(group_id, profile_id)A profile leaves a group.
sky_phone:crewlink:activeChanged(profile_id, group_id)A profile changes its active group.
sky_phone:crewlink:pingCreated(group_id, ping)A group ping is created.
sky_phone:crewlink:pingRemoved(group_id, ping_id)A group ping is removed.

Observe membership changes

server.lua
AddEventHandler("sky_phone:crewlink:memberJoined", function(group_id, profile_id)
    print(("[crew-audit] Profile %s joined group %s"):format(
        profile_id,
        group_id
    ))
end)

AddEventHandler("sky_phone:crewlink:memberLeft", function(group_id, profile_id)
    print(("[crew-audit] Profile %s left group %s"):format(
        profile_id,
        group_id
    ))
end)

Observe the active group

server.lua
AddEventHandler("sky_phone:crewlink:activeChanged", function(profile_id, group_id)
    if group_id then
        print(("[crew-audit] Profile %s activated group %s"):format(
            profile_id,
            group_id
        ))
    else
        print(("[crew-audit] Profile %s has no active group"):format(profile_id))
    end
end)

Observe shared pings

server.lua
AddEventHandler("sky_phone:crewlink:pingCreated", function(group_id, ping)
    print(("[crew-audit] Ping %s created in group %s at %.2f, %.2f, %.2f"):format(
        ping.id,
        group_id,
        ping.coords.x,
        ping.coords.y,
        ping.coords.z
    ))
end)

AddEventHandler("sky_phone:crewlink:pingRemoved", function(group_id, ping_id)
    print(("[crew-audit] Ping %s removed from group %s"):format(
        ping_id,
        group_id
    ))
end)

Do not call TriggerEvent with these names to change CrewLink state. Use CreateCrewLinkPing and RemoveCrewLinkPing for supported mutations.

Custom-app iframe messages

Custom apps communicate with the phone host through a versioned postMessage contract. Every message is validated against its iframe source, origin, app ID, request ID, permission, and payload limit.

App to phone

Message typeRequired dataPurpose
sky-phone-app:readyappId, protocolVersionTell the host that the iframe bridge is ready.
sky-phone-app:requestappId, protocolVersion, requestId, method, payload?Request a permission-bound host method.

Announce bridge readiness

web/app.js
window.parent.postMessage({
  type: 'sky-phone-app:ready',
  appId: 'dispatch-board',
  protocolVersion: 1
}, '*')

Use the exact allowed host origin instead of * when the custom app knows it.

Request app storage

web/app.js
const requestId = crypto.randomUUID()

window.parent.postMessage({
  type: 'sky-phone-app:request',
  appId: 'dispatch-board',
  protocolVersion: 1,
  requestId,
  method: 'device.storage.get',
  payload: {
    key: 'filters'
  }
}, '*')

Supported request methods in ABI 1 are:

  • app.close
  • app.open
  • device.storage.get
  • device.storage.set
  • notification.create

The matching permission must be declared in the client definition and server policy.

Phone to app

Message typeDataPurpose
sky-phone-app:contextcontextSupplies app ID, capabilities, locale, theme, scale, and safe area.
sky-phone-app:opendata?Supplies opening data from OpenCustomApp.
sky-phone-app:messagepayloadDelivers SendCustomAppMessage data.
sky-phone-app:responserequestId, success, data?, error?Resolves a bridge request.

Receive host messages

web/app.js
window.addEventListener('message', (event) => {
  const message = event.data

  if (!message || typeof message !== 'object') return

  switch (message.type) {
    case 'sky-phone-app:context':
      document.documentElement.dataset.theme = message.context.theme
      break

    case 'sky-phone-app:open':
      openIncident(message.data?.incidentId)
      break

    case 'sky-phone-app:message':
      applyPhoneMessage(message.payload)
      break

    case 'sky-phone-app:response':
      resolveBridgeRequest(message)
      break
  }
})

Validate event.origin for remote apps before processing a message.

Custom-app lifecycle hooks

Client definitions may include lifecycle functions. They run inside the resource that registered the app and are useful for local cleanup or telemetry.

client.lua
local definition = {
    id = "dispatch-board",
    name = "Dispatch Board",
    ui = "web/index.html",

    onInstall = function()
        print("[my_dispatch] App installed")
    end,

    onOpen = function(data)
        print(("[my_dispatch] App opened for incident %s"):format(
            tostring(data and data.incidentId)
        ))
    end,

    onReady = function()
        print("[my_dispatch] Iframe bridge ready")
    end,

    onClose = function()
        print("[my_dispatch] App closed")
    end,
}

exports["sky_phone"]:AddCustomApp(definition)

Internal push and net events

The following event families are private implementation details. They are listed so developers can recognize them in logs, not as a compatibility promise.

AreaInternal examples
Devicesky_phone:device:open, sky_phone:device:updated, sky_phone:device:invalidated
Callssky_phone:call:incoming, sky_phone:call:state, sky_phone:calls:changed
Messagessky_phone:messages:changed, sky_phone:messages:new
Billingsky_phone:billing:changed, sky_phone:billing:new
Social appssky_phone:picstagram:new, sky_phone:feather:new, sky_phone:fliptok:new
Gallery and mediasky_phone:gallery:changed, sky_phone:media:upload-ready, sky_phone:media:upload-result
Voice Memossky_phone:memos:upload-ready, sky_phone:memos:upload-result
Radiosky_phone:radio:members, sky_phone:radio:notification
Companiessky_phone:companies:changed, sky_phone:companies:notification
Marketplacesky_phone:marketplace:changed, sky_phone:marketplace:new-message
Do not trigger internal events from another resource. Their payloads, session checks, and names may change. Use public exports and observer events only.

Client-local lifecycle events

These events coordinate Free Phone's own focus, animations, and camera state:

sky_phone:client:nuiReady
sky_phone:nuiClosed
sky_phone:client:forceClose
sky_phone:client:setSuspended
sky_phone:client:setCameraFocus
sky_phone:client:cameraFocusApplied
sky_phone:client:setPayphoneFocus
sky_phone:animation:phone
sky_phone:animation:call
sky_phone:animation:camera
sky_phone:animation:reset

They are not public integration points. A custom app receives lifecycle through its hooks and the iframe bridge instead.