@repugraf/cross-domain-storage
Version:
Enables shared cross domain localStorage and sessionStorage
142 lines (121 loc) • 3.45 kB
text/typescript
import {
IMethod,
IRequestMessage,
IResponseMessage,
IStorageType,
error,
debugLog
} from "./shared";
interface IAllowedDomain {
/** RegExp of allowed domain */
origin: RegExp;
allowedMethods?: IMethod[];
allowedStorageTypes?: IStorageType[];
}
interface IServerConfig {
/** List of allowed domains. Domains not included in this list will be rejected */
allowedDomains: IAllowedDomain[];
/** Will log errors, warnings and useful info */
debug?: boolean;
}
/**
* Creates a server to listen to clients
*
* Call `listen` to start accepting client connections
*
* ```js
* const server = getServer({
* allowedDomains: [
* {
* origin: /sub1.example.com$/,
* allowedMethods: ["get", "set", "remove"]
* },
* {
* origin: /sub2.example.com$/,
* allowedMethods: ["get"]
* }
* ]
* });
*
* await server.listen();
* ```
*/
const getServer = (config: IServerConfig) => {
const domains = config.allowedDomains ?? [];
const debug = config.debug ?? false;
const clients = new Set<string>();
const handler = (e: MessageEvent<any>) => {
const post = postMessage.bind(null, e.origin);
const initialMessage: IResponseMessage = {
id: "",
source: "cross-domain-storage",
isResponse: true,
isError: false,
result: null
};
try {
const data: IRequestMessage = e.data;
if (data.source !== "cross-domain-storage") return;
initialMessage.id = data.id;
const currentDomain = domains.find(d => d.origin.test(e.origin));
if (!currentDomain) {
return post({
...initialMessage,
result: `Domain not allowed (${e.origin})`,
isError: true
});
}
const allowedMethods = currentDomain.allowedMethods ?? ["get", "set", "remove"];
const allowedStorageTypes = currentDomain.allowedStorageTypes ?? [
"localStorage",
"sessionStorage"
];
if (!allowedMethods.includes(data.method)) {
return post({
...initialMessage,
result: `Method not allowed (${data.method})`,
isError: true
});
}
if (!allowedStorageTypes.includes(data.storageType)) {
return post({
...initialMessage,
result: `Storage type not allowed (${data.storageType})`,
isError: true
});
}
clients.add(e.origin);
const result = window[data.storageType][`${data.method}Item`](data.key, data.value);
debugLog(
debug,
`[Server] Action executed: ${data.storageType}.${data.method}Item(${data.key}${
data.value ? `, ${data.value}` : ""
})`
);
post({ ...initialMessage, result });
} catch (err) {
error(debug, err);
}
};
/** Start listening for incoming connections */
const listen = () => {
window.addEventListener("message", handler);
debugLog(debug, "[Server] Listening for incoming connections");
};
/** Stop listening for incoming connections */
const stopListening = () => {
window.removeEventListener("message", handler);
debugLog(debug, "[Server] Stopped listening for incoming connections");
};
return {
listen,
stopListening,
get clients() {
return [...clients];
}
};
};
const postMessage = (origin: string, message: IResponseMessage) => {
window.top?.postMessage(message, origin);
};
export { getServer };