GameServer.Push (game_server_sdk v1.0.1070)

Copy Markdown View Source

Push context – device push-token registry and (see send_to_user/3) server-authoritative delivery of push notifications.

Devices register their FCM registration token or APNs device token against the authenticated user; a user has many devices. Delivery routes per token off the provider column ("fcm" | "apns"), falling back to the zero-config Log provider when nothing is configured (see docs/specs/push.md).

Usage

# Register a device (typically via POST /me/push-tokens)
{:ok, token} = Push.register_token(user_id, %{
  "token" => "fcm-registration-token",
  "platform" => "android",
  "device_id" => "stable-device-key"
})

# List a user's devices
tokens = Push.list_tokens(user_id, page: 1, page_size: 25)

# Remove one (DELETE /me/push-tokens/:id)
{:ok, _} = Push.delete_token(user_id, token.id)

Note: This is an SDK stub. Calling these functions will raise an error. The actual implementation runs on the GameServer.

Summary

Functions

Remove any token row by id (admin). Returns {:ok, %PushToken{}} or {:error, :not_found}.

Count for list_all_tokens/2 (same filters).

Count a user's registered tokens (including disabled).

Remove a token row by id, scoped to user_id (the DELETE /me/push-tokens/:id path). Returns {:ok, %PushToken{}} or {:error, :not_found}.

Soft-disable a token the provider reported dead. The row is kept — re-registration re-enables it — so a token bouncing between valid and invalid never loses its device association.

Admin listing across all users. Supported filters keys (atom or string): :user_id, :platform, :provider, and :status ("live" | "disabled").

List a user's registered tokens, newest first. Includes disabled rows (they are the user's devices; clients can show them greyed out).

The user's live (non-disabled) tokens — the delivery fan-out set. Unpaginated by design: bounded by max_push_tokens_per_user.

Bump last_used_at after a successful delivery.

Resolve the delivery provider for a token: its provider column's module when that module's configured?/0 says it can deliver, else the zero-config Log provider. force_log: true (PUSH_ADAPTER=log) short-circuits everything to Log.

Register (or refresh) a device push token for user_id.

Queue a push message to all of user_id's live devices.

Queue a push message to every live device of user_ids.

Aggregate token counts for the admin stat card and runtime introspection: %{total: n, live: n, disabled: n, by_platform: %{...}, by_provider: %{...}} (platform/provider maps count live tokens only).

Remove a token row by its raw token value, scoped to user_id.

Whether the user has any live device. Cached (60s TTL + version bump on register/remove/disable): Notifications asks this on every insert, and the common no-device answer must not cost a query.

Types

user_id()

@type user_id() :: Ecto.UUID.t()

Functions

admin_delete_token(id)

@spec admin_delete_token(Ecto.UUID.t()) ::
  {:ok, GameServer.Push.PushToken.t()} | {:error, :not_found}

Remove any token row by id (admin). Returns {:ok, %PushToken{}} or {:error, :not_found}.

count_all_tokens(filters)

@spec count_all_tokens(map()) :: non_neg_integer()

Count for list_all_tokens/2 (same filters).

count_tokens(user_id)

@spec count_tokens(user_id()) :: non_neg_integer()

Count a user's registered tokens (including disabled).

delete_token(user_id, id)

@spec delete_token(user_id(), Ecto.UUID.t()) ::
  {:ok, GameServer.Push.PushToken.t()} | {:error, :not_found}

Remove a token row by id, scoped to user_id (the DELETE /me/push-tokens/:id path). Returns {:ok, %PushToken{}} or {:error, :not_found}.

disable_token(token)

@spec disable_token(String.t()) :: :ok

Soft-disable a token the provider reported dead. The row is kept — re-registration re-enables it — so a token bouncing between valid and invalid never loses its device association.

list_all_tokens(filters, opts)

@spec list_all_tokens(
  map(),
  keyword()
) :: [GameServer.Push.PushToken.t()]

Admin listing across all users. Supported filters keys (atom or string): :user_id, :platform, :provider, and :status ("live" | "disabled").

Supports pagination via :page and :page_size options; preloads :user so the admin UI can show names, not UUIDs.

list_tokens(user_id, opts)

@spec list_tokens(
  user_id(),
  keyword()
) :: [GameServer.Push.PushToken.t()]

List a user's registered tokens, newest first. Includes disabled rows (they are the user's devices; clients can show them greyed out).

Supports pagination via :page and :page_size options.

live_tokens(user_id)

@spec live_tokens(user_id()) :: [GameServer.Push.PushToken.t()]

The user's live (non-disabled) tokens — the delivery fan-out set. Unpaginated by design: bounded by max_push_tokens_per_user.

mark_token_used(token)

@spec mark_token_used(String.t()) :: :ok

Bump last_used_at after a successful delivery.

provider_for(push_token)

@spec provider_for(GameServer.Push.PushToken.t()) :: module()

Resolve the delivery provider for a token: its provider column's module when that module's configured?/0 says it can deliver, else the zero-config Log provider. force_log: true (PUSH_ADAPTER=log) short-circuits everything to Log.

register_token(user_id, attrs)

@spec register_token(user_id(), map()) ::
  {:ok, GameServer.Push.PushToken.t()}
  | {:error, :too_many_tokens | Ecto.Changeset.t()}

Register (or refresh) a device push token for user_id.

Upsert semantics, serialized under the :push_tokens advisory lock:

  • a row with the same token already exists → it is claimed for this user/device (a device that logged into another account must not keep receiving the old account's pushes) and re-enabled;
  • else a row with the same (user_id, device_id) exists → its token is rotated in place and the row re-enabled;
  • else a new row is inserted, subject to max_push_tokens_per_user (counting live tokens only).

provider defaults from the platform when omitted: "ios""apns", anything else → "fcm".

Returns {:ok, %PushToken{}}, {:error, :too_many_tokens}, or {:error, changeset}.

send_to_user(user_id, message_attrs, opts)

@spec send_to_user(user_id(), map(), keyword()) :: :ok | {:error, map()}

Queue a push message to all of user_id's live devices.

Server-authoritative: exposed to plugins through the SDK and to admins — never as a public client endpoint. Best-effort by design: no live devices means no jobs, and delivery failures never propagate back to the caller.

Returns :ok or {:error, errors} when the message fails validation (see GameServer.Push.Message.new/1).

send_to_users(user_ids, message_attrs, opts)

@spec send_to_users([user_id()], map(), keyword()) :: :ok | {:error, map() | term()}

Queue a push message to every live device of user_ids.

Small audiences enqueue delivery jobs inline; past 100 recipients the expansion itself becomes a FanoutWorker job (chunked, restart-safe, deduped against identical double-broadcasts).

token_stats()

@spec token_stats() :: map()

Aggregate token counts for the admin stat card and runtime introspection: %{total: n, live: n, disabled: n, by_platform: %{...}, by_provider: %{...}} (platform/provider maps count live tokens only).

unregister_token(user_id, token)

@spec unregister_token(user_id(), String.t()) ::
  {:ok, GameServer.Push.PushToken.t()} | {:error, :not_found}

Remove a token row by its raw token value, scoped to user_id.

Returns {:ok, %PushToken{}} or {:error, :not_found}.

user_has_live_tokens?(user_id)

@spec user_has_live_tokens?(user_id()) :: boolean()

Whether the user has any live device. Cached (60s TTL + version bump on register/remove/disable): Notifications asks this on every insert, and the common no-device answer must not cost a query.