11import { z } from "zod" ;
22import { env } from "../env.js" ;
3- import { HOURS , MINUTES , SECONDS } from "../utils.js" ;
3+ import { HOURS , MINUTES } from "../utils.js" ;
44
55const API_ROOT = "https://api.fishfish.gg/v1" ;
66
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
@@ -20,129 +20,149 @@ const domains = new Map<string, FishFishDomain>();
2020
2121let sessionTokenPromise : Promise < string > | null = null ;
2222
23- const WS_RECONNECT_DELAY = 30 * SECONDS ;
24- let updatesWs : WebSocket | null = null ;
23+ // const WS_RECONNECT_DELAY = 30 * SECONDS;
24+ // const updatesWs: WebSocket | null = null;
2525
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 ( `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 ;
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 ;
7781}
7882
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 ( ) ;
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 ( ) ;
95108}
96109
97110async function refreshFishFishDomains ( ) {
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 ) ;
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+ ) ;
132150}
133151
134152export async function initFishFish ( ) {
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 ) ;
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 ) ;
144164}
145165
146166export function getFishFishDomain ( domain : string ) : FishFishDomain | undefined {
147- return domains . get ( domain . toLowerCase ( ) ) ;
167+ return domains . get ( domain . toLowerCase ( ) ) ;
148168}
0 commit comments