1
+ import axios from 'axios'
2
+ import { endpointUrls } from '@remix-endpoints-helper'
3
+ import { ScanReport } from '@remix-ui/helper'
4
+
5
+ const _paq = ( window . _paq = window . _paq || [ ] )
6
+
7
+ /**
8
+ * Core function to perform Solidity scan and return the scan report
9
+ * @param api - Remix API instance
10
+ * @param compiledFileName - Name of the file to scan
11
+ * @returns Promise with the scan report or throws error
12
+ */
13
+ export const performSolidityScan = async ( api : any , compiledFileName : string ) : Promise < ScanReport > => {
14
+ const workspace = await api . call ( 'filePanel' , 'getCurrentWorkspace' )
15
+ const fileName = `${ workspace . name } /${ compiledFileName } `
16
+ const filePath = `.workspaces/${ fileName } `
17
+ const file = await api . call ( 'fileManager' , 'readFile' , filePath )
18
+
19
+ const urlResponse = await axios . post ( `${ endpointUrls . solidityScan } /uploadFile` , { file, fileName } )
20
+
21
+ if ( urlResponse . data . status !== 'success' ) {
22
+ throw new Error ( urlResponse . data . error || 'Failed to upload file to SolidityScan' )
23
+ }
24
+
25
+ return new Promise ( ( resolve , reject ) => {
26
+ const ws = new WebSocket ( `${ endpointUrls . solidityScanWebSocket } /solidityscan` )
27
+
28
+ const timeout = setTimeout ( ( ) => {
29
+ ws . close ( )
30
+ reject ( new Error ( 'Scan timeout' ) )
31
+ } , 300000 ) // 5 minute timeout
32
+
33
+ ws . addEventListener ( 'error' , ( error ) => {
34
+ clearTimeout ( timeout )
35
+ reject ( new Error ( 'WebSocket connection failed' ) )
36
+ } )
37
+
38
+ ws . addEventListener ( 'message' , async ( event ) => {
39
+ try {
40
+ const data = JSON . parse ( event . data )
41
+
42
+ if ( data . type === "auth_token_register" && data . payload . message === "Auth token registered." ) {
43
+ ws . send ( JSON . stringify ( {
44
+ action : "message" ,
45
+ payload : {
46
+ type : "private_project_scan_initiate" ,
47
+ body : {
48
+ file_urls : [ urlResponse . data . result . url ] ,
49
+ project_name : "RemixProject" ,
50
+ project_type : "new"
51
+ }
52
+ }
53
+ } ) )
54
+ } else if ( data . type === "scan_status" && data . payload . scan_status === "download_failed" ) {
55
+ clearTimeout ( timeout )
56
+ ws . close ( )
57
+ reject ( new Error ( data . payload . scan_status_err_message || 'Scan failed' ) )
58
+ } else if ( data . type === "scan_status" && data . payload . scan_status === "scan_done" ) {
59
+ clearTimeout ( timeout )
60
+ const { data : scanData } = await axios . post ( `${ endpointUrls . solidityScan } /downloadResult` , { url : data . payload . scan_details . link } )
61
+ const scanReport : ScanReport = scanData . scan_report
62
+
63
+ if ( scanReport ?. multi_file_scan_details ?. length ) {
64
+ // Process positions for each template
65
+ for ( const template of scanReport . multi_file_scan_details ) {
66
+ if ( template . metric_wise_aggregated_findings ?. length ) {
67
+ const positions = [ ]
68
+ for ( const details of template . metric_wise_aggregated_findings ) {
69
+ for ( const f of details . findings )
70
+ positions . push ( `${ f . line_nos_start [ 0 ] } :${ f . line_nos_end [ 0 ] } ` )
71
+ }
72
+ template . positions = JSON . stringify ( positions )
73
+ }
74
+ }
75
+ ws . close ( )
76
+ resolve ( scanReport )
77
+ } else {
78
+ ws . close ( )
79
+ reject ( new Error ( 'No scan results found' ) )
80
+ }
81
+ }
82
+ } catch ( error ) {
83
+ clearTimeout ( timeout )
84
+ ws . close ( )
85
+ reject ( error )
86
+ }
87
+ } )
88
+ } )
89
+ }
90
+
91
+ /**
92
+ * Callback type for rendering scan results
93
+ * @param scanReport - The scan report to render
94
+ * @param fileName - The name of the scanned file
95
+ * @returns JSX element or any renderable content for the terminal
96
+ */
97
+ export type ScanReportRenderer = ( scanReport : ScanReport , fileName : string ) => any
98
+
99
+ /**
100
+ * Handler for Solidity scan with notifications and terminal output
101
+ * @param api - Remix API instance
102
+ * @param compiledFileName - Name of the file to scan
103
+ * @param modalMessage - Error modal title message
104
+ * @param renderResults - Callback function to render the scan results (e.g., as JSX)
105
+ */
106
+ export const handleSolidityScan = async (
107
+ api : any ,
108
+ compiledFileName : string ,
109
+ modalMessage : string ,
110
+ renderResults : ScanReportRenderer
111
+ ) => {
112
+ await api . call ( 'notification' , 'toast' , 'Processing data to scan...' )
113
+ _paq . push ( [ 'trackEvent' , 'solidityCompiler' , 'solidityScan' , 'initiateScan' ] )
114
+
115
+ try {
116
+ const workspace = await api . call ( 'filePanel' , 'getCurrentWorkspace' )
117
+ const fileName = `${ workspace . name } /${ compiledFileName } `
118
+
119
+ await api . call ( 'notification' , 'toast' , 'Loading scan result in Remix terminal...' )
120
+
121
+ const scanReport = await performSolidityScan ( api , compiledFileName )
122
+
123
+ _paq . push ( [ 'trackEvent' , 'solidityCompiler' , 'solidityScan' , 'scanSuccess' ] )
124
+ const renderedResults = renderResults ( scanReport , fileName )
125
+ await api . call ( 'terminal' , 'logHtml' , renderedResults )
126
+ } catch ( error ) {
127
+ _paq . push ( [ 'trackEvent' , 'solidityCompiler' , 'solidityScan' , 'scanFailed' ] )
128
+ await api . call ( 'notification' , 'modal' , {
129
+ id : 'SolidityScanError' ,
130
+ title : modalMessage ,
131
+ message : error . message || 'Some error occurred! Please try again' ,
132
+ okLabel : 'Close'
133
+ } )
134
+ console . error ( error )
135
+ }
136
+ }
0 commit comments