@zucker-framework/fe-storage 1.0.2 → 1.0.5

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/README.md CHANGED
@@ -2,4 +2,6 @@
2
2
 
3
3
  `LocalStore` provides the shared browser storage and authentication-key contract, including best-effort handling of unavailable storage.
4
4
 
5
+ `setCookie(name, value, options?)`, `getCookie(name)` and `removeCookie(name, options?)` manage browser-readable cookies with SSR-safe no-op writes/null reads. Values are percent-encoded; malformed legacy encoding is returned unchanged. Names and attributes are validated before writing. `CookieOptions` supports `path`, `domain`, `expires`, `maxAge` (seconds), `sameSite` and `secure`; no authentication key, lifetime, domain or SameSite policy is hard-coded. Deletion must supply the same path/domain. HttpOnly cookies must be issued by the server, never by this API. New exports require a released artifact before production consumers switch.
6
+
5
7
  `downloadBlob(blob, fileName, { revokeDelayMs? }?)` triggers a caller-requested browser download. It owns the temporary anchor and object URL, cleaning both after the click, including click failures. URL cleanup is immediate by default; a caller with an existing delayed-revocation requirement can provide that delay. Applications retain the filename, MIME type and when to download. This helper does not perform HTTP requests and throws outside a browser.
package/dist/index.d.mts CHANGED
@@ -46,4 +46,20 @@ declare function downloadBlob(blob: Blob, fileName: string, options?: {
46
46
  revokeDelayMs?: number;
47
47
  }): void;
48
48
 
49
- export { LocalStore, REFRESH_TOKEN_KEY, TOKEN_KEY, USER_KEY, downloadBlob };
49
+ interface CookieOptions {
50
+ path?: string;
51
+ domain?: string;
52
+ expires?: Date;
53
+ /** Lifetime in seconds, not milliseconds. */
54
+ maxAge?: number;
55
+ sameSite?: 'Strict' | 'Lax' | 'None';
56
+ secure?: boolean;
57
+ }
58
+ /** Browser-readable cookies only. HttpOnly credentials must be set by a server. */
59
+ declare function setCookie(name: string, value: string, options?: CookieOptions): void;
60
+ /** Exact-name lookup; malformed percent encoding is retained as a legacy raw value. */
61
+ declare function getCookie(name: string): string | null;
62
+ /** Deletion must use the same path/domain as the original cookie. */
63
+ declare function removeCookie(name: string, options?: Omit<CookieOptions, 'expires' | 'maxAge'>): void;
64
+
65
+ export { type CookieOptions, LocalStore, REFRESH_TOKEN_KEY, TOKEN_KEY, USER_KEY, downloadBlob, getCookie, removeCookie, setCookie };
package/dist/index.d.ts CHANGED
@@ -46,4 +46,20 @@ declare function downloadBlob(blob: Blob, fileName: string, options?: {
46
46
  revokeDelayMs?: number;
47
47
  }): void;
48
48
 
49
- export { LocalStore, REFRESH_TOKEN_KEY, TOKEN_KEY, USER_KEY, downloadBlob };
49
+ interface CookieOptions {
50
+ path?: string;
51
+ domain?: string;
52
+ expires?: Date;
53
+ /** Lifetime in seconds, not milliseconds. */
54
+ maxAge?: number;
55
+ sameSite?: 'Strict' | 'Lax' | 'None';
56
+ secure?: boolean;
57
+ }
58
+ /** Browser-readable cookies only. HttpOnly credentials must be set by a server. */
59
+ declare function setCookie(name: string, value: string, options?: CookieOptions): void;
60
+ /** Exact-name lookup; malformed percent encoding is retained as a legacy raw value. */
61
+ declare function getCookie(name: string): string | null;
62
+ /** Deletion must use the same path/domain as the original cookie. */
63
+ declare function removeCookie(name: string, options?: Omit<CookieOptions, 'expires' | 'maxAge'>): void;
64
+
65
+ export { type CookieOptions, LocalStore, REFRESH_TOKEN_KEY, TOKEN_KEY, USER_KEY, downloadBlob, getCookie, removeCookie, setCookie };
package/dist/index.js CHANGED
@@ -24,7 +24,10 @@ __export(index_exports, {
24
24
  REFRESH_TOKEN_KEY: () => REFRESH_TOKEN_KEY,
25
25
  TOKEN_KEY: () => TOKEN_KEY,
26
26
  USER_KEY: () => USER_KEY,
27
- downloadBlob: () => downloadBlob
27
+ downloadBlob: () => downloadBlob,
28
+ getCookie: () => getCookie,
29
+ removeCookie: () => removeCookie,
30
+ setCookie: () => setCookie
28
31
  });
29
32
  module.exports = __toCommonJS(index_exports);
30
33
 
@@ -128,11 +131,70 @@ function downloadBlob(blob, fileName, options = {}) {
128
131
  }
129
132
  }
130
133
  }
