libkernel
Advanced tools
Comparing version 0.0.48 to 0.0.49
@@ -1,2 +0,2 @@ | ||
export { init, newKernelQuery } from './init'; | ||
export { init, postKernelQuery } from './init'; | ||
export { testMessage, callModule, upload, padAndEncrypt } from './messages'; |
@@ -1,2 +0,2 @@ | ||
export { init, newKernelQuery } from './init'; | ||
export { init, postKernelQuery } from './init'; | ||
export { testMessage, callModule, upload, padAndEncrypt } from './messages'; |
export declare function log(...inputs: any): void; | ||
export declare function logErr(...inputs: any): void; | ||
export declare function composeErr(...inputs: any): string; | ||
export declare function newKernelQuery(data: any, update: Function): [Function, Promise<any>]; | ||
export declare function postKernelQuery(kernelQuery: any): Promise<any>; | ||
export declare function init(): Promise<string>; |
216
dist/init.js
@@ -10,42 +10,34 @@ // log provides a wrapper for console.log that prefixes 'libkernel'. | ||
} | ||
// composeErr takes a series of inputs and composes them into a single string. | ||
// Each element will be separated by a newline. If the input is not a string, | ||
// it will be transformed into a string with JSON.stringify. | ||
// | ||
// Any object that cannot be stringified will be skipped, though an error will | ||
// be logged. | ||
export function composeErr(...inputs) { | ||
let result = ""; | ||
for (let i = 0; i < inputs.length; i++) { | ||
// Prepend a newline if this isn't the first element. | ||
if (i !== 0) { | ||
result += "\n"; | ||
} | ||
// Strings can be added without modification. | ||
if (typeof inputs[i] === "string") { | ||
result += inputs[i]; | ||
continue; | ||
} | ||
// Everything else needs to be stringified, log an error if it | ||
// fails. | ||
try { | ||
let str = JSON.stringify(inputs[i]); | ||
result += str; | ||
} | ||
catch (_a) { | ||
logErr("unable to stringify input to composeErr"); | ||
} | ||
} | ||
return result; | ||
} | ||
// Establish a hashmap for matching queries to their responses by their nonces. | ||
// nextNonce needs to start at '1' because '0' is reserved for the bridgeTest | ||
// method performed at init. | ||
const namespace = "libkernel-v0"; | ||
var initialized; | ||
var bridgeExists; | ||
var bridgeAvailable; | ||
var blockForBridge = new Promise((resolve, reject) => { | ||
bridgeAvailable = { resolve, reject }; | ||
}); | ||
// Establish a system for matching messages to the kernel with responses from | ||
// the kernel. The nonce is incremented every time a new message is sent, and | ||
// the queries object is used as a hashmap that maps a given message nonce to | ||
// the {resolve, reject} of a promise that will be resolved/rejected when a | ||
// response to the corresponding message is provided. | ||
var nextNonce = 1; | ||
var queries = new Object(); | ||
// postKernelQuery will send a postMessage to the kernel, handling details like | ||
// the nonce and the resolve/reject upon receiving a response. The inputs are a | ||
// resolve and reject function of a promise that should be resolved when the | ||
// response is received, and the message that is going to the kernel itself. | ||
export function postKernelQuery(kernelQuery) { | ||
return new Promise((resolve, reject) => { | ||
let nonce = nextNonce; | ||
nextNonce++; | ||
queries[nonce] = { resolve, reject }; | ||
window.postMessage({ | ||
method: "kernelQuery", | ||
nonce, | ||
kernelQuery, | ||
}, window.location.origin); | ||
}); | ||
} | ||
// handleBridgeResponse will handle a response from the bridge indicating that | ||
// the bridge is working. This needs to be declared before the remaining bridge | ||
// variables because they need to reference it as a handler. | ||
function handleBridgeResponse(resolve, reject, data) { | ||
// the bridge is working. | ||
function handleBridgeResponse(data) { | ||
// Check whether the timeout for the bridge has already fired. If so, | ||
@@ -55,3 +47,2 @@ // log that the bridge is available but late. | ||
logErr("received late signal from bridge"); | ||
reject("received late signal from bridge"); | ||
return; | ||
@@ -61,119 +52,69 @@ } | ||
// Check whether the version is available in the data. | ||
if ("version" in data) { | ||
resolve(data.version); | ||
if (!("version" in data)) { | ||
bridgeAvailable.resolve("bridge did not report a version"); | ||
} | ||
else { | ||
resolve("bridge did not report a version"); | ||
bridgeAvailable.resolve(data.version); | ||
} | ||
} | ||
// Establish a system to test if the bridge script is running. bridgeExists is | ||
// a boolean which establishes whether or not we have already determinied if | ||
// the bridge eixsts, bridgeAvailable is the {resolve, reject} object for a | ||
// promise, and the blockForBridge will resolve/reject when we have determined | ||
// if the bridge exists. | ||
// | ||
// The init() function will use a timeout to decide that the bridge does not | ||
// exist, the hanelMessage function will look for a method called | ||
// "bridgeTestResponse" to determine that the bridge does exist. The init | ||
// script needs to send the bridge a "bridgeTestQuery" message so the bridge | ||
// knows to respond. | ||
var initialized; | ||
var bridgeExists; | ||
var bridgeAvailable; | ||
var blockForBridge = new Promise((resolve, reject) => { | ||
bridgeAvailable = { resolve, reject, update: null, handle: handleBridgeResponse }; | ||
}); | ||
// newKernelQuery will send a postMessage to the kernel, handling details like | ||
// the nonce and the resolve/reject upon receiving a response. | ||
// | ||
// The first return value is a function that can be called to send a | ||
// 'queryUpdate' to the kernel for that nonce. | ||
export function newKernelQuery(data, update) { | ||
let nonce = nextNonce; | ||
nextNonce++; | ||
let sendUpdate = function (data) { | ||
queryUpdate(nonce, data); | ||
}; | ||
let p = new Promise((resolve, reject) => { | ||
queries[nonce] = { resolve, reject, update, handle: handleKernelResponse }; | ||
window.postMessage({ | ||
namespace, | ||
method: "newKernelQuery", | ||
nonce, | ||
data, | ||
}); | ||
}); | ||
return [sendUpdate, p]; | ||
} | ||
// queryUpdate is a function that can be called to send a queryUpdate to an | ||
// existing query. | ||
function queryUpdate(nonce, data) { | ||
window.postMessage({ | ||
namespace, | ||
method: "queryUpdate", | ||
nonce, | ||
data, | ||
}); | ||
} | ||
// handleKernelResponse will parse the kernel's response from the bridge and | ||
// resolve/reject the promise associated with the nonce. | ||
function handleKernelResponse(resolve, reject, data) { | ||
resolve(data); | ||
} | ||
// handleMessage will handle a message from the kernel, using the response to | ||
// resolve the appropriate promise in the set of queries. | ||
function handleMessage(event) { | ||
// Check the message source. | ||
if (event.source !== window) { | ||
function handleKernelResponse(event) { | ||
// Check that we have a promise for the provided nonce. | ||
if (!(event.data.nonce in queries)) { | ||
logErr("nonce of kernelResponse not found\n", event, "\n", queries); | ||
return; | ||
} | ||
// Check that this message is a response targeting libkernel. | ||
if (event.data.namespace !== namespace) { | ||
let result = queries[event.data.nonce]; | ||
delete queries[event.data.nonce]; | ||
// Check the status and then resolve or reject accordingly. | ||
if (!("response" in event.data) || !("queryStatus" in event.data.response)) { | ||
logErr("malformed kernel response\n", event); | ||
return; | ||
} | ||
if (!("method" in event.data) || typeof event.data.method !== "string") { | ||
logErr("received message targeting our namespace with malformed method", event.data); | ||
return; | ||
if (event.data.response.queryStatus === "resolve") { | ||
result.resolve(event.data.response); | ||
} | ||
if (event.data.method !== "response" && event.data.method !== "responseUpdate") { | ||
// We don't log here because that would catch outbound messages | ||
// from ourself. | ||
else if (event.data.response.queryStatus === "reject") { | ||
result.reject(event.data.response); | ||
} | ||
else { | ||
logErr("malformed queryStatus"); | ||
} | ||
} | ||
// handleKernelResponseErr is a special handler for situations where the | ||
// content script was unable to communicate with the background script. | ||
function handleKernelResponseErr(event) { | ||
let reject = queries[event.data.nonce]; | ||
delete queries[event.data.nonce]; | ||
if (!("err" in event.data) || typeof event.data.err !== "string") { | ||
logErr("malformed error received from bridge"); | ||
return; | ||
} | ||
// Check that we have a nonce for this message. | ||
if (!(event.data.nonce in queries)) { | ||
logErr("message received with no matching nonce\n", event.data, "\n", queries); | ||
logErr(event.data.err); | ||
reject(event.data.err); | ||
} | ||
// handleMessage will handle a message from the kernel, using the response to | ||
// resolve the appropriate promise in the set of queries. | ||
function handleMessage(event) { | ||
// Check the message source. | ||
if (event.source !== window) { | ||
return; | ||
} | ||
// If this is a responseUpdate, pass the data to the update handler. | ||
if (event.data.method === "responseUpdate") { | ||
let handler = queries[event.data.nonce]; | ||
if (!("update" in handler) || typeof handler.update !== "function") { | ||
logErr("responseUpdate received, but no update method defined in handler"); | ||
return; | ||
} | ||
if (!("data" in event.data)) { | ||
logErr("responseUpdate received, but no data provided: " + JSON.stringify(event.data)); | ||
return; | ||
} | ||
handler.update(event.data.data); | ||
// Check that this message is a kernelResponse. | ||
if (!("data" in event) || !("method" in event.data)) { | ||
return; | ||
} | ||
// The method is "response", meaning the query is closed out can can be | ||
// deleted. | ||
let handler = queries[event.data.nonce]; | ||
delete queries[event.data.nonce]; | ||
if (!("err" in event.data)) { | ||
handler.reject("no err field provided in response: " + JSON.stringify(event.data)); | ||
if (event.data.method === "bridgeTestResponse") { | ||
handleBridgeResponse(event.data); | ||
return; | ||
} | ||
if (event.data.err !== null) { | ||
handler.reject("response to bridge contained an error: " + event.data.err); | ||
if (event.data.method === "kernelResponse") { | ||
handleKernelResponse(event); | ||
return; | ||
} | ||
if (!("data" in event.data)) { | ||
handler.reject("no data field provided in query: " + JSON.stringify(event.data)); | ||
if (event.data.method === "kernelResponseErr") { | ||
handleKernelResponseErr(event); | ||
return; | ||
} | ||
handler.handle(handler.resolve, handler.reject, event.data.data); | ||
} | ||
@@ -192,10 +133,9 @@ // init will add an event listener for messages from the kernel bridge. It is | ||
window.addEventListener("message", handleMessage); | ||
// Send a message checking if the bridge is alive and responding. The | ||
// nonce '0' is kept available explicitly for this purpose. | ||
// Send a message checking if the bridge is alive and responding. We | ||
// use a fake nonce because we don't care about the nonce for the | ||
// bridge. | ||
window.postMessage({ | ||
namespace, | ||
method: "bridgeTestQuery", | ||
nonce: 0, | ||
method: "test", | ||
}); | ||
queries[0] = bridgeAvailable; | ||
// After 2 seconds, check whether the bridge has responded. If not, | ||
@@ -206,3 +146,3 @@ // fail the bridge. | ||
bridgeExists = false; | ||
bridgeAvailable.reject("bridge unavailable, need skynet extension"); | ||
bridgeAvailable.reject(new Error("bridge unavailable, need skynet extension")); | ||
logErr("bridge did not respond after 2 seconds"); | ||
@@ -209,0 +149,0 @@ } |
export declare function testMessage(): Promise<string>; | ||
export declare function callModule(module: string, method: string, data: any): Promise<any>; | ||
export declare function callModule(module: string, moduleMethod: string, moduleInput: any): Promise<any>; | ||
export declare function upload(filename: string, fileData: Uint8Array): Promise<string>; | ||
export declare function padAndEncrypt(filepath: string, fileData: Uint8Array): Promise<string>; |
@@ -1,3 +0,2 @@ | ||
import { logErr, composeErr, init, newKernelQuery } from './init'; | ||
const noBridge = "the bridge failed to initialize (do you have the Skynet browser extension?)"; | ||
import { logErr, init, postKernelQuery } from './init'; | ||
// testMessage will send a test message to the kernel, ensuring that basic | ||
@@ -18,27 +17,12 @@ // kernel communications are working. The promise will resolve to the version | ||
.then(x => { | ||
// Send a 'test' message to the kernel, which is a | ||
// method with no parameters. | ||
// | ||
// The first return value of newKernelQuery is ignored | ||
// because it is an update function that we can call if | ||
// we wish to provide new information to the query via | ||
// a 'queryUpdate'. The 'test' method does not support | ||
// any queryUpdates. | ||
// | ||
// The second input of newKernelQuery is passed as | ||
// null, it's usually a handler function to accept | ||
// 'responseUpdate' messages from the kernel related to | ||
// the query. The 'test' method doesn't have any | ||
// 'responseUpdates', so there is no need to create an | ||
// updateHandler. | ||
let [_, query] = newKernelQuery({ | ||
method: "test", | ||
}, null); | ||
// We use nested promises instead of promise chaining | ||
// because promise chaining didn't provide enough | ||
// control over handling the error. We like wrapping | ||
// our errors to help indicate exactly which part of | ||
// the code has gone wrong, and that nuance gets lost | ||
// with promise chaining. | ||
query.then(response => { | ||
// Send a 'requestTest' message to the kernel. The | ||
// request test message uniquely doesn't have any other | ||
// parameters. | ||
postKernelQuery({ | ||
kernelMethod: "requestTest", | ||
}) | ||
// We use nested promises instead of promise chaining | ||
// because promise chaining didn't provide enough | ||
// control over handling the error. | ||
.then(response => { | ||
if (!("version" in response)) { | ||
@@ -50,5 +34,9 @@ resolve("kernel did not report a version"); | ||
}) | ||
.catch(err => { | ||
let cErr = composeErr("newKernelQuery failed", err); | ||
reject(cErr); | ||
.catch(response => { | ||
if (!("err" in response) || typeof response.err !== "string") { | ||
logErr("unrecognized response in postKernelQuery catch", response); | ||
reject("unrecognized repsonse"); | ||
return; | ||
} | ||
reject(response.err); | ||
}); | ||
@@ -60,5 +48,4 @@ }) | ||
// the browser extension. | ||
let cErr = composeErr(noBridge, err); | ||
logErr(cErr); | ||
reject(cErr); | ||
logErr("bridge is not initialized:", err); | ||
reject(err); | ||
}); | ||
@@ -70,30 +57,21 @@ }); | ||
// handled for the user. | ||
// | ||
// callModule can only be used for query-response communications, there is no | ||
// support for sending queryUpdate messages or receiving responseUpdate | ||
// messages. If you need those, use 'connectModule' instead. | ||
export function callModule(module, method, data) { | ||
export function callModule(module, moduleMethod, moduleInput) { | ||
return new Promise((resolve, reject) => { | ||
init() | ||
.then(x => { | ||
let [_, query] = newKernelQuery({ | ||
method: "moduleCall", | ||
data: { | ||
module, | ||
method, | ||
data, | ||
} | ||
}, null); | ||
query.then(response => { | ||
return postKernelQuery({ | ||
kernelMethod: "moduleCall", | ||
module, | ||
moduleMethod, | ||
moduleInput, | ||
}) | ||
.then(response => { | ||
resolve(response); | ||
}) | ||
.catch(err => { | ||
let cErr = composeErr("moduleCall query to kernel failed", err); | ||
reject(err); | ||
.catch(response => { | ||
reject(response); | ||
}); | ||
}) | ||
.catch(err => { | ||
let cErr = composeErr(noBridge, err); | ||
logErr(cErr); | ||
reject(cErr); | ||
reject(err); | ||
}); | ||
@@ -115,3 +93,3 @@ }); | ||
.then(x => { | ||
let [_, query] = newKernelQuery({ | ||
return postKernelQuery({ | ||
kernelMethod: "moduleCall", | ||
@@ -124,8 +102,8 @@ module: "AQCS3RHbDlk00IdICFEI1rKZp-VNsnsKWC0n7K-taoAuog", | ||
}, | ||
}, null); | ||
query.then(response => { | ||
}) | ||
.then(response => { | ||
resolve(response.output); | ||
}) | ||
.catch(err => { | ||
reject(err); | ||
.catch(response => { | ||
reject(response.err); | ||
}); | ||
@@ -149,3 +127,3 @@ }) | ||
.then(x => { | ||
let [_, query] = newKernelQuery({ | ||
postKernelQuery({ | ||
kernelMethod: "moduleCall", | ||
@@ -158,4 +136,4 @@ module: "AQAs00kS6OKUd-FIWj9qdJLArCiEDMVgYBSkaetuTF-MsQ", | ||
}, | ||
}, null); | ||
query.then(response => { | ||
}) | ||
.then(response => { | ||
resolve(response.output); | ||
@@ -162,0 +140,0 @@ }) |
{ | ||
"name": "libkernel", | ||
"version": "0.0.48", | ||
"version": "0.0.49", | ||
"description": "helper library to interact with the skynet kernel", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
11663
300