@repugraf/cross-domain-storage
Version:
Enables shared cross domain localStorage and sessionStorage
140 lines (139 loc) • 4.89 kB
JavaScript
import { createMessage, error, debugLog } from "./shared.js";
/**
* Creates client instance
*
* Call `connect` to start communications with the server
*
* ```js
* const client = getClient({
* domain: "https://www.example.com"
* });
*
* await client.connect();
*
* await client.set("key", "val");
*
* await client.get("key");
* ```
*/
const getClient = (config) => {
var _a, _b, _c, _d, _e;
const timeout = (_a = config.timeout) !== null && _a !== void 0 ? _a : 10000;
const debug = (_b = config.debug) !== null && _b !== void 0 ? _b : false;
const target = (_c = config.target) !== null && _c !== void 0 ? _c : document.body;
const duplicate = (_d = config.duplicate) !== null && _d !== void 0 ? _d : false;
const fallback = (_e = config.fallback) !== null && _e !== void 0 ? _e : false;
const iframe = document.createElement("iframe");
iframe.setAttribute("src", config.domain);
iframe.style.width = "0";
iframe.style.height = "0";
iframe.style.display = "none";
iframe.id = "cross-domain-storage";
let isConnected = false;
/**
* Connects to specified domain
*
* Under the hood will append invisible iframe to `document.body` with `src` of specified domain
*
* @param _timeout Timeout after which the method will reject
*/
const connect = (_timeout = timeout) => {
return new Promise((resolve, reject) => {
const timeoutID = setTimeout(() => reject(false), _timeout);
iframe.onload = () => {
clearTimeout(timeoutID);
isConnected = true;
debugLog(debug, `Connected to server (${config.domain})`);
resolve();
};
iframe.onerror = e => {
clearTimeout(timeoutID);
error(debug, `Failed to connect to server (${config.domain})`, e);
reject(e);
};
target.appendChild(iframe);
}).catch(e => error(debug, e));
};
/**
* Disconnect from specified domain
*
* Under the hood will remove invisible iframe from `document.body`
*/
const disconnect = () => {
target.removeChild(iframe);
isConnected = false;
};
const set = (key, value, storageType) => {
return handleOperation({
method: "set",
key,
value,
storageType
});
};
const get = (key, storageType) => {
return handleOperation({
method: "get",
key,
storageType
});
};
const remove = (key, storageType) => {
return handleOperation({
method: "remove",
key,
storageType
});
};
const handleOperation = async (props) => {
var _a;
const message = createMessage({
method: props.method,
key: props.key,
value: props.value,
storageType: (_a = props.storageType) !== null && _a !== void 0 ? _a : "localStorage"
});
try {
const { result } = await new Promise((resolve, reject) => {
const clearListener = () => window.removeEventListener("message", handler);
const timeoutID = setTimeout(() => {
clearListener();
reject(new Error(`Timeout (${timeout})`));
}, timeout);
const handler = (e) => {
const response = e.data;
if (!response || response.source !== "cross-domain-storage" || message.id !== response.id)
return;
clearTimeout(timeoutID);
return response.isError ? reject(new Error(response.result)) : resolve(response);
};
window.addEventListener("message", handler);
if (!isConnected || !iframe.contentWindow) {
clearListener();
return reject(new Error("Not connected"));
}
iframe.contentWindow.postMessage(message, config.domain);
if (duplicate)
window[message.storageType][`${message.method}Item`](message.key, message.value);
});
debugLog(debug, `[Client] Action executed: ${message.storageType}.${message.method}Item(${message.key}${message.value ? `, ${message.value}` : ""})`);
return result;
}
catch (err) {
error(debug, err);
if (fallback)
return window[message.storageType][`${message.method}Item`](message.key, message.value);
}
};
return {
connect,
disconnect,
set,
get,
remove,
get isConnected() {
return isConnected;
}
};
};
export { getClient };