SSR / Pages that work without JavaScript
State between two posts
A post starts from init. Whatever must outlive it lives where the next
request reads it: domains-plus keeps its cart in a cookie, through
Mirror.cookie (foldkit-mirror), which the server and the scripted page
share:
export const CartInCookie = Mirror.cookie(App, {
name: 'cart',
fields: [App.model.cart],
keys: { cart: { codec: Cart.CartCookie } },
})
// The server, for every page: the cart's names in the request's cookie.
const held = CartInCookie.keysOf(request.headers.get('cookie'))
// The server, answering a post: the Set-Cookie for the cart the post reached.
CartInCookie.setCookie(reached, request)The scripted page brings CartInCookie.wiring() into its assembly, and its
Subscription writes document.cookie as the cart changes, so a cart filled
with scripts is there on a reload without them, and the other way round.
- Reading it is validating it. The cookie is the visitor's to edit. The mirror reads only its own keys, and nothing from a cookie past 4096 bytes; the server decodes its names with a Schema, drops repeats, keeps at most 50, and prices each again from its own data; a taken or unknown name is dropped.
- The posted Message is validated three times: its Schema (a name has a
length), the plan (an active Surface must list it), and
update, which adds a name only if the Model read it as available, at the price it read. - Writing it needs the Model the post reached.
SSR.entry'sheaders(request, model)is given it, and domains-plus answers a post withCartInCookie.setCookie(model, request).headersis called for every answer, aGETand a refusal too, so it writes the cookie only for aPOST. - A page drawn with someone's cart is theirs. Answer it
cache-control: private, no-store. - Answer a post with a redirect.
answerPost: 'redirect'answers it303to the address it was posted to, so the page the reader sees is aGET, and a reload asks for it again instead of posting twice. What the post changed reaches thatGETthrough the cookieheaderswrote; the page shows nothing else of the post. A refused post is still a400.
The cookie keeps the cart's names alone, apart by a +: a name holds no
price, so the cart's codec (Cart.CartCookie) encodes the names and refuses
to decode them, and the server reads them with Cart.CartNames and prices
each. 50 names of the longest a search makes (a 63-character label and the
longest extension) take 3,959 of the cookie's 4096 bytes; past that the
mirror writes nothing and reports CookieTooLarge, and the cookie keeps the
cart it held. The server reads at most 50 names, however short.