134
+
135
+ // src/cookies.ts
136
+ function validateName(name) {
137
+ if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name)) {
138
+ throw new TypeError("Invalid cookie name");
139
+ }
140
+ }
141
+ function setCookie(name, value, options = {}) {
142
+ validateName(name);
143
+ const parts = [`${name}=${encodeURIComponent(value)}`];
144
+ if (options.path !== void 0) {
145
+ if (!options.path.startsWith("/") || /[;\x00-\x1f\x7f]/.test(options.path)) {
146
+ throw new TypeError("Invalid cookie path");
147
+ }
148
+ parts.push(`Path=${options.path}`);
149
+ }
150
+ if (options.domain !== void 0) {
151
+ if (!/^\.?[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i.test(options.domain)) {
152
+ throw new TypeError("Invalid cookie domain");
153
+ }
154
+ parts.push(`Domain=${options.domain}`);
155
+ }
156
+ if (options.expires !== void 0) {
157
+ if (!Number.isFinite(options.expires.getTime())) throw new TypeError("Invalid cookie expiry");
158
+ parts.push(`Expires=${options.expires.toUTCString()}`);
159
+ }
160
+ if (options.maxAge !== void 0) {
161
+ if (!Number.isSafeInteger(options.maxAge)) throw new TypeError("Invalid cookie max age");
162
+ parts.push(`Max-Age=${options.maxAge}`);
163
+ }
164
+ if (options.sameSite !== void 0) {
165
+ if (!["Strict", "Lax", "None"].includes(options.sameSite)) throw new TypeError("Invalid cookie SameSite");
166
+ parts.push(`SameSite=${options.sameSite}`);
167
+ }
168
+ if (options.secure) parts.push("Secure");
169
+ if (typeof document !== "undefined") document.cookie = parts.join("; ");
170
+ }
171
+ function getCookie(name) {
172
+ validateName(name);
173
+ if (typeof document === "undefined") return null;
174
+ const prefix = `${name}=`;
175
+ for (const entry of document.cookie.split(";")) {
176
+ const cookie = entry.trimStart();
177
+ if (!cookie.startsWith(prefix)) continue;
178
+ const value = cookie.slice(prefix.length);
179
+ try {
180
+ return decodeURIComponent(value);
181
+ } catch {
182
+ return value;
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ function removeCookie(name, options = {}) {
188
+ setCookie(name, "", { ...options, expires: /* @__PURE__ */ new Date(0), maxAge: 0 });
189
+ }
131
190
  // Annotate the CommonJS export names for ESM import in node:
132
191
  0 && (module.exports = {
133
192
  LocalStore,
134
193
  REFRESH_TOKEN_KEY,
135
194
  TOKEN_KEY,
136
195
  USER_KEY,
137
- downloadBlob
196
+ downloadBlob,
197
+ getCookie,
198
+ removeCookie,
199
+ setCookie
138
200
  });
package/dist/index.mjs CHANGED
@@ -98,10 +98,69 @@ function downloadBlob(blob, fileName, options = {}) {
98
98
  }
99
99
  }
100
100
  }
101
+
102
+ // src/cookies.ts
103
+ function validateName(name) {
104
+ if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name)) {
105
+ throw new TypeError("Invalid cookie name");
106
+ }
107
+ }
108
+ function setCookie(name, value, options = {}) {
109
+ validateName(name);
110
+ const parts = [`${name}=${encodeURIComponent(value)}`];
111
+ if (options.path !== void 0) {
112
+ if (!options.path.startsWith("/") || /[;\x00-\x1f\x7f]/.test(options.path)) {
113
+ throw new TypeError("Invalid cookie path");
114
+ }
115
+ parts.push(`Path=${options.path}`);
116
+ }
117
+ if (options.domain !== void 0) {
118
+ if (!/^\.?[a-z0-9-]+(?:\.[a-z0-9-]+)*$/i.test(options.domain)) {
119
+ throw new TypeError("Invalid cookie domain");
120
+ }
121
+ parts.push(`Domain=${options.domain}`);
122
+ }
123
+ if (options.expires !== void 0) {
124
+ if (!Number.isFinite(options.expires.getTime())) throw new TypeError("Invalid cookie expiry");
125
+ parts.push(`Expires=${options.expires.toUTCString()}`);
126
+ }
127
+ if (options.maxAge !== void 0) {
128
+ if (!Number.isSafeInteger(options.maxAge)) throw new TypeError("Invalid cookie max age");
129
+ parts.push(`Max-Age=${options.maxAge}`);
130
+ }
131
+ if (options.sameSite !== void 0) {
132
+ if (!["Strict", "Lax", "None"].includes(options.sameSite)) throw new TypeError("Invalid cookie SameSite");
133
+ parts.push(`SameSite=${options.sameSite}`);
134
+ }
135
+ if (options.secure) parts.push("Secure");
136
+ if (typeof document !== "undefined") document.cookie = parts.join("; ");
137
+ }
138
+ function getCookie(name) {
139
+ validateName(name);
140
+ if (typeof document === "undefined") return null;
141
+ const prefix = `${name}=`;
142
+ for (const entry of document.cookie.split(";")) {
143
+ const cookie = entry.trimStart();
144
+ if (!cookie.startsWith(prefix)) continue;
145
+ const value = cookie.slice(prefix.length);
146
+ try {
147
+ return decodeURIComponent(value);
148
+ } catch {
149
+ return value;
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ function removeCookie(name, options = {}) {
155
+ setCookie(name, "", { ...options, expires: /* @__PURE__ */ new Date(0), maxAge: 0 });
156
+ }
101
157
  export {
102
158
  LocalStore,
103
159
  REFRESH_TOKEN_KEY,
104
160
  TOKEN_KEY,
105
161
  USER_KEY,
106
- downloadBlob
162
+ downloadBlob,
163
+ getCookie,
164
+ removeCookie,
165
+ setCookie
107
166
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zucker-framework/fe-storage",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",