1- import axios , { AxiosError , AxiosResponse } from 'axios' ;
21import * as config from './config' ;
32import { detect } from 'detect-browser' ;
43import { SnackReporter } from './snack/SnackManager' ;
54import { observable , runInAction , action } from 'mobx' ;
65import { IClient , IUser } from './types' ;
6+ import { identityTransform , jsonBody , jsonTransform , ResponseTransformer } from './apiAuth' ;
77
88const tokenKey = 'gotify-login-key' ;
99
@@ -33,32 +33,75 @@ export class CurrentUser {
3333 return '' ;
3434 } ;
3535
36+ public authenticatedFetch = async < T > (
37+ url : string ,
38+ init : RequestInit ,
39+ xform : ResponseTransformer < T >
40+ ) : Promise < T > => {
41+ const headers = new Headers ( init ?. headers ) ;
42+ if ( this . loggedIn && ! headers . has ( 'X-Gotify-Key' ) )
43+ headers . set ( 'X-Gotify-Key' , this . token ( ) ) ;
44+ let response ;
45+ try {
46+ response = await fetch ( url , { ...init , headers} ) ;
47+ } catch ( error ) {
48+ this . snack ( 'Gotify server is not reachable, try refreshing the page.' ) ;
49+ throw error ;
50+ }
51+ if ( response . ok ) {
52+ try {
53+ return xform ( response ) ;
54+ } catch ( error ) {
55+ this . snack ( 'Response transformation failed: ' + error ) ;
56+ throw error ;
57+ }
58+ }
59+ if ( response . status === 401 ) {
60+ this . tryAuthenticate ( ) . then ( ( ) => this . snack ( 'Could not complete request.' ) ) ;
61+ }
62+
63+ let error = 'Unexpected status code: ' + response . status ;
64+ if ( response . status === 400 || response . status === 403 || response . status === 500 ) {
65+ if ( response . headers . get ( 'content-type' ) ?. includes ( 'application/json' ) ) {
66+ const data = await response . json ( ) ;
67+ error = data . error + ': ' + data . errorDescription ;
68+ } else {
69+ const text = await response . text ( ) ;
70+ error = 'Unexpected response: ' + text ;
71+ }
72+ }
73+ this . snack ( error ) ;
74+ throw new Error ( error ) ;
75+ } ;
76+
3677 private readonly setToken = ( token : string ) => {
3778 this . tokenCache = token ;
3879 window . localStorage . setItem ( tokenKey , token ) ;
3980 } ;
4081
41- public register = async ( name : string , pass : string ) : Promise < boolean > =>
42- axios
43- . create ( )
44- . post ( config . get ( 'url' ) + 'user' , { name, pass} )
82+ public register = async ( name : string , pass : string ) : Promise < boolean > => {
83+ runInAction ( ( ) => {
84+ this . loggedIn = false ;
85+ } ) ;
86+ return this . authenticatedFetch (
87+ config . get ( 'url' ) + 'user' ,
88+ jsonBody ( { name, pass} ) ,
89+ identityTransform
90+ )
4591 . then ( ( ) => {
4692 this . snack ( 'User Created. Logging in...' ) ;
4793 this . login ( name , pass ) ;
4894 return true ;
4995 } )
50- . catch ( ( error : AxiosError < { error ?: string ; errorDescription ?: string } > ) => {
51- if ( ! error || ! error . response ) {
96+ . catch ( ( error ) => {
97+ if ( error instanceof TypeError ) {
5298 this . snack ( 'No network connection or server unavailable.' ) ;
5399 return false ;
54100 }
55- const { data} = error . response ;
56-
57- this . snack (
58- `Register failed: ${ data ?. error ?? 'unknown' } : ${ data ?. errorDescription ?? '' } `
59- ) ;
101+ this . snack ( `Register failed: ${ error ?. message ?? error } ` ) ;
60102 return false ;
61103 } ) ;
104+ } ;
62105
63106 public login = async ( username : string , password : string ) => {
64107 runInAction ( ( ) => {
@@ -67,17 +110,17 @@ export class CurrentUser {
67110 } ) ;
68111 const browser = detect ( ) ;
69112 const name = ( browser && browser . name + ' ' + browser . version ) || 'unknown browser' ;
70- axios
71- . create ( )
72- . request ( {
73- url : config . get ( 'url' ) + 'client' ,
74- method : 'POST ',
75- data : { name } ,
76- headers : { Authorization : 'Basic ' + btoa ( username + ':' + password ) } ,
77- } )
78- . then ( ( resp : AxiosResponse < IClient > ) => {
113+ const fetchInit = jsonBody ( { name } ) ;
114+ fetchInit . headers = new Headers ( fetchInit . headers ) ;
115+ fetchInit . headers . set ( 'Authorization' , 'Basic ' + btoa ( username + ':' + password ) ) ;
116+ return this . authenticatedFetch (
117+ config . get ( 'url' ) + 'client ',
118+ fetchInit ,
119+ jsonTransform < IClient >
120+ )
121+ . then ( ( resp ) => {
79122 this . snack ( `A client named '${ name } ' was created for your session.` ) ;
80- this . setToken ( resp . data . token ) ;
123+ this . setToken ( resp . token ) ;
81124 this . tryAuthenticate ( ) . catch ( ( ) => {
82125 console . log (
83126 'create client succeeded, but authenticated with given token failed'
@@ -92,59 +135,58 @@ export class CurrentUser {
92135 ) ;
93136 } ;
94137
95- public tryAuthenticate = async ( ) : Promise < AxiosResponse < IUser > > => {
138+ public tryAuthenticate = async ( ) : Promise < IUser > => {
96139 if ( this . token ( ) === '' ) {
97140 runInAction ( ( ) => {
98141 this . authenticating = false ;
99142 } ) ;
100143 return Promise . reject ( ) ;
101144 }
102145
103- return axios
104- . create ( )
105- . get ( config . get ( 'url' ) + 'current/user' , { headers : { 'X-Gotify-Key' : this . token ( ) } } )
106- . then (
107- action ( ( passThrough ) => {
108- this . user = passThrough . data ;
109- this . loggedIn = true ;
110- this . authenticating = false ;
111- this . connectionErrorMessage = null ;
112- this . reconnectTime = 7500 ;
113- return passThrough ;
114- } )
115- )
146+ return fetch ( config . get ( 'url' ) + 'current/user' , { headers : { 'X-Gotify-Key' : this . token ( ) } } )
147+ . then ( async ( response ) => {
148+ if ( response . ok ) {
149+ const user = await response . json ( ) ;
150+ runInAction ( ( ) => {
151+ this . user = user ;
152+ this . loggedIn = true ;
153+ this . authenticating = false ;
154+ this . connectionErrorMessage = null ;
155+ this . reconnectTime = 7500 ;
156+ } ) ;
157+ return user ;
158+ }
159+ if ( response . status >= 500 ) {
160+ this . connectionError ( `${ response . statusText } (code: ${ response . status } ).` ) ;
161+ return Promise . reject ( new Error ( 'Server error' ) ) ;
162+ }
163+
164+ this . connectionErrorMessage = null ;
165+
166+ if ( response . status >= 400 && response . status < 500 ) {
167+ this . logout ( ) ;
168+ }
169+ throw new Error ( 'Unexpected status code: ' + response . status ) ;
170+ } )
116171 . catch (
117- action ( ( error : AxiosError ) => {
172+ action ( ( error ) => {
118173 this . authenticating = false ;
119- if ( ! error || ! error . response ) {
120- this . connectionError ( 'No network connection or server unavailable.' ) ;
121- return Promise . reject ( error ) ;
122- }
123-
124- if ( error . response . status >= 500 ) {
125- this . connectionError (
126- `${ error . response . statusText } (code: ${ error . response . status } ).`
127- ) ;
128- return Promise . reject ( error ) ;
129- }
130-
131- this . connectionErrorMessage = null ;
132-
133- if ( error . response . status >= 400 && error . response . status < 500 ) {
134- this . logout ( ) ;
135- }
174+ this . connectionError ( 'No network connection or server unavailable.' ) ;
136175 return Promise . reject ( error ) ;
137176 } )
138177 ) ;
139178 } ;
140179
141180 public logout = async ( ) => {
142- await axios
143- . get ( config . get ( 'url' ) + 'client' )
144- . then ( ( resp : AxiosResponse < IClient [ ] > ) => {
145- resp . data
146- . filter ( ( client ) => client . token === this . tokenCache )
147- . forEach ( ( client ) => axios . delete ( config . get ( 'url' ) + 'client/' + client . id ) ) ;
181+ await this . authenticatedFetch ( config . get ( 'url' ) + 'client' , { } , jsonTransform < IClient [ ] > )
182+ . then ( ( resp ) => {
183+ resp . filter ( ( client ) => client . token === this . tokenCache ) . forEach ( ( client ) =>
184+ this . authenticatedFetch (
185+ config . get ( 'url' ) + 'client/' + client . id ,
186+ { } ,
187+ jsonTransform
188+ )
189+ ) ;
148190 } )
149191 . catch ( ( ) => Promise . resolve ( ) ) ;
150192 window . localStorage . removeItem ( tokenKey ) ;
@@ -155,9 +197,15 @@ export class CurrentUser {
155197 } ;
156198
157199 public changePassword = ( pass : string ) => {
158- axios
159- . post ( config . get ( 'url' ) + 'current/user/password' , { pass} )
160- . then ( ( ) => this . snack ( 'Password changed' ) ) ;
200+ this . authenticatedFetch (
201+ config . get ( 'url' ) + 'current/user/password' ,
202+ jsonBody ( { pass} ) ,
203+ identityTransform
204+ )
205+ . then ( ( ) => this . snack ( 'Password changed' ) )
206+ . catch ( ( error ) => {
207+ this . snack ( `Change password failed: ${ error ?. message ?? error } ` ) ;
208+ } ) ;
161209 } ;
162210
163211 public tryReconnect = ( quiet = false ) => {
0 commit comments