From d37603fcd5170cd8d334a8476fe04da68183ba83 Mon Sep 17 00:00:00 2001 From: Sam Carlton Date: Sun, 7 Aug 2022 14:33:58 -0500 Subject: [PATCH] Move scan business login into scan file --- helpers/scanner/client.mjs | 458 +++-------------------------------- helpers/scanner/scan.mjs | 436 +++++++++++++++++++++++++++++++++ helpers/scanner/worker.js | 4 +- test/scanner/client.test.mjs | 54 +---- 4 files changed, 476 insertions(+), 476 deletions(-) create mode 100644 helpers/scanner/scan.mjs diff --git a/helpers/scanner/client.mjs b/helpers/scanner/client.mjs index 00026b1..ee28ee2 100644 --- a/helpers/scanner/client.mjs +++ b/helpers/scanner/client.mjs @@ -1,436 +1,48 @@ -import { Buffer } from 'buffer/index.js' -import prettyBytes from 'pretty-bytes' -import * as zip from '@zip.js/zip.js' +import AppScanWorker from '~/helpers/scanner/worker.js?worker' -import * as FileApi from '~/helpers/scanner/file-api.js' -import { isString, isNonEmptyString } from '~/helpers/check-types.js' -import { parsePlistBuffer } from '~/helpers/scanner/parsers/plist.js' -import { extractMachoMeta } from '~/helpers/scanner/parsers/macho.js' +export async function runScanWorker ( file ) { + // console.log( 'file', file ) -// https://gildas-lormeau.github.io/zip.js/core-api.html#configuration -zip.configure({ - // Disable Web Workers for SSR since Node doesn't support them yet - // https://vitejs.dev/guide/env-and-mode.html#env-variables - useWebWorkers: !import.meta.env.SSR -}) + const appScanWorker = new AppScanWorker() + const scan = await new Promise( ( resolve, reject ) => { + // Set up the worker message handler + appScanWorker.onmessage = async (event) => { + // console.log( 'Main received message', event ) -function makeNodeFileBuffer ( buffer ) { - const fileBuffer = new Buffer.alloc( buffer.byteLength ) + const { status } = event.data - for (var i = 0; i < buffer.length; i++) - fileBuffer[i] = buffer[i]; - - // console.log( 'this.machoFileInstance', this.machoFileInstance.buffer.byteLength ) - - return fileBuffer -} - -export class AppScan { - constructor ({ - fileLoader, - messageReceiver - }) { - - this.fileLoader = fileLoader - this.messageReceiver = messageReceiver - - this.status = 'idle' - this.file = null - this.bundleFileEntries = [] - this.infoPlist = {} - this.machoExcutables = [] - - // Data that is derived after we've read the files and pulled out the infoPlist - this.appVersion = '' - this.displayName = '' - this.details = [] - this.bundleExecutable = null - this.displayBinarySize = '' - this.binarySize = 0 - this.machoMeta = {} - this.binarySupportsNative = undefined - - this.info = {} - } - - sendMessage ( details ) { - if ( details?.status ) { - this.status = details.status - } - - if ( typeof( this.messageReceiver ) === 'function' ) { - this.messageReceiver( details ) - } - } - - get hasInfoPlist () { - return Object.keys( this.infoPlist ).length > 0 - } - - get hasMachoMeta () { - return Object.keys( this.machoMeta ).length > 0 - } - - get hasInfo () { - return Object.keys( this.info ).length > 0 - } - - get bundleExecutablePath () { - if ( !this.hasInfoPlist ) return '' - - // There our CFBundleExecutable is a path to the executable - // then use it - if ( this.infoPlist.CFBundleExecutable.includes('/') ) return `/Contents/${ this.infoPlist.CFBundleExecutable }` - - // Use default executable path - return `/Contents/MacOS/${ this.infoPlist.CFBundleExecutable }` - } - - get supportedArchitectures () { - if ( !this.hasMachoMeta ) return [] - - return this.machoMeta.architectures.filter( architecture => architecture.processorType !== 0 ) - } - - async readFileEntryData ( fileEntry, Writer = zip.TextWriter ) { - // Get blob data from zip - // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry - return await fileEntry.getData( - // writer - // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing - new Writer()//zip.TextWriter(), - ) - } - - - async readFileBlob ( FileInstance ) { - return new Promise( async ( resolve, reject ) => { - // Check if file is a Blob, typically in the Browser - // otherwise convert it to a Blob, like in Node - // Both Browser and Node have Blob - // Node/Our File Polyfill references .arrayBuffer as a property - // Browser currently references .arrayBuffer as an async method - const FileBlob = FileInstance instanceof Blob ? FileInstance : new Blob( FileInstance.arrayBuffer ) - - // console.log( 'FileBlob', FileBlob ) - - const fileReader = new zip.BlobReader( FileBlob ) - - // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-reading - const zipReader = new zip.ZipReader( fileReader ) - - zipReader - .getEntries() - .then( entries => { - - // do something on entries - this.sendMessage({ - message: '📖 Reading file complete. Entries found', - status: 'read' - }) - - resolve( entries ) - }) - - }) - } - - classifyBinaryEntryArchitecture ( binaryEntry ) { - // Find an ARM Architecture - const armArchitecture = binaryEntry.architectures.find( architecture => { - // if ( architecture.processorType === 0 ) return false - - // If processorType not a string - // then return false - if ( !isString(architecture.processorType) ) return false - - return architecture.processorType.toLowerCase().includes('arm') - }) - - // Was an ARM Architecture found - return (armArchitecture !== undefined) - } - - matchesMachoExecutable ( entry ) { - // Skip files that are deeper than 3 folders - if ( entry.filename.split('/').length > 4 ) return false - - // Skip folders - // if ( !!entry.directory ) return false - - // `${ appName }.app/Contents/MacOS/${ appName }` - // Does this entry path match any of our wanted paths - return [ - // `${ appName }.app/Contents/MacOS/${ appName }` - // `.app/Contents/MacOS/`, - `Contents/MacOS/` - ].some( pathToMatch => { - return entry.filename.includes( pathToMatch ) - }) - } - - matchesRootInfoPlist ( entry ) { - // Skip files that are deeper than 2 folders - if ( entry.filename.split('/').length > 3 ) return false - - // Skip folders - if ( entry.filename.endsWith('/') ) return false - - // If this entry matches the root info.plist path exactly - // then we have found the root info.plist - if ( entry.filename === 'Contents/Info.plist' ) return true - - // Does this entry path match any of our wanted paths - return [ - // `zoom.us.app/Contents/Info.plist` - `.app/Contents/Info.plist`, - `.zip/Contents/Info.plist` - ].some( pathToMatch => { - return entry.filename.endsWith( pathToMatch ) - }) - } - - fileEntryType ( fileEntry ) { - if ( !!fileEntry.directory ) return 'directory' - - if ( this.matchesMachoExecutable( fileEntry ) ) return 'machoExecutable' - - if ( this.matchesRootInfoPlist( fileEntry ) ) return 'rootInfoPlist' - - // getData - - return 'unknown' - } - - storeInfoPlist = async ( fileEntry ) => { - // Throw if we have more than one target file - if ( this.hasInfoPlist ) { - throw new Error( 'More than one root info.plist found' ) - } - - const infoUint8Array = await this.readFileEntryData( fileEntry, zip.Uint8ArrayWriter ) - // console.log( 'infoUint8Array', infoUint8Array ) - - const infoNodeBuffer = makeNodeFileBuffer( infoUint8Array ) - - // Parse the Info.plist data - this.infoPlist = await parsePlistBuffer( infoNodeBuffer ) - - this.sendMessage({ - message: 'â„šī¸ Found Info.plist', - // data: this.infoPlist - }) - } - - storeMachoExecutable = ( fileEntry ) => { - this.machoExcutables.push( fileEntry ) - - this.sendMessage({ - message: 'đŸĨŠ Found a Macho executable', - // data: machoExecutable, - }) - } - - storeResultInfo () { - this.info = { - filename: this.file.name, - appVersion: this.appVersion, - result: this.binarySupportsNative ? '✅' : 'đŸ”ļ', - machoMeta: { - ...this.machoMeta, - file: undefined, - architectures: this.machoMeta.architectures.map( architecture => { - return { - bits: architecture.bits, - fileType: architecture.fileType, - header: architecture.header, - loadCommandsInfo: architecture.loadCommandsInfo, - magic: architecture.magic, - offset: architecture.offset, - processorSubType: architecture.processorSubType, - processorType: architecture.processorType, - } - }) - }, - infoPlist: this.infoPlist, - } - } - - storeMachoMeta = async ( fileEntry ) => { - // Throw if we have more than one target file - if ( this.hasMachoMeta ) { - throw new Error( 'More than one primary Macho executable found' ) - } - - // Get zip as Uint8Array - const bundleExecutableUint8Array = await this.readFileEntryData( fileEntry, zip.Uint8ArrayWriter ) - - const machoFileInstance = new FileApi.File({ - name: this.bundleExecutable.filename, - type: 'application/x-mach-binary', - buffer: Buffer.from( bundleExecutableUint8Array ) - }) - - // Get zip as blob - // so we can use it in for the File API when we're in the browser context - // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry - machoFileInstance.blob = await this.readFileEntryData( fileEntry, zip.BlobWriter ) - - this.machoMeta = await extractMachoMeta({ - machoFileInstance, - FileApi - }) - - // console.log( 'this.machoMeta', this.machoMeta ) - - } - - - targetFiles = { - rootInfoPlist: { - method: this.storeInfoPlist - }, - machoExecutable: { - method: this.storeMachoExecutable, - } - } - - findMainExecutable () { - - // Now that we have the info.plist Determine our entry Macho Executable from the list of Macho Executables - const bundleExecutables = this.machoExcutables.filter( machoEntry => { - - if ( machoEntry.filename.includes( this.bundleExecutablePath ) ) { - return true + // Resolves promise on finished status + if ( status === 'finished' ) { + const { scan } = event.data + resolve( scan ) } - - return this.bundleExecutablePath.includes( machoEntry.filename ) - }) - - // Warn if Bundle Executable doesn't look right - if ( bundleExecutables.length > 1) { - throw new Error('More than one root bundleExecutable found', bundleExecutables) - } else if ( bundleExecutables.length === 0 ) { - throw new Error('No root bundleExecutable found', bundleExecutables) } - return bundleExecutables[ 0 ] - } + // Set up the worker error handler + appScanWorker.onerror = async ( errorEvent ) => { + // console.log( 'appScanWorker.onerror', errorEvent ) + reject() + } - async findTargetFiles () { - - for ( const fileEntry of this.bundleFileEntries ) { - const type = this.fileEntryType( fileEntry ) - - // console.log( 'fileEntry', type, fileEntry.filename ) - - // Check if we have a target file - if ( this.targetFiles[ type ] ) { - // console.log( 'fileEntry', type, fileEntry.filename ) - - // Call the target file method - await this.targetFiles[ type ].method( fileEntry ) + // Start the worker + // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage + appScanWorker.postMessage( { + status: 'start', + options: { + fileLoader: () => ({ + ...file, + arrayBuffer: file.arrayBuffer + }), + messageReceiver: ( details ) => { + console.log( 'Scan message:', details ) + } } + }, [ file.arrayBuffer ] ) + }) - // console.log( 'File Entry Type:', type ) - } - - // Now that we have the info.plist Determine our entry Macho Executable from the list of Macho Executables - - // Find valid app version that is a string but not empty - this.appVersion = ([ - this.infoPlist.CFBundleShortVersionString, - this.infoPlist.CFBundleVersion - ]).find( isNonEmptyString )[0] - - // Find Display Name that is a string but not empty - this.displayName = ([ - this.infoPlist.CFBundleDisplayName, - this.infoPlist.CFBundleName, - this.infoPlist.CFBundleExecutable, - ]).find( isNonEmptyString )[0] - - // We loop through possible details and add them to the details array - // if they are not empty - ;([ - [ 'Version', this.infoPlist.CFBundleShortVersionString ], - [ 'Bundle Identifier', this.infoPlist.CFBundleIdentifier ], - [ 'File Mime Type', this.file.type ], - [ 'Copyright', this.infoPlist.NSHumanReadableCopyright ], - // [ 'Version', info.CFBundleShortVersionString ], - ]).forEach( ([ label, value ]) => { - if ( !value || value.length === 0 ) return - - this.details.push({ - label, - value, - }) - } ) - - // Set the bundleExecutable - this.bundleExecutable = this.findMainExecutable() - - console.log('Parsing ', this.bundleExecutable.filename, this.bundleExecutable.uncompressedSize / 1000 ) - - this.displayBinarySize = prettyBytes( this.bundleExecutable.uncompressedSize ) - this.binarySize = this.bundleExecutable.uncompressedSize - - - await this.storeMachoMeta( this.bundleExecutable ) - - this.binarySupportsNative = this.classifyBinaryEntryArchitecture( this.machoMeta ) - } - - async runScan () { - // Load in the file - this.sendMessage({ - message: '🚛 Loading file...', - status: 'loading' - }) - - this.file = await this.fileLoader() - - // console.log( 'File:', this.file ) - - this.bundleFileEntries = await this.readFileBlob( this.file ) - - this.sendMessage({ - message: 'đŸŽŦ Starting scan', - status: 'scanning' - }) - - await this.findTargetFiles() - - this.storeResultInfo() - - this.sendMessage({ - message: '🔎 Checking online for native versions...', - status: 'checking' - }) - - // Sleep for 3 seconds - // await new Promise( resolve => setTimeout( resolve, 3000 ) ) - - this.sendMessage({ - message: '🏁 Scan complete! ', - status: 'complete' - }) - } - - async start () { - - try { - - await this.runScan() - - } catch ( error ) { - this.sendMessage({ - message: 'đŸšĢ Error: ' + error.message, - status: 'error', - error - }) - } - + return { + scan, + appScanWorker } } diff --git a/helpers/scanner/scan.mjs b/helpers/scanner/scan.mjs new file mode 100644 index 0000000..00026b1 --- /dev/null +++ b/helpers/scanner/scan.mjs @@ -0,0 +1,436 @@ +import { Buffer } from 'buffer/index.js' +import prettyBytes from 'pretty-bytes' +import * as zip from '@zip.js/zip.js' + +import * as FileApi from '~/helpers/scanner/file-api.js' +import { isString, isNonEmptyString } from '~/helpers/check-types.js' +import { parsePlistBuffer } from '~/helpers/scanner/parsers/plist.js' +import { extractMachoMeta } from '~/helpers/scanner/parsers/macho.js' + +// https://gildas-lormeau.github.io/zip.js/core-api.html#configuration +zip.configure({ + // Disable Web Workers for SSR since Node doesn't support them yet + // https://vitejs.dev/guide/env-and-mode.html#env-variables + useWebWorkers: !import.meta.env.SSR +}) + + +function makeNodeFileBuffer ( buffer ) { + const fileBuffer = new Buffer.alloc( buffer.byteLength ) + + for (var i = 0; i < buffer.length; i++) + fileBuffer[i] = buffer[i]; + + // console.log( 'this.machoFileInstance', this.machoFileInstance.buffer.byteLength ) + + return fileBuffer +} + +export class AppScan { + constructor ({ + fileLoader, + messageReceiver + }) { + + this.fileLoader = fileLoader + this.messageReceiver = messageReceiver + + this.status = 'idle' + this.file = null + this.bundleFileEntries = [] + this.infoPlist = {} + this.machoExcutables = [] + + // Data that is derived after we've read the files and pulled out the infoPlist + this.appVersion = '' + this.displayName = '' + this.details = [] + this.bundleExecutable = null + this.displayBinarySize = '' + this.binarySize = 0 + this.machoMeta = {} + this.binarySupportsNative = undefined + + this.info = {} + } + + sendMessage ( details ) { + if ( details?.status ) { + this.status = details.status + } + + if ( typeof( this.messageReceiver ) === 'function' ) { + this.messageReceiver( details ) + } + } + + get hasInfoPlist () { + return Object.keys( this.infoPlist ).length > 0 + } + + get hasMachoMeta () { + return Object.keys( this.machoMeta ).length > 0 + } + + get hasInfo () { + return Object.keys( this.info ).length > 0 + } + + get bundleExecutablePath () { + if ( !this.hasInfoPlist ) return '' + + // There our CFBundleExecutable is a path to the executable + // then use it + if ( this.infoPlist.CFBundleExecutable.includes('/') ) return `/Contents/${ this.infoPlist.CFBundleExecutable }` + + // Use default executable path + return `/Contents/MacOS/${ this.infoPlist.CFBundleExecutable }` + } + + get supportedArchitectures () { + if ( !this.hasMachoMeta ) return [] + + return this.machoMeta.architectures.filter( architecture => architecture.processorType !== 0 ) + } + + async readFileEntryData ( fileEntry, Writer = zip.TextWriter ) { + // Get blob data from zip + // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry + return await fileEntry.getData( + // writer + // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing + new Writer()//zip.TextWriter(), + ) + } + + + async readFileBlob ( FileInstance ) { + return new Promise( async ( resolve, reject ) => { + // Check if file is a Blob, typically in the Browser + // otherwise convert it to a Blob, like in Node + // Both Browser and Node have Blob + // Node/Our File Polyfill references .arrayBuffer as a property + // Browser currently references .arrayBuffer as an async method + const FileBlob = FileInstance instanceof Blob ? FileInstance : new Blob( FileInstance.arrayBuffer ) + + // console.log( 'FileBlob', FileBlob ) + + const fileReader = new zip.BlobReader( FileBlob ) + + // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-reading + const zipReader = new zip.ZipReader( fileReader ) + + zipReader + .getEntries() + .then( entries => { + + // do something on entries + this.sendMessage({ + message: '📖 Reading file complete. Entries found', + status: 'read' + }) + + resolve( entries ) + }) + + }) + } + + classifyBinaryEntryArchitecture ( binaryEntry ) { + // Find an ARM Architecture + const armArchitecture = binaryEntry.architectures.find( architecture => { + // if ( architecture.processorType === 0 ) return false + + // If processorType not a string + // then return false + if ( !isString(architecture.processorType) ) return false + + return architecture.processorType.toLowerCase().includes('arm') + }) + + // Was an ARM Architecture found + return (armArchitecture !== undefined) + } + + matchesMachoExecutable ( entry ) { + // Skip files that are deeper than 3 folders + if ( entry.filename.split('/').length > 4 ) return false + + // Skip folders + // if ( !!entry.directory ) return false + + // `${ appName }.app/Contents/MacOS/${ appName }` + // Does this entry path match any of our wanted paths + return [ + // `${ appName }.app/Contents/MacOS/${ appName }` + // `.app/Contents/MacOS/`, + `Contents/MacOS/` + ].some( pathToMatch => { + return entry.filename.includes( pathToMatch ) + }) + } + + matchesRootInfoPlist ( entry ) { + // Skip files that are deeper than 2 folders + if ( entry.filename.split('/').length > 3 ) return false + + // Skip folders + if ( entry.filename.endsWith('/') ) return false + + // If this entry matches the root info.plist path exactly + // then we have found the root info.plist + if ( entry.filename === 'Contents/Info.plist' ) return true + + // Does this entry path match any of our wanted paths + return [ + // `zoom.us.app/Contents/Info.plist` + `.app/Contents/Info.plist`, + `.zip/Contents/Info.plist` + ].some( pathToMatch => { + return entry.filename.endsWith( pathToMatch ) + }) + } + + fileEntryType ( fileEntry ) { + if ( !!fileEntry.directory ) return 'directory' + + if ( this.matchesMachoExecutable( fileEntry ) ) return 'machoExecutable' + + if ( this.matchesRootInfoPlist( fileEntry ) ) return 'rootInfoPlist' + + // getData + + return 'unknown' + } + + storeInfoPlist = async ( fileEntry ) => { + // Throw if we have more than one target file + if ( this.hasInfoPlist ) { + throw new Error( 'More than one root info.plist found' ) + } + + const infoUint8Array = await this.readFileEntryData( fileEntry, zip.Uint8ArrayWriter ) + // console.log( 'infoUint8Array', infoUint8Array ) + + const infoNodeBuffer = makeNodeFileBuffer( infoUint8Array ) + + // Parse the Info.plist data + this.infoPlist = await parsePlistBuffer( infoNodeBuffer ) + + this.sendMessage({ + message: 'â„šī¸ Found Info.plist', + // data: this.infoPlist + }) + } + + storeMachoExecutable = ( fileEntry ) => { + this.machoExcutables.push( fileEntry ) + + this.sendMessage({ + message: 'đŸĨŠ Found a Macho executable', + // data: machoExecutable, + }) + } + + storeResultInfo () { + this.info = { + filename: this.file.name, + appVersion: this.appVersion, + result: this.binarySupportsNative ? '✅' : 'đŸ”ļ', + machoMeta: { + ...this.machoMeta, + file: undefined, + architectures: this.machoMeta.architectures.map( architecture => { + return { + bits: architecture.bits, + fileType: architecture.fileType, + header: architecture.header, + loadCommandsInfo: architecture.loadCommandsInfo, + magic: architecture.magic, + offset: architecture.offset, + processorSubType: architecture.processorSubType, + processorType: architecture.processorType, + } + }) + }, + infoPlist: this.infoPlist, + } + } + + storeMachoMeta = async ( fileEntry ) => { + // Throw if we have more than one target file + if ( this.hasMachoMeta ) { + throw new Error( 'More than one primary Macho executable found' ) + } + + // Get zip as Uint8Array + const bundleExecutableUint8Array = await this.readFileEntryData( fileEntry, zip.Uint8ArrayWriter ) + + const machoFileInstance = new FileApi.File({ + name: this.bundleExecutable.filename, + type: 'application/x-mach-binary', + buffer: Buffer.from( bundleExecutableUint8Array ) + }) + + // Get zip as blob + // so we can use it in for the File API when we're in the browser context + // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry + machoFileInstance.blob = await this.readFileEntryData( fileEntry, zip.BlobWriter ) + + this.machoMeta = await extractMachoMeta({ + machoFileInstance, + FileApi + }) + + // console.log( 'this.machoMeta', this.machoMeta ) + + } + + + targetFiles = { + rootInfoPlist: { + method: this.storeInfoPlist + }, + machoExecutable: { + method: this.storeMachoExecutable, + } + } + + findMainExecutable () { + + // Now that we have the info.plist Determine our entry Macho Executable from the list of Macho Executables + const bundleExecutables = this.machoExcutables.filter( machoEntry => { + + if ( machoEntry.filename.includes( this.bundleExecutablePath ) ) { + return true + } + + return this.bundleExecutablePath.includes( machoEntry.filename ) + }) + + // Warn if Bundle Executable doesn't look right + if ( bundleExecutables.length > 1) { + throw new Error('More than one root bundleExecutable found', bundleExecutables) + } else if ( bundleExecutables.length === 0 ) { + throw new Error('No root bundleExecutable found', bundleExecutables) + } + + return bundleExecutables[ 0 ] + } + + async findTargetFiles () { + + for ( const fileEntry of this.bundleFileEntries ) { + const type = this.fileEntryType( fileEntry ) + + // console.log( 'fileEntry', type, fileEntry.filename ) + + // Check if we have a target file + if ( this.targetFiles[ type ] ) { + // console.log( 'fileEntry', type, fileEntry.filename ) + + // Call the target file method + await this.targetFiles[ type ].method( fileEntry ) + } + + // console.log( 'File Entry Type:', type ) + } + + // Now that we have the info.plist Determine our entry Macho Executable from the list of Macho Executables + + // Find valid app version that is a string but not empty + this.appVersion = ([ + this.infoPlist.CFBundleShortVersionString, + this.infoPlist.CFBundleVersion + ]).find( isNonEmptyString )[0] + + // Find Display Name that is a string but not empty + this.displayName = ([ + this.infoPlist.CFBundleDisplayName, + this.infoPlist.CFBundleName, + this.infoPlist.CFBundleExecutable, + ]).find( isNonEmptyString )[0] + + // We loop through possible details and add them to the details array + // if they are not empty + ;([ + [ 'Version', this.infoPlist.CFBundleShortVersionString ], + [ 'Bundle Identifier', this.infoPlist.CFBundleIdentifier ], + [ 'File Mime Type', this.file.type ], + [ 'Copyright', this.infoPlist.NSHumanReadableCopyright ], + // [ 'Version', info.CFBundleShortVersionString ], + ]).forEach( ([ label, value ]) => { + if ( !value || value.length === 0 ) return + + this.details.push({ + label, + value, + }) + } ) + + // Set the bundleExecutable + this.bundleExecutable = this.findMainExecutable() + + console.log('Parsing ', this.bundleExecutable.filename, this.bundleExecutable.uncompressedSize / 1000 ) + + this.displayBinarySize = prettyBytes( this.bundleExecutable.uncompressedSize ) + this.binarySize = this.bundleExecutable.uncompressedSize + + + await this.storeMachoMeta( this.bundleExecutable ) + + this.binarySupportsNative = this.classifyBinaryEntryArchitecture( this.machoMeta ) + } + + async runScan () { + // Load in the file + this.sendMessage({ + message: '🚛 Loading file...', + status: 'loading' + }) + + this.file = await this.fileLoader() + + // console.log( 'File:', this.file ) + + this.bundleFileEntries = await this.readFileBlob( this.file ) + + this.sendMessage({ + message: 'đŸŽŦ Starting scan', + status: 'scanning' + }) + + await this.findTargetFiles() + + this.storeResultInfo() + + this.sendMessage({ + message: '🔎 Checking online for native versions...', + status: 'checking' + }) + + // Sleep for 3 seconds + // await new Promise( resolve => setTimeout( resolve, 3000 ) ) + + this.sendMessage({ + message: '🏁 Scan complete! ', + status: 'complete' + }) + } + + async start () { + + try { + + await this.runScan() + + } catch ( error ) { + this.sendMessage({ + message: 'đŸšĢ Error: ' + error.message, + status: 'error', + error + }) + } + + } +} diff --git a/helpers/scanner/worker.js b/helpers/scanner/worker.js index 3f90c33..502ebdd 100644 --- a/helpers/scanner/worker.js +++ b/helpers/scanner/worker.js @@ -1,4 +1,4 @@ -import { AppScan } from '~/helpers/scanner/client.mjs' +import { AppScan } from '~/helpers/scanner/scan.mjs' self.onmessage = async ( event ) => { @@ -26,7 +26,7 @@ self.onmessage = async ( event ) => { self.postMessage( { - status: 'finished', + status: 'finished', scan }) diff --git a/test/scanner/client.test.mjs b/test/scanner/client.test.mjs index 58b6c55..26ed7a4 100644 --- a/test/scanner/client.test.mjs +++ b/test/scanner/client.test.mjs @@ -15,7 +15,7 @@ import glob from 'fast-glob' import { LocalFileData } from 'get-file-object-from-local-path' import { Zip } from 'zip-lib' -import AppScanWorker from '~/helpers/scanner/worker.js?worker' +import { runScanWorker } from '~/helpers/scanner/client.mjs' const appGlobOptions = { @@ -74,55 +74,6 @@ async function makeZipFromBundlePath ( bundlePath ) { return archiveFile } - -async function runScanWorker ( file ) { - // console.log( 'file', file ) - - const appScanWorker = new AppScanWorker() - - const scan = await new Promise( ( resolve, reject ) => { - // Set up the worker message handler - appScanWorker.onmessage = async (event) => { - // console.log( 'Main received message', event ) - - const { status } = event.data - - // Resolves promise on finished status - if ( status === 'finished' ) { - const { scan } = event.data - resolve( scan ) - } - } - - // Set up the worker error handler - appScanWorker.onerror = async ( errorEvent ) => { - // console.log( 'appScanWorker.onerror', errorEvent ) - reject() - } - - // Start the worker - // https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage - appScanWorker.postMessage( { - status: 'start', - options: { - fileLoader: () => ({ - ...file, - arrayBuffer: file.arrayBuffer - }), - messageReceiver: ( details ) => { - console.log( 'Scan message:', details ) - } - } - }, [ file.arrayBuffer ] ) - }) - - return { - scan, - appScanWorker - } -} - - describe.concurrent('Apps', async () => { // Compress plain app bundles to zipped File Objects @@ -131,9 +82,10 @@ describe.concurrent('Apps', async () => { // Get the App's file name from bundlePath const appName = path.basename( bundlePath ) - // Generate a faux JavaScript File instance + // Generate a faux JavaScript File instance const file = await makeZipFromBundlePath( bundlePath ) + // Scan the app const { scan } = await runScanWorker( file )