| 1 | |
| 2 | * react-router v8.3.0
|
| 3 | *
|
| 4 | * Copyright (c) Remix Software Inc.
|
| 5 | *
|
| 6 | * This source code is licensed under the MIT license found in the
|
| 7 | * LICENSE.md file in the root directory of this source tree.
|
| 8 | *
|
| 9 | * @license MIT
|
| 10 | */
|
| 11 | import { warnOnce } from "./warnings.js";
|
| 12 | import { createCookie, isCookie } from "./cookies.js";
|
| 13 |
|
| 14 | function flash(name) {
|
| 15 | return `__flash_${name}__`;
|
| 16 | }
|
| 17 | |
| 18 | * Creates a new Session object.
|
| 19 | *
|
| 20 | * Note: This function is typically not invoked directly by application code.
|
| 21 | * Instead, use a `SessionStorage` object's `getSession` method.
|
| 22 | *
|
| 23 | * @category Utils
|
| 24 | * @param initialData The initial data for the session.
|
| 25 | * @param id The identifier for the session. Defaults to an empty string for a
|
| 26 | * new session.
|
| 27 | * @returns A new {@link Session} object.
|
| 28 | */
|
| 29 | const createSession = (initialData = {}, id = "") => {
|
| 30 | let map = new Map(Object.entries(initialData));
|
| 31 | return {
|
| 32 | get id() {
|
| 33 | return id;
|
| 34 | },
|
| 35 | get data() {
|
| 36 | return Object.fromEntries(map);
|
| 37 | },
|
| 38 | has(name) {
|
| 39 | return map.has(name) || map.has(flash(name));
|
| 40 | },
|
| 41 | get(name) {
|
| 42 | if (map.has(name)) return map.get(name);
|
| 43 | let flashName = flash(name);
|
| 44 | if (map.has(flashName)) {
|
| 45 | let value = map.get(flashName);
|
| 46 | map.delete(flashName);
|
| 47 | return value;
|
| 48 | }
|
| 49 | },
|
| 50 | set(name, value) {
|
| 51 | map.set(name, value);
|
| 52 | },
|
| 53 | flash(name, value) {
|
| 54 | map.set(flash(name), value);
|
| 55 | },
|
| 56 | unset(name) {
|
| 57 | map.delete(name);
|
| 58 | }
|
| 59 | };
|
| 60 | };
|
| 61 | |
| 62 | * Returns `true` if a value is a React Router {@link Session} object.
|
| 63 | *
|
| 64 | * @public
|
| 65 | * @category Utils
|
| 66 | * @mode framework
|
| 67 | * @mode data
|
| 68 | * @param object The value to check.
|
| 69 | * @returns `true` if the value is a React Router {@link Session} object;
|
| 70 | * otherwise, `false`.
|
| 71 | */
|
| 72 | const isSession = (object) => {
|
| 73 | return object != null && typeof object.id === "string" && typeof object.data !== "undefined" && typeof object.has === "function" && typeof object.get === "function" && typeof object.set === "function" && typeof object.flash === "function" && typeof object.unset === "function";
|
| 74 | };
|
| 75 | |
| 76 | * Creates a SessionStorage object using a SessionIdStorageStrategy.
|
| 77 | *
|
| 78 | * Note: This is a low-level API that should only be used if none of the
|
| 79 | * existing session storage options meet your requirements.
|
| 80 | *
|
| 81 | * @category Utils
|
| 82 | * @param strategy The strategy used to store session identifiers and data.
|
| 83 | * @returns A {@link SessionStorage} object that persists session data using the
|
| 84 | * provided strategy.
|
| 85 | */
|
| 86 | function createSessionStorage({ cookie: cookieArg, createData, readData, updateData, deleteData }) {
|
| 87 | let cookie = isCookie(cookieArg) ? cookieArg : createCookie(cookieArg?.name || "__session", cookieArg);
|
| 88 | warnOnceAboutSigningSessionCookie(cookie);
|
| 89 | return {
|
| 90 | async getSession(cookieHeader, options) {
|
| 91 | let id = cookieHeader && await cookie.parse(cookieHeader, options);
|
| 92 | return createSession(id && await readData(id) || {}, id || "");
|
| 93 | },
|
| 94 | async commitSession(session, options) {
|
| 95 | let { id, data } = session;
|
| 96 | let expires = options?.maxAge != null ? new Date(Date.now() + options.maxAge * 1e3) : options?.expires != null ? options.expires : cookie.expires;
|
| 97 | if (id) await updateData(id, data, expires);
|
| 98 | else id = await createData(data, expires);
|
| 99 | return cookie.serialize(id, options);
|
| 100 | },
|
| 101 | async destroySession(session, options) {
|
| 102 | await deleteData(session.id);
|
| 103 | return cookie.serialize("", {
|
| 104 | ...options,
|
| 105 | maxAge: void 0,
|
| 106 | expires: new Date(0)
|
| 107 | });
|
| 108 | }
|
| 109 | };
|
| 110 | }
|
| 111 | function warnOnceAboutSigningSessionCookie(cookie) {
|
| 112 | warnOnce(cookie.isSigned, `The "${cookie.name}" cookie is not signed, but session cookies should be signed to prevent tampering on the client before they are sent back to the server. See https://reactrouter.com/explanation/sessions-and-cookies#signing-cookies for more information.`);
|
| 113 | }
|
| 114 |
|
| 115 | export { createSession, createSessionStorage, isSession, warnOnceAboutSigningSessionCookie };
|