lambder 3.3.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Lambder.d.ts CHANGED
@@ -130,7 +130,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
130
130
  enableSlidingExpiration?: boolean;
131
131
  /** Min seconds between sliding-expiration writes. Default: max(60, 5% of TTL). */
132
132
  slidingWriteIntervalSeconds?: number;
133
- /** Session cookie attributes, e.g. { domain: ".example.com" } for cross-subdomain sessions. */
133
+ /** Session cookie attributes, e.g. { domain: ".example.com" } for cross-subdomain sessions. `domain` may be a (hostname) => string function for multi-domain deployments. */
134
134
  cookie?: LambderSessionCookieOptions;
135
135
  partitionKey?: string;
136
136
  sortKey?: string;
@@ -41,7 +41,8 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
41
41
  private fetchEndedHandler?;
42
42
  private sessionTokenCookieKey;
43
43
  private sessionCsrfCookieKey;
44
- constructor({ apiPath, apiVersion, isCorsEnabled, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, }: {
44
+ private sessionCookieDomain?;
45
+ constructor({ apiPath, apiVersion, isCorsEnabled, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, sessionCookieDomain, }: {
45
46
  apiPath: string;
46
47
  apiVersion?: string;
47
48
  isCorsEnabled: boolean;
@@ -54,8 +55,11 @@ export default class LambderCaller<TContract extends ApiContractShape = any> {
54
55
  fetchStartedHandler?: FetchStartEventHandler;
55
56
  fetchEndedHandler?: FetchEndEventHandler;
56
57
  apiInputValidationErrorHandler?: ValidationErrorHandler;
58
+ /** Must mirror the server's session cookie Domain, otherwise expired cookies cannot be cleared. */
59
+ sessionCookieDomain?: string | ((hostname: string) => string | undefined | null);
57
60
  });
58
61
  setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): void;
62
+ private clearSessionCookies;
59
63
  apiRaw<TApiName extends keyof TContract & string = string, TOutput = TApiName extends keyof TContract ? TContract[TApiName]['output'] : any>(apiName: TApiName, payload?: TApiName extends keyof TContract ? TContract[TApiName]['input'] : any, options?: {
60
64
  headers?: Record<string, any>;
61
65
  versionExpiredHandler?: VoidFunction;
@@ -16,10 +16,12 @@ export default class LambderCaller {
16
16
  fetchEndedHandler;
17
17
  sessionTokenCookieKey = "LMDRSESSIONTKID";
18
18
  sessionCsrfCookieKey = "LMDRSESSIONCSTK";
19
- constructor({ apiPath, apiVersion, isCorsEnabled = false, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, }) {
19
+ sessionCookieDomain;
20
+ constructor({ apiPath, apiVersion, isCorsEnabled = false, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, sessionCookieDomain, }) {
20
21
  this.apiPath = apiPath ?? "/api";
21
22
  this.apiVersion = apiVersion;
22
23
  this.isCorsEnabled = isCorsEnabled;
24
+ this.sessionCookieDomain = sessionCookieDomain;
23
25
  this.versionExpiredHandler = versionExpiredHandler;
24
26
  this.sessionExpiredHandler = sessionExpiredHandler;
25
27
  this.messageHandler = messageHandler;
@@ -35,6 +37,17 @@ export default class LambderCaller {
35
37
  this.sessionTokenCookieKey = sessionTokenCookieKey;
36
38
  this.sessionCsrfCookieKey = sessionCsrfCookieKey;
37
39
  }
40
+ clearSessionCookies() {
41
+ const domainOption = this.sessionCookieDomain;
42
+ const hostname = typeof window !== "undefined" ? window.location.hostname : "";
43
+ const resolvedDomain = typeof domainOption === "function" ? domainOption(hostname) : domainOption;
44
+ for (const key of [this.sessionTokenCookieKey, this.sessionCsrfCookieKey]) {
45
+ // Host-only and domain-scoped cookies are distinct entries; clear both.
46
+ Cookies.remove(key);
47
+ if (resolvedDomain)
48
+ Cookies.remove(key, { domain: resolvedDomain, path: "/" });
49
+ }
50
+ }
38
51
  async apiRaw(apiName, payload, options) {
39
52
  const headers = options?.headers;
40
53
  const fetchTracker = { apiName, done: false, fetchEndCalled: false };
@@ -102,8 +115,7 @@ export default class LambderCaller {
102
115
  return null;
103
116
  }
104
117
  if (data && data.sessionExpired) {
105
- Cookies.set(this.sessionTokenCookieKey, '', { expires: -1 });
106
- Cookies.set(this.sessionCsrfCookieKey, '', { expires: -1 });
118
+ this.clearSessionCookies();
107
119
  if (this.sessionExpiredHandler) {
108
120
  await this.sessionExpiredHandler();
109
121
  }
@@ -2,8 +2,12 @@ import { LambderRenderContext, LambderSessionRenderContext } from "./LambderCont
2
2
  import type LambderSessionManager from "./LambderSessionManager.js";
3
3
  import type { LambderSessionContext } from "./LambderSessionManager.js";
4
4
  export type LambderSessionCookieOptions = {
5
- /** e.g. ".example.com" to share sessions across subdomains. */
6
- domain?: string;
5
+ /**
6
+ * e.g. ".example.com" to share sessions across subdomains. Pass a function to
7
+ * derive it from the request hostname when one deployment serves several
8
+ * apex domains; return undefined for a host-only cookie.
9
+ */
10
+ domain?: string | ((hostname: string) => string | undefined | null);
7
11
  path?: string;
8
12
  sameSite?: "Strict" | "Lax" | "None";
9
13
  secure?: boolean;
@@ -14,11 +14,14 @@ export default class LambderSessionController {
14
14
  ;
15
15
  buildCookie(key, value, expiresAtMs, httpOnly) {
16
16
  const { domain, path = "/", sameSite = "Lax", secure = true } = this.cookieOptions;
17
+ // Host header can carry a port; browsers match the Domain attribute on hostname only.
18
+ const hostname = (this.ctx.host || "").split(":")[0];
19
+ const resolvedDomain = typeof domain === "function" ? domain(hostname) : domain;
17
20
  const parts = [
18
21
  `${key}=${value}`,
19
22
  `Expires=${new Date(expiresAtMs).toUTCString()}`,
20
23
  `Path=${path}`,
21
- ...(domain ? [`Domain=${domain}`] : []),
24
+ ...(resolvedDomain ? [`Domain=${resolvedDomain}`] : []),
22
25
  ...(httpOnly ? ["HttpOnly"] : []),
23
26
  `SameSite=${sameSite}`,
24
27
  ...(secure ? ["Secure"] : []),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",