1- /**
2- * @typedef {import("./interface.ts").CookieOptions } CookieOptions
3- */
1+ import { $injectTokens } from "../../injection-tokens.js" ;
2+ import {
3+ assert ,
4+ isDefined ,
5+ isFunction ,
6+ isNullOrUndefined ,
7+ isNumber ,
8+ isObject ,
9+ isString ,
10+ } from "../../shared/utils.js" ;
411
512/**
613 * @returns {Record<string,string> }
@@ -13,6 +20,7 @@ function parseCookies() {
1320 const parts = document . cookie . split ( "; " ) ;
1421 for ( const part of parts ) {
1522 const eq = part . indexOf ( "=" ) ;
23+ if ( eq === - 1 ) continue ; // skip malformed cookie
1624 const key = decodeURIComponent ( part . substring ( 0 , eq ) ) ;
1725 const val = decodeURIComponent ( part . substring ( eq + 1 ) ) ;
1826 out [ key ] = val ;
@@ -23,27 +31,70 @@ function parseCookies() {
2331/** Utility: stringify options */
2432/**
2533 *
26- * @param {CookieOptions } opts
34+ * @param {ng.CookieOptions } opts
35+ * @returns {string }
36+ */
37+ /**
38+ * Build cookie options string from an options object.
39+ * Safely validates types for path, domain, expires, secure, and samesite.
40+ *
41+ * @param {ng.CookieOptions } opts
2742 * @returns {string }
43+ * @throws {TypeError } if any of options are invalid
2844 */
2945function buildOptions ( opts = { } ) {
30- let s = "" ;
46+ const parts = [ ] ;
3147
32- if ( opts . path ) s += `;path=${ opts . path } ` ;
33- if ( opts . domain ) s += `;domain=${ opts . domain } ` ;
48+ // Path
49+ if ( isDefined ( opts . path ) ) {
50+ if ( ! isString ( opts . path ) ) throw new TypeError ( `badarg:path ${ opts . path } ` ) ;
51+ parts . push ( `path=${ opts . path } ` ) ;
52+ }
3453
35- if ( opts . expires ) {
36- const exp =
37- opts . expires instanceof Date
38- ? opts . expires . toUTCString ( )
39- : new Date ( opts . expires ) . toUTCString ( ) ;
40- s += `;expires=${ exp } ` ;
54+ // Domain
55+ if ( isDefined ( opts . domain ) ) {
56+ if ( ! isString ( opts . domain ) )
57+ throw new TypeError ( `badarg:domain ${ opts . domain } ` ) ;
58+ parts . push ( `domain=${ opts . domain } ` ) ;
4159 }
4260
43- if ( opts . secure ) s += `;secure` ;
44- if ( opts . samesite ) s += `;samesite=${ opts . samesite } ` ;
61+ // Expires
62+ if ( opts . expires != null ) {
63+ let expDate ;
64+
65+ if ( opts . expires instanceof Date ) {
66+ expDate = opts . expires ;
67+ } else if ( isNumber ( opts . expires ) || isString ( opts . expires ) ) {
68+ expDate = new Date ( opts . expires ) ;
69+ } else {
70+ throw new TypeError ( `badarg:expires ${ String ( opts . expires ) } ` ) ;
71+ }
72+
73+ if ( isNaN ( expDate . getTime ( ) ) ) {
74+ throw new TypeError ( `badarg:expires ${ String ( opts . expires ) } ` ) ;
75+ }
4576
46- return s ;
77+ parts . push ( `expires=${ expDate . toUTCString ( ) } ` ) ;
78+ }
79+
80+ // Secure
81+ if ( opts . secure ) {
82+ parts . push ( "secure" ) ;
83+ }
84+
85+ // SameSite
86+ if ( isDefined ( opts . samesite ) ) {
87+ if ( ! isString ( opts . samesite ) )
88+ throw new TypeError ( `badarg:samesite ${ opts . samesite } ` ) ;
89+ const s = opts . samesite . toLowerCase ( ) ;
90+ if ( ! [ "lax" , "strict" , "none" ] . includes ( s ) ) {
91+ throw new TypeError ( `badarg:samesite ${ opts . samesite } ` ) ;
92+ }
93+ parts . push ( `samesite=${ s } ` ) ;
94+ }
95+
96+ // Join all parts with semicolons
97+ return parts . length ? ";" + parts . join ( ";" ) : "" ;
4798}
4899
49100/**
@@ -58,10 +109,14 @@ export class CookieService {
58109 /**
59110 * @param {ng.CookieOptions } defaults
60111 * Default cookie attributes defined by `$cookiesProvider.defaults`.
112+ * @param {ng.ExceptionHandlerService } $exceptionHandler
61113 */
62- constructor ( defaults ) {
63- /** @type {CookieOptions } */
64- this . defaults = defaults ;
114+ constructor ( defaults , $exceptionHandler ) {
115+ assert ( isObject ( defaults ) , "badarg" ) ;
116+ assert ( isFunction ( $exceptionHandler ) , "badarg" ) ;
117+ /** @type {ng.CookieOptions } */
118+ this . defaults = Object . freeze ( { ...defaults } ) ;
119+ this . $exceptionHandler = $exceptionHandler ;
65120 }
66121
67122 /**
@@ -71,6 +126,7 @@ export class CookieService {
71126 * @returns {string|null }
72127 */
73128 get ( key ) {
129+ assert ( isString ( key ) , "badarg" ) ;
74130 const all = parseCookies ( ) ;
75131 return all [ key ] || null ;
76132 }
@@ -81,14 +137,18 @@ export class CookieService {
81137 * @template T
82138 * @param {string } key
83139 * @returns {T|null }
140+ * @throws {SyntaxError } if cookie JSON is invalid
84141 */
85142 getObject ( key ) {
143+ assert ( isString ( key ) , "badarg" ) ;
86144 const raw = this . get ( key ) ;
87145 if ( ! raw ) return null ;
88146 try {
89147 return /** @type {T } */ ( JSON . parse ( raw ) ) ;
90- } catch {
91- return null ;
148+ } catch ( err ) {
149+ const error = new SyntaxError ( `badparse: "${ key } " => ${ err . message } ` ) ;
150+ this . $exceptionHandler ( error ) ;
151+ throw error ;
92152 }
93153 }
94154
@@ -106,35 +166,53 @@ export class CookieService {
106166 *
107167 * @param {string } key
108168 * @param {string } value
109- * @param {CookieOptions } [options]
169+ * @param {ng. CookieOptions } [options]
110170 */
111171 put ( key , value , options = { } ) {
172+ assert ( isString ( key ) , "badarg: key" ) ;
173+ assert ( isString ( value ) , "badarg: value" ) ;
112174 const encodedKey = encodeURIComponent ( key ) ;
113175 const encodedVal = encodeURIComponent ( value ) ;
114176
115- document . cookie =
116- `${ encodedKey } =${ encodedVal } ` +
117- buildOptions ( { ...this . defaults , ...options } ) ;
177+ try {
178+ document . cookie =
179+ `${ encodedKey } =${ encodedVal } ` +
180+ buildOptions ( { ...this . defaults , ...options } ) ;
181+ } catch ( e ) {
182+ this . $exceptionHandler ( e ) ;
183+ throw e ;
184+ }
118185 }
119186
120187 /**
121188 * Serializes an object as JSON and stores it as a cookie.
122189 *
123190 * @param {string } key
124191 * @param {any } value
125- * @param {CookieOptions } [options]
192+ * @param {ng.CookieOptions } [options]
193+ * @throws {TypeError } if Object cannot be converted to JSON
126194 */
127195 putObject ( key , value , options ) {
128- this . put ( key , JSON . stringify ( value ) , options ) ;
196+ assert ( isString ( key ) , "badarg: key" ) ;
197+ assert ( ! isNullOrUndefined ( key ) , "badarg: key" ) ;
198+ try {
199+ const str = JSON . stringify ( value ) ;
200+ this . put ( key , str , options ) ;
201+ } catch ( err ) {
202+ const error = new TypeError ( `badserialize: "${ key } " => ${ err . message } ` ) ;
203+ this . $exceptionHandler ( error ) ;
204+ throw error ;
205+ }
129206 }
130207
131208 /**
132209 * Removes a cookie by setting an expired date.
133210 *
134211 * @param {string } key
135- * @param {CookieOptions } [options]
212+ * @param {ng. CookieOptions } [options]
136213 */
137214 remove ( key , options = { } ) {
215+ assert ( isString ( key ) , "badarg" ) ;
138216 this . put ( key , "" , {
139217 ...this . defaults ,
140218 ...options ,
@@ -148,7 +226,9 @@ export class CookieProvider {
148226 this . defaults = { } ;
149227 }
150228
151- $get ( ) {
152- return new CookieService ( this . defaults ) ;
153- }
229+ $get = [
230+ $injectTokens . $exceptionHandler ,
231+ /** @param {ng.ExceptionHandlerService } $exceptionHandler */
232+ ( $exceptionHandler ) => new CookieService ( this . defaults , $exceptionHandler ) ,
233+ ] ;
154234}
0 commit comments