@@ -77,6 +77,53 @@ export default class ReadwiseMirrorSettingTab extends PluginSettingTab {
77
77
}
78
78
}
79
79
80
+ // Button-based authentication inspired by the official Readwise plugin
81
+ private async getUserAuthToken ( button : HTMLElement , attempt = 0 ) : Promise < boolean > {
82
+ const baseURL = 'https://readwise.io' ;
83
+ const uuid = this . getReadwiseMirrorClientId ( ) ;
84
+
85
+ if ( attempt === 0 ) {
86
+ window . open ( `${ baseURL } /api_auth?token=${ uuid } &service=readwise-mirror` ) ;
87
+ }
88
+
89
+ let response , data ;
90
+ try {
91
+ response = await fetch ( `${ baseURL } /api/auth?token=${ uuid } ` ) ;
92
+ if ( response . ok ) {
93
+ data = await response . json ( ) ;
94
+ if ( data . userAccessToken ) {
95
+ this . plugin . settings . apiToken = data . userAccessToken ;
96
+ this . plugin . readwiseApi . setToken ( data . userAccessToken ) ;
97
+ await this . plugin . saveSettings ( ) ;
98
+ this . display ( ) ; // Refresh the settings page
99
+ return true ;
100
+ }
101
+ }
102
+ } catch ( e ) {
103
+ console . log ( "Failed to authenticate with Readwise:" , e ) ;
104
+ }
105
+
106
+ if ( attempt > 20 ) {
107
+ this . notify . notice ( "Authentication timeout. Please try again." ) ;
108
+ return false ;
109
+ }
110
+
111
+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
112
+ return this . getUserAuthToken ( button , attempt + 1 ) ;
113
+ }
114
+
115
+ // Button-based authentication inspired by the official Readwise plugin
116
+ private getReadwiseMirrorClientId ( ) {
117
+ let readwiseMirrorClientId = window . localStorage . getItem ( 'readwise-mirror-obsidian-client-id' ) ;
118
+ if ( readwiseMirrorClientId ) {
119
+ return readwiseMirrorClientId ;
120
+ } else {
121
+ readwiseMirrorClientId = Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
122
+ window . localStorage . setItem ( 'readwise-mirror-obsidian-client-id' , readwiseMirrorClientId ) ;
123
+ return readwiseMirrorClientId ;
124
+ }
125
+ }
126
+
80
127
private createTemplateDocumentation ( title : string , variables : [ string , string ] [ ] ) {
81
128
return createFragment ( ( fragment ) => {
82
129
const documentationContainer = fragment . createDiv ( {
@@ -118,29 +165,82 @@ export default class ReadwiseMirrorSettingTab extends PluginSettingTab {
118
165
119
166
containerEl . createEl ( 'h1' , { text : 'Readwise Sync Configuration' } ) ;
120
167
121
- new Setting ( containerEl )
122
- . setName ( 'Authentication' )
123
- . setHeading ( ) ;
168
+ // Authentication section inspired by the official Readwise plugin
169
+ new Setting ( containerEl ) . setName ( 'Authentication' ) . setHeading ( ) ;
124
170
125
- const apiTokenFragment = document . createDocumentFragment ( ) ;
126
- apiTokenFragment . createEl ( 'span' , null , ( spanEl ) =>
127
- spanEl . createEl ( 'a' , null , ( aEl ) => ( aEl . innerText = aEl . href = 'https://readwise.io/access_token' ) )
128
- ) ;
171
+
172
+ const hasValidToken = await this . plugin . readwiseApi . hasValidToken ( ) ;
173
+
174
+ const tokenValidationError = containerEl . createDiv ( {
175
+ cls : 'setting-item-description validation-error' ,
176
+ text : 'Invalid token. Please try authenticating again.' ,
177
+ attr : {
178
+ style : 'color: var(--text-error); margin-top: 0.5em; display: none;'
179
+ }
180
+ } ) ; ;
181
+ const tokenValidationSuccess = containerEl . createDiv ( {
182
+ cls : 'setting-item-description validation-success' ,
183
+ text : 'Token validated successfully' ,
184
+ attr : {
185
+ style : 'color: var(--text-success); margin-top: 0.5em; display: none;'
186
+ }
187
+ } ) ;
129
188
130
189
new Setting ( containerEl )
131
- . setName ( 'Enter your Readwise Access Token' )
132
- . setDesc ( apiTokenFragment )
133
- . addText ( ( text ) =>
190
+ . setName ( 'Readwise Authentication' )
191
+ . setDesc ( createFragment ( ( fragment ) => {
192
+ fragment . createEl ( 'br' ) ;
193
+ fragment . createEl ( 'br' ) ;
194
+ fragment . createEl ( 'strong' , { text : 'Important: ' } ) ;
195
+ fragment . appendText ( 'After successful authentication, a window with an error message will appear.' ) ;
196
+ fragment . createEl ( 'br' ) ;
197
+ fragment . appendText ( 'This is expected and can be safely closed.' ) ;
198
+ fragment . createEl ( 'br' ) ;
199
+ fragment . createEl ( 'br' ) ;
200
+ fragment . append ( tokenValidationError ) ;
201
+ fragment . append ( tokenValidationSuccess ) ;
202
+
203
+ // Show success or error message based on token validity
204
+ if ( hasValidToken ) {
205
+ tokenValidationSuccess . show ( ) ;
206
+ tokenValidationError . hide ( ) ;
207
+ } else {
208
+ tokenValidationSuccess . hide ( ) ;
209
+ tokenValidationError . show ( ) ;
210
+ }
211
+ } ) )
212
+ . addButton ( ( button ) => {
213
+ button
214
+ . setButtonText ( ! hasValidToken ? 'Re-authenticate with Readwise' : 'Authenticate with Readwise' )
215
+ . setCta ( )
216
+ . onClick ( async ( evt ) => {
217
+ const buttonEl = evt . target as HTMLElement ;
218
+ const token = await this . getUserAuthToken ( buttonEl ) ;
219
+ if ( token ) {
220
+ if ( ! hasValidToken ) {
221
+ tokenValidationError . show ( ) ;
222
+ tokenValidationSuccess . hide ( ) ;
223
+ } else {
224
+ tokenValidationError . hide ( ) ;
225
+ tokenValidationSuccess . show ( ) ;
226
+ }
227
+ }
228
+ } ) . setDisabled ( hasValidToken ) ;
229
+ return button ;
230
+ } )
231
+ . addText ( ( text ) => {
232
+ const token = this . plugin . settings . apiToken ;
233
+ const maskedToken = token
234
+ ? token . slice ( 0 , 6 ) + '*' . repeat ( token . length - 6 )
235
+ : '' ;
236
+
134
237
text
135
- . setPlaceholder ( 'Readwise Access Token' )
136
- . setValue ( this . plugin . settings . apiToken )
137
- . onChange ( async ( value ) => {
138
- if ( ! value ) return ;
139
- this . plugin . settings . apiToken = value ;
140
- await this . plugin . saveSettings ( ) ;
141
- this . plugin . readwiseApi = new ReadwiseApi ( value , this . notify ) ;
142
- } )
143
- ) ;
238
+ . setPlaceholder ( 'Token will be filled automatically after authentication' )
239
+ . setValue ( maskedToken )
240
+ . setDisabled ( true ) ;
241
+ } ) ;
242
+
243
+
144
244
145
245
new Setting ( containerEl )
146
246
. setName ( 'Library Settings' )
0 commit comments