Skip to content

Commit e2817a9

Browse files
Merge remote-tracking branch 'origin/master' into 9.9.0
2 parents 6086000 + 9ef278b commit e2817a9

3 files changed

Lines changed: 374 additions & 3 deletions

File tree

src/ptk/automationBridge.js

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,31 @@
5050
})
5151
}
5252

53+
/**
54+
* Prefer the native encoder when available, but keep a fallback
55+
* because PTK still supports older browser/extension
56+
*/
57+
function bytesToBase64(value) {
58+
if (!(value instanceof Uint8Array)) {
59+
throw new TypeError('unsupported_chunk_payload_type')
60+
}
61+
if (!value.length) return ''
62+
63+
if (typeof value.toBase64 === 'function') {
64+
return value.toBase64()
65+
}
66+
67+
const chunkSize = 0x8000
68+
const parts = []
69+
// Build a binary string in safe chunks for btoa() on older runtimes
70+
for (let i = 0; i < value.length; i += chunkSize) {
71+
const slice = value.subarray(i, i + chunkSize)
72+
parts.push(String.fromCharCode.apply(null, slice))
73+
}
74+
75+
return btoa(parts.join(''))
76+
}
77+
5378
window.addEventListener('message', (event) => {
5479
// Only accept messages from same window
5580
if (event.source !== window) return
@@ -98,7 +123,7 @@
98123
version: this.version,
99124
bridgeId: this.bridgeId,
100125
capabilities: enabled
101-
? ['startSession', 'endSession', 'getStats', 'getFindings', 'exportScan', 'getSessionProgress']
126+
? ['startSession', 'endSession', 'getStats', 'getFindings', 'exportScan', 'getSessionProgress', 'exportScanChunk', 'releaseExportScan']
102127
: [],
103128
automationEnabled: enabled,
104129
error: enabled ? undefined : 'automation_disabled'
@@ -287,6 +312,55 @@
287312
}
288313
},
289314

315+
// Return chunk data as base64 at the page boundary so external callers get a JSON-safe response shape
316+
async exportScanChunk(options = {}) {
317+
if (this._automationEnabled === false) {
318+
return { ok: false, error: 'automation_disabled' }
319+
}
320+
321+
try {
322+
const response = await sendMessage('export-scan-chunk', { options })
323+
// Normalise low-level failure variants from the extension side into one bridge-level check
324+
if (response.ok === false || response.success === false) {
325+
return { ok: false, error: response.error || 'export_not_found_or_expired' }
326+
}
327+
328+
if (!(response.chunk instanceof Uint8Array)) {
329+
return { ok: false, error: 'unsupported_chunk_payload_type' }
330+
}
331+
332+
const chunkBytes = response.chunk
333+
return {
334+
ok: true,
335+
exportId: response.exportId,
336+
index: response.index,
337+
chunkCount: response.chunkCount,
338+
encoding: 'base64',
339+
byteLength: chunkBytes.byteLength,
340+
chunkBase64: bytesToBase64(chunkBytes)
341+
}
342+
} catch (err) {
343+
return { ok: false, error: err.message }
344+
}
345+
},
346+
347+
// Release retained chunked export state once the caller has finished fetching the report
348+
async releaseExportScan(options = {}) {
349+
if (this._automationEnabled === false) {
350+
return { ok: false, error: 'automation_disabled' }
351+
}
352+
353+
try {
354+
const response = await sendMessage('release-export-scan', { options })
355+
if (response.ok === false || response.success === false) {
356+
return { ok: false, error: response.error || 'export_not_found_or_expired' }
357+
}
358+
return { ok: true }
359+
} catch (err) {
360+
return { ok: false, error: err.message }
361+
}
362+
},
363+
290364
isAvailable() { return true },
291365
getSessionId() { return currentSessionId },
292366
_automationEnabled: initialAutomationEnabled

src/ptk/content.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,41 @@ function sendRuntimeMessage(payload) {
5454
}
5555
}
5656

57+
function normalizeAutomationBridgeResponse(type, response) {
58+
// Some replies use ok, some use success; only explicit false means failure here
59+
const isSuccessfulResponse = response && response.ok !== false && response.success !== false
60+
if (type === 'export-scan-chunk' && isSuccessfulResponse) {
61+
if (response.chunk instanceof Uint8Array) {
62+
return response
63+
}
64+
65+
const serializedChunk = response.chunk
66+
if (Array.isArray(serializedChunk)) {
67+
return {
68+
...response,
69+
chunk: Uint8Array.from(serializedChunk)
70+
}
71+
}
72+
73+
if (serializedChunk && typeof serializedChunk === 'object') {
74+
// Chrome runtime messaging uses JSON serialization, so Uint8Array
75+
// chunk payloads can arrive as numeric-keyed plain objects here
76+
const byteKeys = Object.keys(serializedChunk)
77+
.filter((key) => /^\d+$/.test(key))
78+
.sort((left, right) => Number(left) - Number(right))
79+
80+
if (byteKeys.length) {
81+
return {
82+
...response,
83+
chunk: Uint8Array.from(byteKeys.map((key) => serializedChunk[key]))
84+
}
85+
}
86+
}
87+
}
88+
89+
return response
90+
}
91+
5792
let ptkUserInteractionSent = false;
5893
let ptkUserInteractionHooked = false;
5994

@@ -689,7 +724,16 @@ window.addEventListener("message", (event) => {
689724
}, false)
690725

691726
function handleAutomationBridgeMessage(data) {
692-
const validTypes = ['session-start', 'session-end', 'get-stats', 'get-findings', 'export-scan', 'get-session-progress']
727+
const validTypes = [
728+
'session-start',
729+
'session-end',
730+
'get-stats',
731+
'get-findings',
732+
'export-scan',
733+
'get-session-progress',
734+
'export-scan-chunk',
735+
'release-export-scan'
736+
]
693737
if (!validTypes.includes(data.type)) return
694738

695739
const payload = {
@@ -705,11 +749,12 @@ function handleAutomationBridgeMessage(data) {
705749
}
706750

707751
browser.runtime.sendMessage(payload).then((response) => {
752+
const normalizedResponse = normalizeAutomationBridgeResponse(data.type, response)
708753
window.postMessage({
709754
source: 'ptk-extension',
710755
nonce: automationNonce, // Include nonce in response
711756
requestId: data.requestId,
712-
...response
757+
...normalizedResponse
713758
}, '*')
714759
}).catch((error) => {
715760
console.error('[PTK Content] Message error:', error)

0 commit comments

Comments
 (0)