@@ -21,6 +21,13 @@ interface BookmarkResponse {
2121 bookmarks : Bookmark [ ] ;
2222}
2323
24+ interface StorytelAuthError extends Error {
25+ isStorytelUnauthorized : boolean ;
26+ storytelStatus ?: number ;
27+ storytelData ?: unknown ;
28+ isLoginFailure ?: boolean ;
29+ }
30+
2431class StorytelClient {
2532 private client : AxiosInstance ;
2633 public loginData : LoginData ;
@@ -77,6 +84,7 @@ class StorytelClient {
7784 } ,
7885 ( error ) => {
7986 const url = error . config ?. url || "" ;
87+ const isLoginRequest = url . includes ( "login.action" ) ;
8088 let cleanUrl = url ;
8189 if ( cleanUrl . includes ( "login.action" ) ) {
8290 cleanUrl = cleanUrl . replace ( / p w d = [ ^ & ] + / , "pwd=***" ) ;
@@ -92,8 +100,15 @@ class StorytelClient {
92100 // Propagate Storytel 401 as a distinct error type so Fastify routes
93101 // can return 401 to the frontend instead of a generic 500.
94102 if ( error . response ?. status === 401 ) {
95- const authError : any = new Error ( "Storytel session expired" ) ;
103+ const authError : StorytelAuthError = new Error (
104+ isLoginRequest
105+ ? "Storytel login rejected"
106+ : "Storytel session expired" ,
107+ ) as StorytelAuthError ;
96108 authError . isStorytelUnauthorized = true ;
109+ authError . isLoginFailure = isLoginRequest ;
110+ authError . storytelStatus = error . response . status ;
111+ authError . storytelData = error . response . data ;
97112 return Promise . reject ( authError ) ;
98113 }
99114 return Promise . reject ( error ) ;
@@ -109,14 +124,26 @@ class StorytelClient {
109124 }
110125
111126 async login ( email : string , password : string ) : Promise < LoginData > {
112- const encryptedPassword = encryptPassword ( password . trim ( ) ) ;
113- const url = `https://www.storytel.com/api/login.action?m=1&uid=${ email . trim ( ) } &pwd=${ encryptedPassword } ` ;
127+ const trimmedEmail = email . trim ( ) ;
128+ const trimmedPassword = password . trim ( ) ;
129+ const encryptedPassword = encryptPassword ( trimmedPassword ) ;
130+ const url = "https://www.storytel.com/api/login.action" ;
131+ const params = {
132+ m : 1 ,
133+ uid : trimmedEmail ,
134+ pwd : encryptedPassword ,
135+ } ;
114136
115137 try {
116- const response = await this . client . get < LoginData > ( url ) ;
138+ const response = await this . client . get < LoginData > ( url , {
139+ params,
140+ } ) ;
117141 this . loginData = response . data ;
118142 return this . loginData ;
119143 } catch ( error : any ) {
144+ if ( error . isStorytelUnauthorized ) {
145+ throw error ;
146+ }
120147 throw new Error ( `Login failed: ${ error . message } ` ) ;
121148 }
122149 }
0 commit comments