Liking cljdoc? Tell your friends :D

game.gm.visitor

Anonymous visitors: the web layer of coseller attribution for readers who have no seat yet.

A reader arriving through a ?ref= link cannot use /events — that route is seat-gated, and event-payload rewrites the actor to the seat. The GM mints them a visitor:<uuid> instead and records real touches against it; when they later sign in, coseller/claim-visitor re-keys those touches onto their player-name. See game.protocols.coseller for why the prefix is load-bearing.

Deliberately NOT under game.protocols, for the same reason as game.gm.identity: everything there is a pure event handler with a manifest, dispatched from MoM through /v1/gm/handle. This is the opposite shape — HTTP routes served to a browser, proxying MoM, holding no game state and advertising no handlers. The protocol half of anonymous visitors is already in game.protocols.coseller and stays there.

A consuming GM supplies three things:

  1. the routes themselves — gm-lib carries no compojure dependency, so each game keeps its own route definitions and delegates here:

    (POST "/public/touch" [:as request] (gm-visitor/record-visitor-touch request)) (POST "/coseller/claim-visitor" [:as request] (let [{:keys [context seat response]} (require-selected-seat request)] (or response (gm-visitor/claim-visitor-touches request (:game-name context) seat {:submit-ui-event-fn submit-ui-event}))))

  2. ring.middleware.cookies/wrap-cookies in the app's middleware stack. Without it :cookies is absent on the request and unserialised on the response, so every visit mints a new id and nothing is ever claimable — a silent failure with a 200 on every call. This is the one wiring mistake that looks like it works.

  3. the dispatch fn for the claim (:submit-ui-event-fn), because the claim must run as the signed-in caller, and building that payload is game-specific. The public touch needs no injection: it runs on service credentials via game.gm.identity/submit-service-event, and the game name comes from the runtime.

Anonymous visitors: the web layer of coseller attribution for readers who
have no seat yet.

A reader arriving through a `?ref=` link cannot use /events — that route is
seat-gated, and event-payload rewrites the actor to the seat. The GM mints
them a `visitor:<uuid>` instead and records real touches against it; when
they later sign in, `coseller/claim-visitor` re-keys those touches onto their
player-name. See `game.protocols.coseller` for why the prefix is load-bearing.

Deliberately NOT under `game.protocols`, for the same reason as
`game.gm.identity`: everything there is a pure event handler with a manifest,
dispatched from MoM through /v1/gm/handle. This is the opposite shape — HTTP
routes served to a *browser*, proxying MoM, holding no game state and
advertising no handlers. The *protocol* half of anonymous visitors is already
in `game.protocols.coseller` and stays there.

A consuming GM supplies three things:

  1. the routes themselves — gm-lib carries no compojure dependency, so each
     game keeps its own route definitions and delegates here:

       (POST "/public/touch" [:as request]
         (gm-visitor/record-visitor-touch request))
       (POST "/coseller/claim-visitor" [:as request]
         (let [{:keys [context seat response]} (require-selected-seat request)]
           (or response
               (gm-visitor/claim-visitor-touches
                 request (:game-name context) seat
                 {:submit-ui-event-fn submit-ui-event}))))

  2. `ring.middleware.cookies/wrap-cookies` in the app's middleware stack.
     Without it `:cookies` is absent on the request and unserialised on the
     response, so every visit mints a *new* id and nothing is ever claimable
     — a silent failure with a 200 on every call. This is the one wiring
     mistake that looks like it works.

  3. the dispatch fn for the claim (`:submit-ui-event-fn`), because the claim
     must run as the signed-in caller, and building that payload is
     game-specific. The public touch needs no injection: it runs on service
     credentials via `game.gm.identity/submit-service-event`, and the game
     name comes from the runtime.
raw docstring

claim-visitor-touchesclj

(claim-visitor-touches request game-name seat {:keys [submit-ui-event-fn]})

Bind this browser's anonymous referral history to the signed-in seat. The visitor id comes from the cookie and never from the body — a caller can only ever claim what their own browser holds.

:submit-ui-event-fn is (fn [request game-name seat] -> ring response) and must dispatch as the caller, not on service credentials: the claim decides who owns a referral chain, so MoM has to re-verify the human.

Bind this browser's anonymous referral history to the signed-in seat. The
visitor id comes from the cookie and never from the body — a caller can only
ever claim what their own browser holds.

`:submit-ui-event-fn` is (fn [request game-name seat] -> ring response) and
must dispatch as the *caller*, not on service credentials: the claim decides
who owns a referral chain, so MoM has to re-verify the human.
raw docstring

(cookie-visitor-id request)

The visitor id this browser already carries, if it is well-formed. Never trust the shape blind: the cookie is client-presented.

The visitor id this browser already carries, if it is well-formed. Never trust
the shape blind: the cookie is client-presented.
raw docstring

record-visitor-touchclj

(record-visitor-touch request)
(record-visitor-touch request {:keys [game-name cookie-domain]})

Public, unauthenticated: record an anonymous ?ref= visit. The visitor id is resolved server-side from the cookie (or minted); anything the client sends about who they are is ignored, mirroring event-payload's strip discipline.

The cookie is only set once MoM accepts the touch, so a token that resolves to no coseller cannot be used to mint visitor ids in a loop.

Options: :game-name overrides the runtime's registered game name, and :cookie-domain widens the cookie past the serving host.

Public, unauthenticated: record an anonymous `?ref=` visit. The visitor id is
resolved server-side from the cookie (or minted); anything the client sends
about who they are is ignored, mirroring event-payload's strip discipline.

The cookie is only set once MoM accepts the touch, so a token that resolves to
no coseller cannot be used to mint visitor ids in a loop.

Options: `:game-name` overrides the runtime's registered game name, and
`:cookie-domain` widens the cookie past the serving host.
raw docstring


One year. This MUST outlive the coseller policy's :window-days — a touch whose cookie expired first can never be claimed, so the chain silently truncates. If a GM ever raises :window-days beyond this, raise this too (see game.protocols.coseller/default-policy and resolve-policy).

One year. This MUST outlive the coseller policy's :window-days — a touch whose
cookie expired first can never be claimed, so the chain silently truncates. If
a GM ever raises :window-days beyond this, raise this too (see
game.protocols.coseller/default-policy and resolve-policy).
raw docstring

(with-visitor-cookie response request visitor)
(with-visitor-cookie response request visitor domain)

Attach the visitor cookie to response.

:domain is worth passing whenever the pages and the app are served from different subdomains: without it the browser scopes the id to the exact host that set it, the id splits per subdomain, and chains break silently.

Attach the visitor cookie to `response`.

`:domain` is worth passing whenever the pages and the app are served from
different subdomains: without it the browser scopes the id to the exact host
that set it, the id splits per subdomain, and chains break silently.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close