@@ -7,11 +7,11 @@ const API_ROOT = "https://api.fishfish.gg/v1";
77const zDomainCategory = z . literal ( [ "safe" , "malware" , "phishing" ] ) ;
88
99const zDomain = z . object ( {
10- name : z . string ( ) ,
11- category : zDomainCategory ,
12- description : z . string ( ) ,
13- added : z . number ( ) ,
14- checked : z . number ( ) ,
10+ name : z . string ( ) ,
11+ category : zDomainCategory ,
12+ description : z . string ( ) ,
13+ added : z . number ( ) ,
14+ checked : z . number ( ) ,
1515} ) ;
1616export type FishFishDomain = z . output < typeof zDomain > ;
1717
@@ -26,143 +26,123 @@ let sessionTokenPromise: Promise<string> | null = null;
2626export class FishFishError extends Error { }
2727
2828const zTokenResponse = z . object ( {
29- expires : z . number ( ) ,
30- token : z . string ( ) ,
29+ expires : z . number ( ) ,
30+ token : z . string ( ) ,
3131} ) ;
3232
3333async function getSessionToken ( ) : Promise < string > {
34- if ( sessionTokenPromise ) {
35- return sessionTokenPromise ;
36- }
37-
38- const apiKey = env . FISHFISH_API_KEY ;
39- if ( ! apiKey ) {
40- throw new FishFishError ( "FISHFISH_API_KEY is missing" ) ;
41- }
42-
43- sessionTokenPromise = ( async ( ) => {
44- const response = await fetch ( `${ API_ROOT } /users/@me/tokens` , {
45- method : "POST" ,
46- headers : {
47- Authorization : apiKey ,
48- "Content-Type" : "application/json" ,
49- } ,
50- } ) ;
51-
52- if ( ! response . ok ) {
53- throw new FishFishError (
54- `Failed to get session token: ${ response . status } ${ response . statusText } ` ,
55- ) ;
56- }
57-
58- const parseResult = zTokenResponse . safeParse ( await response . json ( ) ) ;
59- if ( ! parseResult . success ) {
60- throw new FishFishError (
61- `Parse error when fetching session token: ${ parseResult . error . message } ` ,
62- ) ;
63- }
64-
65- const timeUntilExpiry = parseResult . data . expires * 1000 - Date . now ( ) ;
66- setTimeout (
67- ( ) => {
68- sessionTokenPromise = null ;
69- } ,
70- timeUntilExpiry - 1 * MINUTES ,
71- ) ; // Subtract a minute to ensure we refresh before expiry
72-
73- return parseResult . data . token ;
74- } ) ( ) ;
75- sessionTokenPromise . catch ( ( err ) => {
76- sessionTokenPromise = null ;
77- throw err ;
78- } ) ;
79-
80- return sessionTokenPromise ;
34+ if ( sessionTokenPromise ) {
35+ return sessionTokenPromise ;
36+ }
37+
38+ const apiKey = env . FISHFISH_API_KEY ;
39+ if ( ! apiKey ) {
40+ throw new FishFishError ( "FISHFISH_API_KEY is missing" ) ;
41+ }
42+
43+ sessionTokenPromise = ( async ( ) => {
44+ const response = await fetch ( `${ API_ROOT } /users/@me/tokens` , {
45+ method : "POST" ,
46+ headers : {
47+ Authorization : apiKey ,
48+ "Content-Type" : "application/json" ,
49+ } ,
50+ } ) ;
51+
52+ if ( ! response . ok ) {
53+ throw new FishFishError ( `Failed to get session token: ${ response . status } ${ response . statusText } ` ) ;
54+ }
55+
56+ const parseResult = zTokenResponse . safeParse ( await response . json ( ) ) ;
57+ if ( ! parseResult . success ) {
58+ throw new FishFishError ( `Parse error when fetching session token: ${ parseResult . error . message } ` ) ;
59+ }
60+
61+ const timeUntilExpiry = parseResult . data . expires * 1000 - Date . now ( ) ;
62+ setTimeout (
63+ ( ) => {
64+ sessionTokenPromise = null ;
65+ } ,
66+ timeUntilExpiry - 1 * MINUTES ,
67+ ) ; // Subtract a minute to ensure we refresh before expiry
68+
69+ return parseResult . data . token ;
70+ } ) ( ) ;
71+ sessionTokenPromise . catch ( ( err ) => {
72+ sessionTokenPromise = null ;
73+ throw err ;
74+ } ) ;
75+
76+ return sessionTokenPromise ;
8177}
8278
83- async function fishFishApiCall (
84- method : string ,
85- path : string ,
86- query : Record < string , string > = { } ,
87- ) : Promise < unknown > {
88- const sessionToken = await getSessionToken ( ) ;
89- const queryParams = new URLSearchParams ( query ) ;
90- const response = await fetch (
91- `https://api.fishfish.gg/v1/${ path } ?${ queryParams } ` ,
92- {
93- method,
94- headers : {
95- Authorization : sessionToken ,
96- "Content-Type" : "application/json" ,
97- } ,
98- } ,
99- ) ;
100-
101- if ( ! response . ok ) {
102- throw new FishFishError (
103- `FishFish API call failed: ${ response . status } ${ response . statusText } ` ,
104- ) ;
105- }
106-
107- return response . json ( ) ;
79+ async function fishFishApiCall ( method : string , path : string , query : Record < string , string > = { } ) : Promise < unknown > {
80+ const sessionToken = await getSessionToken ( ) ;
81+ const queryParams = new URLSearchParams ( query ) ;
82+ const response = await fetch ( `https://api.fishfish.gg/v1/${ path } ?${ queryParams } ` , {
83+ method,
84+ headers : {
85+ Authorization : sessionToken ,
86+ "Content-Type" : "application/json" ,
87+ } ,
88+ } ) ;
89+
90+ if ( ! response . ok ) {
91+ throw new FishFishError ( `FishFish API call failed: ${ response . status } ${ response . statusText } ` ) ;
92+ }
93+
94+ return response . json ( ) ;
10895}
10996
11097async function refreshFishFishDomains ( ) {
111- const rawData = await fishFishApiCall ( "GET" , "domains" , { full : "true" } ) ;
112- const parseResult = z . array ( zDomain ) . safeParse ( rawData ) ;
113- if ( ! parseResult . success ) {
114- throw new FishFishError (
115- `Parse error when refreshing domains: ${ parseResult . error . message } ` ,
116- ) ;
117- }
118-
119- domains . clear ( ) ;
120- for ( const domain of parseResult . data ) {
121- domains . set ( domain . name , domain ) ;
122- }
123-
124- domains . set ( "malware-link.test.zeppelin.gg" , {
125- name : "malware-link.test.zeppelin.gg" ,
126- category : "malware" ,
127- description : "" ,
128- added : Date . now ( ) ,
129- checked : Date . now ( ) ,
130- } ) ;
131- domains . set ( "phishing-link.test.zeppelin.gg" , {
132- name : "phishing-link.test.zeppelin.gg" ,
133- category : "phishing" ,
134- description : "" ,
135- added : Date . now ( ) ,
136- checked : Date . now ( ) ,
137- } ) ;
138- domains . set ( "safe-link.test.zeppelin.gg" , {
139- name : "safe-link.test.zeppelin.gg" ,
140- category : "safe" ,
141- description : "" ,
142- added : Date . now ( ) ,
143- checked : Date . now ( ) ,
144- } ) ;
145-
146- console . log (
147- "[FISHFISH] Refreshed FishFish domains, total count:" ,
148- domains . size ,
149- ) ;
98+ const rawData = await fishFishApiCall ( "GET" , "domains" , { full : "true" } ) ;
99+ const parseResult = z . array ( zDomain ) . safeParse ( rawData ) ;
100+ if ( ! parseResult . success ) {
101+ throw new FishFishError ( `Parse error when refreshing domains: ${ parseResult . error . message } ` ) ;
102+ }
103+
104+ domains . clear ( ) ;
105+ for ( const domain of parseResult . data ) {
106+ domains . set ( domain . name , domain ) ;
107+ }
108+
109+ domains . set ( "malware-link.test.zeppelin.gg" , {
110+ name : "malware-link.test.zeppelin.gg" ,
111+ category : "malware" ,
112+ description : "" ,
113+ added : Date . now ( ) ,
114+ checked : Date . now ( ) ,
115+ } ) ;
116+ domains . set ( "phishing-link.test.zeppelin.gg" , {
117+ name : "phishing-link.test.zeppelin.gg" ,
118+ category : "phishing" ,
119+ description : "" ,
120+ added : Date . now ( ) ,
121+ checked : Date . now ( ) ,
122+ } ) ;
123+ domains . set ( "safe-link.test.zeppelin.gg" , {
124+ name : "safe-link.test.zeppelin.gg" ,
125+ category : "safe" ,
126+ description : "" ,
127+ added : Date . now ( ) ,
128+ checked : Date . now ( ) ,
129+ } ) ;
130+
131+ console . log ( "[FISHFISH] Refreshed FishFish domains, total count:" , domains . size ) ;
150132}
151133
152134export async function initFishFish ( ) {
153- if ( ! env . FISHFISH_API_KEY ) {
154- console . warn (
155- "[FISHFISH] FISHFISH_API_KEY is not set, FishFish functionality will be disabled." ,
156- ) ;
157- return ;
158- }
159-
160- await refreshFishFishDomains ( ) ;
161- // Real-time updates disabled until we switch to a WebSocket lib that supports authorization headers
162- // void subscribeToFishFishUpdates();
163- setInterval ( ( ) => refreshFishFishDomains ( ) , FULL_REFRESH_INTERVAL ) ;
135+ if ( ! env . FISHFISH_API_KEY ) {
136+ console . warn ( "[FISHFISH] FISHFISH_API_KEY is not set, FishFish functionality will be disabled." ) ;
137+ return ;
138+ }
139+
140+ await refreshFishFishDomains ( ) ;
141+ // Real-time updates disabled until we switch to a WebSocket lib that supports authorization headers
142+ // void subscribeToFishFishUpdates();
143+ setInterval ( ( ) => refreshFishFishDomains ( ) , FULL_REFRESH_INTERVAL ) ;
164144}
165145
166146export function getFishFishDomain ( domain : string ) : FishFishDomain | undefined {
167- return domains . get ( domain . toLowerCase ( ) ) ;
147+ return domains . get ( domain . toLowerCase ( ) ) ;
168148}
0 commit comments