1- import { PersistentStorages } from '@masknet/shared-base'
1+ import { fromHex , toHex , PersistentStorages } from '@masknet/shared-base'
2+
3+ const FIREFLY_ROOT_URL = 'https://firefly.social'
4+ const FIREFLY_API_URL = 'https://api.firefly.land'
5+ const APP_LOGIN_ENCRYPT_IV = '0x4f05c37c16c801c2516b0338a8fd0cf9'
6+
7+ // Types for desktop sync
8+ export interface SocialAccountTwitter {
9+ type : 'x' // SourceInURL.X
10+ user_id : string
11+ handle : string
12+ consumerKey : string
13+ consumerKeySecret : string
14+ accessToken : string
15+ accessTokenSecret : string
16+ cookie ?: string
17+ }
18+
19+ export interface TwitterOAuthData {
20+ oauth_token : string
21+ oauth_token_secret : string
22+ user_id : string
23+ screen_name : string
24+ }
225
326export async function loginFireflyViaTwitter ( ) {
427 const data = await browser . storage . local . get ( 'firefly_x_oauth' )
@@ -18,7 +41,7 @@ export async function loginFireflyViaTwitter() {
1841 const json = await res . json ( )
1942 if ( ! json . success ) throw new Error ( json . message )
2043
21- const res2 = await fetch ( 'https://api.firefly.land/ v3/auth/exchange/twitter' , {
44+ const res2 = await fetch ( ` ${ FIREFLY_API_URL } / v3/auth/exchange/twitter` , {
2245 method : 'POST' ,
2346 headers : {
2447 'Content-Type' : 'application/json' ,
@@ -32,3 +55,135 @@ export async function loginFireflyViaTwitter() {
3255 await PersistentStorages . Settings . storage . firefly_account . setValue ( json2 . data )
3356 return json2 . data
3457}
58+
59+ // Desktop sync functions
60+ export async function encrypt ( plainText : string , cryptoKey : string ) : Promise < string > {
61+ const iv = fromHex ( APP_LOGIN_ENCRYPT_IV )
62+ const cryptoBytes = new TextEncoder ( ) . encode ( cryptoKey )
63+ const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , cryptoBytes )
64+ const aesKey = await crypto . subtle . importKey ( 'raw' , hashBuffer , { name : 'AES-CBC' } , false , [ 'encrypt' ] )
65+
66+ const plainBytes = new TextEncoder ( ) . encode ( plainText )
67+ const encryptedBuffer = await crypto . subtle . encrypt (
68+ {
69+ name : 'AES-CBC' ,
70+ iv,
71+ } ,
72+ aesKey ,
73+ plainBytes ,
74+ )
75+
76+ const hex = toHex ( new Uint8Array ( encryptedBuffer ) )
77+ return hex . startsWith ( '0x' ) ? hex . slice ( 2 ) : hex
78+ }
79+
80+ export interface DesktopLinkInfoResponse {
81+ link : string
82+ session : string
83+ expiresAt : string
84+ }
85+
86+ export async function getDesktopSyncLinkInfo ( accessToken : string ) : Promise < DesktopLinkInfoResponse > {
87+ const url = `${ FIREFLY_API_URL } /desktop/sync/linkInfo`
88+ const response = await fetch ( url , {
89+ method : 'GET' ,
90+ headers : {
91+ Authorization : `Bearer ${ accessToken } ` ,
92+ } ,
93+ } )
94+ if ( ! response . ok ) throw new Error ( `Failed to get desktop sync link info: ${ response . statusText } ` )
95+ const json = await response . json ( )
96+ if ( json . code ) throw new Error ( 'Failed to get desktop sync link info, code: ' + json . code )
97+ return json . data
98+ }
99+
100+ export enum DesktopSyncChannelStatus {
101+ Pending = 'pending' ,
102+ Scanned = 'scanned' ,
103+ Confirmed = 'confirmed' ,
104+ DataReady = 'dataReady' ,
105+ Cancel = 'cancel' ,
106+ Expired = 'expired' ,
107+ }
108+
109+ export interface SyncChannelStatusResponse {
110+ status : DesktopSyncChannelStatus
111+ }
112+
113+ export async function getSyncChannelStatus ( session : string , accessToken : string ) : Promise < SyncChannelStatusResponse > {
114+ const url = `${ FIREFLY_API_URL } /desktop/sync/channelStatus`
115+ const response = await fetch ( url , {
116+ method : 'POST' ,
117+ headers : {
118+ 'Content-Type' : 'application/json' ,
119+ Authorization : `Bearer ${ accessToken } ` ,
120+ } ,
121+ body : JSON . stringify ( { session } ) ,
122+ } )
123+ if ( ! response . ok ) throw new Error ( `Failed to get sync channel status: ${ response . statusText } ` )
124+ const json = await response . json ( )
125+ return json . data
126+ }
127+
128+ export type ConfirmSyncChannelOperation = 'confirm' | 'cancel'
129+
130+ export async function confirmSyncChannel (
131+ session : string ,
132+ operation : ConfirmSyncChannelOperation ,
133+ accessToken : string ,
134+ ) : Promise < { status : string } > {
135+ const url = `${ FIREFLY_API_URL } /desktop/sync/confirm`
136+ const response = await fetch ( url , {
137+ method : 'POST' ,
138+ headers : {
139+ 'Content-Type' : 'application/json' ,
140+ Authorization : `Bearer ${ accessToken } ` ,
141+ } ,
142+ body : JSON . stringify ( { session, op : operation } ) ,
143+ } )
144+ if ( ! response . ok ) throw new Error ( `Failed to confirm sync channel: ${ response . statusText } ` )
145+ const json = await response . json ( )
146+ return json . data
147+ }
148+
149+ export interface TwitterCookiesPayload {
150+ twitterAccounts : SocialAccountTwitter [ ]
151+ fireflyAccountData : {
152+ firefly_account_token : string
153+ account_id : string
154+ account_uid : string
155+ display_name : string
156+ avatar : string
157+ }
158+ }
159+
160+ // Helper function to get Twitter OAuth data from storage
161+ export async function getTwitterOAuthData ( ) : Promise < TwitterOAuthData | null > {
162+ const data = await browser . storage . local . get ( 'firefly_x_oauth' )
163+ if ( ! data ?. firefly_x_oauth ) return null
164+ return data . firefly_x_oauth as TwitterOAuthData
165+ }
166+
167+ export async function syncTwitterCookies (
168+ session : string ,
169+ cryptoKey : string ,
170+ encryptedPayload : string ,
171+ accessToken : string ,
172+ ) : Promise < void > {
173+ const url = `${ FIREFLY_ROOT_URL } /api/firefly/desktop-sync/upload`
174+ const response = await fetch ( url , {
175+ method : 'POST' ,
176+ headers : {
177+ 'Content-Type' : 'application/json' ,
178+ Authorization : `Bearer ${ accessToken } ` ,
179+ } ,
180+ body : JSON . stringify ( {
181+ session,
182+ cryptoKey,
183+ encryptedPayload,
184+ } ) ,
185+ } )
186+ if ( ! response . ok ) throw new Error ( `Failed to sync twitter cookies: ${ response . statusText } ` )
187+ const json = await response . json ( )
188+ if ( ! json . success ) throw new Error ( json . message )
189+ }
0 commit comments