@@ -8,6 +8,8 @@ export interface TokenInfo {
88 readonly tokenType : string ;
99 /** Original expires_in from server response (seconds). */
1010 readonly expiresIn : number ;
11+ /** Provider-specific refresh metadata. Never contains access/refresh tokens. */
12+ readonly metadata ?: Readonly < Record < string , string > > | undefined ;
1113}
1214
1315/** JSON wire format for token persistence (snake_case, Python-compatible). */
@@ -18,6 +20,15 @@ export interface TokenInfoWire {
1820 readonly scope : string ;
1921 readonly token_type : string ;
2022 readonly expires_in : number ;
23+ readonly metadata ?: Readonly < Record < string , string > > | undefined ;
24+ }
25+
26+ function sanitizeMetadata ( value : unknown ) : Readonly < Record < string , string > > | undefined {
27+ if ( value === null || typeof value !== 'object' || Array . isArray ( value ) ) return undefined ;
28+ const entries = Object . entries ( value as Record < string , unknown > ) . filter (
29+ ( entry ) : entry is [ string , string ] => typeof entry [ 1 ] === 'string' ,
30+ ) ;
31+ return entries . length > 0 ? Object . fromEntries ( entries ) : undefined ;
2132}
2233
2334export function tokenToWire ( token : TokenInfo ) : TokenInfoWire {
@@ -28,6 +39,7 @@ export function tokenToWire(token: TokenInfo): TokenInfoWire {
2839 scope : token . scope ,
2940 token_type : token . tokenType ,
3041 expires_in : token . expiresIn ,
42+ ...( token . metadata === undefined ? { } : { metadata : token . metadata } ) ,
3143 } ;
3244}
3345
@@ -39,5 +51,6 @@ export function tokenFromWire(wire: Partial<TokenInfoWire>): TokenInfo {
3951 scope : wire . scope ?? '' ,
4052 tokenType : wire . token_type ?? '' ,
4153 expiresIn : typeof wire . expires_in === 'number' ? wire . expires_in : 0 ,
54+ metadata : sanitizeMetadata ( wire . metadata ) ,
4255 } ;
4356}
0 commit comments