@metamask/snaps-utils
Version:
A collection of utilities for MetaMask Snaps
82 lines • 3.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMetaMaskUrl = exports.SNAP_PATHS = exports.CLIENT_PATHS = void 0;
const utils_1 = require("@metamask/utils");
const snaps_1 = require("./snaps.cjs");
exports.CLIENT_PATHS = ['/'];
exports.SNAP_PATHS = ['/home'];
/**
* Parse a url string to an object containing the authority, path and Snap id (if snap authority).
* This also validates the url before parsing it.
*
* Note: The Snap id returned from this function should always be validated to be an installed snap.
*
* @param str - The url string to validate and parse.
* @returns A parsed url object.
* @throws If the authority or path is invalid.
*/
function parseMetaMaskUrl(str) {
const url = new URL(str);
const { protocol } = url;
if (protocol !== 'metamask:') {
throw new Error(`Unable to parse URL. Expected the protocol to be "metamask:", but received "${protocol}".`);
}
// The browser version of URL differs from the Node version so we rely on the href
// property to grab the relevant parts of the url instead of hostname and pathname
const [authority, ...pathElements] = url.href
.replace('metamask://', '')
.split('/');
const path = `/${pathElements.join('/')}`;
switch (authority) {
case 'client':
(0, utils_1.assert)(exports.CLIENT_PATHS.includes(path), `Unable to navigate to "${path}". The provided path is not allowed.`);
return {
authority,
path,
};
case 'snap':
return parseSnapPath(path);
default:
throw new Error(`Expected "metamask:" URL to start with "client" or "snap", but received "${authority}".`);
}
}
exports.parseMetaMaskUrl = parseMetaMaskUrl;
/**
* Parse a snap path and throws if it is invalid, returns an object with link data otherwise.
*
* @param path - The snap path to be parsed.
* @returns A parsed url object.
* @throws If the path or Snap id is invalid.
*/
function parseSnapPath(path) {
const baseErrorMessage = 'Invalid MetaMask url:';
const strippedPath = (0, snaps_1.stripSnapPrefix)(path.slice(1));
const location = path.slice(1).startsWith('npm:') ? 'npm:' : 'local:';
const isNameSpaced = strippedPath.startsWith('@');
const pathTokens = strippedPath.split('/');
const lastPathToken = `/${pathTokens[pathTokens.length - 1]}`;
let partialSnapId;
if (location === 'local:') {
const [localProtocol, , ...rest] = pathTokens.slice(0, -1);
partialSnapId = `${localProtocol}//${rest.join('/')}`;
// we can't make assumptions of the structure of the local snap url since it can have a nested path
// so we only check that the last path token is one of the allowed paths
(0, utils_1.assert)(exports.SNAP_PATHS.includes(lastPathToken), `${baseErrorMessage} invalid snap path.`);
}
else {
partialSnapId = isNameSpaced
? `${pathTokens[0]}/${pathTokens[1]}`
: pathTokens[0];
(0, utils_1.assert)(isNameSpaced
? pathTokens.length === 3 && exports.SNAP_PATHS.includes(lastPathToken)
: pathTokens.length === 2 && exports.SNAP_PATHS.includes(lastPathToken), `${baseErrorMessage} invalid snap path.`);
}
const snapId = `${location}${partialSnapId}`;
(0, snaps_1.assertIsValidSnapId)(snapId);
return {
authority: 'snap',
snapId,
path: lastPathToken,
};
}
//# sourceMappingURL=url.cjs.map