# Visitor

> `Visitor.Id` names the browser someone is using. The platform issues it on their first visit, proves it on every request with a sealed HttpOnly cookie, and answers the same value in a page, in a server function and inside a `security` rule. It gives work started before sign-up — a shop's basket, a saved filter — somewhere to belong, and with `where VisitId == Visitor.Id` a guest's rows are their own browser's and nobody else's.

<!-- id: ui-visitor · area: ui · stability: stable · html: https://osysharp.com/reference/ui/visitor/ -->

## Summary        {#summary}
Plenty of what a person does happens **before they are anybody**. They browse a catalogue, fill a basket, set a filter
— and only then, if at all, do they sign up. `Session.CurrentUser` is null for all of it, which is correct and no help:
that work still has to live somewhere, and still has to be there after a refresh.

**`Visitor.Id`** is the missing name. The platform issues it to a browser on its first request and proves it on every
request after, so an app can key rows to *the visit that created them*, keep them the visitor's own, and hand them a
real owner later.

```osy title="a basket keyed to the visit" test app=ui-visitor
[Route("/cart")]
[AllowAnonymous]
[Render(CSR)]
component CartPage() {
  string visitId = Visitor.Id;                 // who this browser is, signed in or not
  live var cart = Cart.Include(c => c.Lines).SingleOrDefault(c => c.VisitId == visitId);
  render {
    if (cart != null) { foreach (var l in cart.Lines) { Text(l.Product.Name); } }
  }
}
```

