@@ -2,15 +2,24 @@ import urlcat from 'urlcat'
2
2
import { apiUrl , baseUrl } from './constants'
3
3
import { getChatIdFromUrl , getConversationFromSharePage , isSharePage } from './page'
4
4
import { blobToDataURL } from './utils/dom'
5
+ import { memorize } from './utils/memorize'
5
6
6
7
interface ApiSession {
7
8
accessToken : string
9
+ authProvider : string
8
10
expires : string
9
11
user : {
10
12
email : string
11
13
groups : string [ ]
14
+ // token's issued_at timestamp
15
+ iat : number
12
16
id : string
17
+ // token's expiration timestamp
18
+ idp : string
13
19
image : string
20
+ intercom_hash : string
21
+ // whether the user has multi-factor authentication enabled
22
+ mfa : boolean
14
23
name : string
15
24
picture : string
16
25
}
@@ -192,6 +201,46 @@ export interface ApiConversations {
192
201
total : number
193
202
}
194
203
204
+ interface ApiAccountsCheckAccountDetail {
205
+ account_user_role : 'account-owner' | string
206
+ account_user_id : string | null
207
+ processor : Record < string , boolean >
208
+ account_id : string | null
209
+ organization_id ?: string | null
210
+ is_most_recent_expired_subscription_gratis : boolean
211
+ has_previously_paid_subscription : boolean
212
+ name ?: string | null
213
+ profile_picture_id ?: string | null
214
+ profile_picture_url ?: string | null
215
+ structure : 'workspace' | 'personal'
216
+ plan_type : 'team' | 'free'
217
+ is_deactivated : boolean
218
+ promo_data : Record < string , unknown >
219
+ }
220
+
221
+ interface ApiAccountsCheckEntitlement {
222
+ subscription_id ?: string | null
223
+ has_active_subscription ?: boolean
224
+ subscription_plan ?: 'chatgptteamplan' | 'chatgptplusplan'
225
+ expires_at ?: string | null
226
+ billing_period ?: 'monthly' | string | null
227
+ }
228
+
229
+ interface ApiAccountsCheckAccount {
230
+ account : ApiAccountsCheckAccountDetail
231
+ features : string [ ]
232
+ entitlement : ApiAccountsCheckEntitlement
233
+ last_active_subscription ?: Record < string , unknown > | null
234
+ is_eligible_for_yearly_plus_subscription : boolean
235
+ }
236
+
237
+ interface ApiAccountsCheck {
238
+ accounts : {
239
+ [ key : string ] : ApiAccountsCheckAccount
240
+ }
241
+ account_ordering : string [ ]
242
+ }
243
+
195
244
type ApiFileDownload = {
196
245
status : 'success'
197
246
/** signed download url */
@@ -210,6 +259,7 @@ const sessionApi = urlcat(baseUrl, '/api/auth/session')
210
259
const conversationApi = ( id : string ) => urlcat ( apiUrl , '/conversation/:id' , { id } )
211
260
const conversationsApi = ( offset : number , limit : number ) => urlcat ( apiUrl , '/conversations' , { offset, limit } )
212
261
const fileDownloadApi = ( id : string ) => urlcat ( apiUrl , '/files/:id/download' , { id } )
262
+ const accountsCheckApi = urlcat ( apiUrl , '/accounts/check/v4-2023-04-27' )
213
263
214
264
export async function getCurrentChatId ( ) : Promise < string > {
215
265
if ( isSharePage ( ) ) {
@@ -349,12 +399,14 @@ export async function deleteConversation(chatId: string): Promise<boolean> {
349
399
350
400
async function fetchApi < T > ( url : string , options ?: RequestInit ) : Promise < T > {
351
401
const accessToken = await getAccessToken ( )
402
+ const accountId = await getTeamAccountId ( )
352
403
353
404
const response = await fetch ( url , {
354
405
...options ,
355
406
headers : {
356
407
'Authorization' : `Bearer ${ accessToken } ` ,
357
408
'X-Authorization' : `Bearer ${ accessToken } ` ,
409
+ ...( accountId ? { 'Chatgpt-Account-Id' : accountId } : { } ) ,
358
410
...options ?. headers ,
359
411
} ,
360
412
} )
@@ -364,20 +416,46 @@ async function fetchApi<T>(url: string, options?: RequestInit): Promise<T> {
364
416
return response . json ( )
365
417
}
366
418
419
+ async function _fetchSession ( ) : Promise < ApiSession > {
420
+ const response = await fetch ( sessionApi )
421
+ if ( ! response . ok ) {
422
+ throw new Error ( response . statusText )
423
+ }
424
+ return response . json ( )
425
+ }
426
+
427
+ const fetchSession = memorize ( _fetchSession )
428
+
367
429
async function getAccessToken ( ) : Promise < string > {
368
430
const session = await fetchSession ( )
369
431
return session . accessToken
370
432
}
371
433
372
- let session : ApiSession | null = null
373
- async function fetchSession ( ) : Promise < ApiSession > {
374
- if ( session ) return session
375
- const response = await fetch ( sessionApi )
434
+ async function _fetchAccountsCheck ( ) : Promise < ApiAccountsCheck > {
435
+ const accessToken = await getAccessToken ( )
436
+
437
+ const response = await fetch ( accountsCheckApi , {
438
+ headers : {
439
+ 'Authorization' : `Bearer ${ accessToken } ` ,
440
+ 'X-Authorization' : `Bearer ${ accessToken } ` ,
441
+ } ,
442
+ } )
376
443
if ( ! response . ok ) {
377
444
throw new Error ( response . statusText )
378
445
}
379
- session = await response . json ( )
380
- return session !
446
+ return response . json ( )
447
+ }
448
+
449
+ const fetchAccountsCheck = memorize ( _fetchAccountsCheck )
450
+
451
+ export async function getTeamAccountId ( ) : Promise < string | null > {
452
+ const accountsCheck = await fetchAccountsCheck ( )
453
+ const accountKey = accountsCheck . account_ordering ?. [ 0 ] || 'default'
454
+ const account = accountsCheck . accounts [ accountKey ]
455
+ if ( ! account ) return null
456
+ if ( account . account . plan_type !== 'team' ) return null
457
+
458
+ return account . account . account_id
381
459
}
382
460
383
461
export interface ConversationResult {
0 commit comments