← Documentation

Share and friend invitations

This guide describes the integrated source reference. Verify the matching SDK version is published and the platform feature is enabled before depending on it; the reference commit alone is not a release receipt.

Works without creator code

Share on either the game page or play page copies the canonical game-page link. It excludes build previews, session tokens and world state. If clipboard access fails, players can select and copy the link manually.

Invite opens a platform-owned picker containing accepted friends only. A confirmed send creates a notification for the selected friend. Opening it signs the recipient in if needed, revalidates the invite and opens the game's published build. Neither the game nor its backend needs an invitation handler for this default behaviour.

Only the intended recipient can accept. Both players must still have access to the game, valid accounts and an accepted friendship, with neither blocking the other. Invites do not grant access to private games or drafts. Unfriending or blocking revokes old invites; adding the friend again does not revive them.

Optional custom context and acceptance

import { Inkwell } from '@silicon-jungle/inkwell-sdk'

let currentWorld = 'lobby'
export function worldChanged(worldId: string) { currentWorld = worldId }
const removeProvider = Inkwell.invites.setContextProvider(() => ({
  worldId: currentWorld,
}))
const stopAccepted = Inkwell.invites.onAccepted(async invite => {
  const worldId = invite.context.worldId
  if (typeof worldId !== 'string') return // Basic invitation: normal startup.
  // Your backend implements this action and validates access/capacity itself.
  const connection = await Inkwell.backend.connect()
  try {
    await connection.action('world.join', { worldId, invitationId: invite.id })
    // This minimal example ends the connection. A live game would retain it.
  } finally { connection.close() }
}, error => console.warn('Could not follow this invitation:', error))
window.addEventListener('pagehide', () => {
  removeProvider()
  stopAccepted()
}, { once: true })

setContextProvider registers one active provider in the running game. The host asks it for fresh JSON when the player sends; the provider never receives the friend list and cannot select recipients or send notifications. Registering a new provider replaces the old one. The returned cleanup unregisters it. The game detail page, where no game is running, sends empty context.

Preparation is bounded to five seconds. A rejection, invalid context, game-frame change or timeout fails visibly; the player may explicitly retry or choose a basic game link. The platform does not silently discard custom context and report a successful custom invite.

getAccepted() returns the accepted invitation for this launch, or null for a normal launch. onAccepted(handler, onError?) also works if registered after acceptance and calls each subscription at most once for that launch. It returns an unsubscribe function. Reopening the same valid invitation may deliver its same ID again: deduplicate irreversible game operations by invitation ID.

Payload, limits and failure behaviour

An accepted invitation contains id, gameSlug, from.username, game-scoped from.playerId, context, and ISO timestamps createdAt, acceptedAt, expiresAt. No email, global account ID, session token or friend list is exposed. The raw launch invite parameter stays in the platform wrapper, not the creator iframe URL.

  • Context must be a JSON object: at most 4 KiB UTF-8, depth 8, and 2,048 visited values. Finite numbers only; no functions, cycles, arbitrary object prototypes or reserved prototype keys.
  • Invites expire after 24 hours. Delivery limits are 10/minute and 100/day per sender across games. Deleting notifications or unfriending does not refund quota.
  • Host sends use an immutable request ID and payload for uncertain retries. A timeout can mean the notification arrived; retrying that same request does not send twice.
  • Acceptance is idempotent and preserves its original acceptance time. An expired, revoked or unauthorized invite does not provide creator context. The player can still play normally if otherwise authorized.
  • getAccepted accepts an AbortSignal and times out after 20 seconds. InviteError exposes a code. Outside a trusted frame it fails; onAccepted can route errors to its optional error callback. Unsubscribing suppresses later callback delivery.
  • Invitation records have seven-day retention. This is not durable world state, an unlimited messaging channel or a matchmaking queue.

Context is untrusted application input, not a signed entitlement to join a room or spawn at a location. Your server must recheck room membership, capacity, world access and spawn rules using the actual connecting player. The inviter's public identity is not an authorization credential.

Platform and SDK boundaries

Sending and accepting use session-authenticated platform POST routes under /api/v1/games/:slug/invites and /api/v1/games/:slug/invites/:inviteId/accept. These are host UI operations, not creator API-key or runtime-token permissions. The SDK bridge exposes context preparation and accepted-context reads only.

Every invitation type and export · Implement room/world joins in your server · Platform social features