**What this needs around it:** a `Cart` entity carrying the visit's `VisitId` and a `Lines` collection, a `CartLine`,
and a `security` block on each that says a guest's cart is its own visit's — [guest rows (a visit's own rows)](https://osysharp.com/reference/security/guest-rows/).
[The example below](#examples) declares all of it as one compiled app.

## Signature      {#signature}
```osy syntax
Visitor.Id     // string — the id the platform issued this browser. Never empty in a page or a request.
```

Available in every component and every function, with no `using`, and — as `Visitor.Id` — inside a `security` rule.

## Description    {#description}

### How stable is `Visitor.Id`? — same browser, same value   {#what}
The **same** value on every page of the app, after a reload, in every tab, and after signing in or out. A **different**
value in another browser, another profile, or a private window — and a **new** one if the visitor clears their site
data, which is what "forget me" should mean.

It is not derived from anything about the person: not their address, not their device, not their behaviour. It is a
random id the platform minted, and it says only *"this is the same browser as before"*.

### The platform issues it, and proves it   {#issued}
The id never comes from the browser. On the first request the platform mints it and sets a cookie carrying it,
**sealed** under the app's own key: the browser cannot read it from script (`HttpOnly`), another site cannot send it
(`SameSite=Lax`), and on HTTPS a sibling app on the same domain cannot plant one (`__Host-`). Every later request is
that visitor because its cookie opens under the key. A cookie that does not — edited, another app's, made up — is not
a visitor at all: that request gets a **fresh** id, never the one it claimed.

The page is told the id (never the cookie) when it boots, so `Visitor.Id` in a component and `Visitor.Id` in the
server function it calls are the **same value**.

### Read it on the server — don't pass it   {#server-side}
A server function reads `Visitor.Id` itself; the visit it acts on is the caller's own, not one the caller names:

```osy title="the server reads the caller's own visit" test app=ui-visitor
[Route("/products/{slug}")] [AllowAnonymous] [Render(CSR)]
component ProductPage(string slug) {
  var product = Product.SingleOrDefault(p => p.Name == slug);
  action Add(Product p) { AddToCart(p); }              // no "which visit" — the server knows
  render {
    if (product != null) { Pressable(onClick: () => Add(product)) { Text("Add to cart"); } }
  }
}
```

⚠ **Never take the visit as a parameter.** `void AddToCart(string visitId, …)` lets any caller name any visit — the
parameter is somebody else's the moment a caller sets it. Read `Visitor.Id` in the body instead, and let the entity's
rules say the same thing again ([guest rows (a visit's own rows)](https://osysharp.com/reference/security/guest-rows/)).

### Moving a visitor's rows to their account on sign-in   {#claiming}
The point of a visit-keyed row is that it becomes an owned one. When the visitor signs in, the app moves the rows it
cares about from the visit to the user — the handover it exists for. The visit is still the caller's own, so the
signed-in request reads it as the guest did:

```osy title="handing a visit's rows a real owner" test app=ui-visitor
void ClaimCart(User owner) {
  var visitId = Visitor.Id;
  var cart = Cart.SingleOrDefault(c => c.VisitId == visitId && c.Owner == null);
  if (cart != null) { cart.Owner = owner; }
}
```

Whether to claim, merge, or discard when the user already has rows of their own is the app's decision, and the platform
has no opinion: it supplies the id and nothing else.

### Where there is no visitor   {#no-visitor}
A workflow timer, a scheduled job or a background worker is not a request from a browser, so there `Visitor.Id` is
`null` — and a rule comparing against it matches nothing. Work of that kind reads guest rows as the shop (a staff grant,
or a workflow running as the system), never as a visitor.

### In a test   {#testing}
Each test is one fresh browser: a new visitor, and its `Ui.*` screen is handed the same one. To be a **second** browser,
say so:

```osy syntax
runas (new Visitor { Id = "guest-b" }) {
  Assert.Equal(0, CartSize());          // guest-b sees none of the first browser's cart
}
```

It changes only who the browser is — the signed-in principal, if any, stays — so `runas (new Visitor { … }) { runas
(Maja) { … } }` is "Maja, signing in on that browser". `Ui.*` verbs work inside it: the test's browser becomes that
visitor too.

## Examples       {#examples}
An anonymous basket — the whole shape, from an empty visit to a claimed cart, each guest reading only its own:

```osy title="visitor-cart" test app=ui-visitor
[Principal] entity User { string Email; }

entity Product {
  [Required, MaxLength(80)] string Name;
  decimal Price;
  security { allow read when IsAnonymous || IsAuthenticated; }   // a catalogue is public — that is what a shop is
}

entity Cart {
  [Required, MaxLength(64)] string VisitId;    // the visit this basket belongs to
  User Owner;                                  // null until someone signs in and claims it
  [ForeignKey(Cart)] CartLine[] Lines;
  // A guest's cart is its own visit's; a claimed one is its owner's.
  security { allow read, create, update when IsAnonymous || IsAuthenticated where Owner == null && VisitId == Visitor.Id || Owner == user; }
}

entity CartLine {
  [Required] Cart Cart;
  [Required] Product Product;
  int Quantity = 1;
  security {
    allow read, create, update when IsAnonymous || IsAuthenticated
      where Cart.Owner == null && Cart.VisitId == Visitor.Id || Cart.Owner == user;
  }
}

// The visit is the caller's own — read, never passed.
[AllowAnonymous]
void AddToCart(Product product) {
  var visitId = Visitor.Id;
  var cart = Cart.SingleOrDefault(c => c.VisitId == visitId);
  if (cart == null) { cart = new Cart { VisitId = visitId }; }
  var line = new CartLine { Cart = cart, Product = product };
  UnitOfWork.Commit();      // adding to a basket is a complete act — nothing else is going to commit it
}

[Route("/")]
[AllowAnonymous]
[Render(CSR)]
component Catalog() {
  live var products = Product.OrderBy(p => p.Name).ToList();

  action Add(Product p) { AddToCart(p); }

  render {
    Stack(gap: 3) {
      foreach (var p in products) {
        Row(gap: 2) {
          Text(p.Name);
          Button("Add to bag", onPress: () => Add(p));
        }
      }
    }
  }
}
```

## See also       {#see-also}
- [guest rows (a visit's own rows)](https://osysharp.com/reference/security/guest-rows/) — `where VisitId == Visitor.Id`: a guest's rows are their own browser's
- [routes and pages](https://osysharp.com/reference/ui/routing/) — protected-by-default routing, and the `[AllowAnonymous]` a visitor-facing page must declare
- [page authorization (policies)](https://osysharp.com/reference/ui/authorize/) — what a real authorization gate looks like
- [component](https://osysharp.com/reference/ui/component/) — where an ambient is read, and how a component holds it in a field
