UNPKG

ssb-uri2

Version:

Utilities for recognizing and converting SSB URIs

361 lines (360 loc) 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decompose = exports.compose = exports.getMessageSSBURIRegex = exports.getFeedSSBURIRegex = exports.isSSBURI = exports.isExperimentalSSBURIWithAction = exports.isExperimentalSSBURI = exports.isIdentitySSBURI = exports.isIdentityFusionSSBURI = exports.isIdentityGroupSSBURI = exports.isIdentityPOBoxSSBURI = exports.isEncryptionKeyBox2DMDiffieHellmanSSBURI = exports.isAddressSSBURI = exports.isClassicBlobSSBURI = exports.isMessageSSBURI = exports.isCloakedV1MessageSSBURI = exports.isIndexedV1MessageSSBURI = exports.isButtwooV1MessageSSBURI = exports.isGabbyGroveV1MessageSSBURI = exports.isBendyButtV1MessageSSBURI = exports.isClassicMessageSSBURI = exports.isFeedSSBURI = exports.isIndexedV1FeedSSBURI = exports.isButtwooV1FeedSSBURI = exports.isGabbyGroveV1FeedSSBURI = exports.isBendyButtV1FeedSSBURI = exports.isClassicFeedSSBURI = exports.toMultiserverAddress = exports.toBlobSigil = exports.toMessageSigil = exports.toFeedSigil = exports.fromMultiserverAddress = exports.fromBlobSigil = exports.fromMessageSigil = exports.fromFeedSigil = void 0; const Base64 = { unsafeToSafe(input) { return input.replace(/\+/g, '-').replace(/\//g, '_'); }, safeToUnsafe(input) { return input.replace(/-/g, '+').replace(/_/g, '/'); }, }; function extractBase64Data(pathname) { var _a; if (!pathname) return null; const lastPortion = (_a = /(:|\/)([\w_\-=]+)$/.exec(pathname)) === null || _a === void 0 ? void 0 : _a[2]; if (!lastPortion) return null; return Base64.safeToUnsafe(lastPortion); } function fromFeedSigil(sigil) { if (typeof sigil !== 'string') return null; if (!sigil.startsWith('@')) return null; const data = Base64.unsafeToSafe(sigil.slice(1, -8)); return `ssb:feed/classic/${data}`; } exports.fromFeedSigil = fromFeedSigil; function fromMessageSigil(sigil) { if (typeof sigil !== 'string') return null; if (!sigil.startsWith('%')) return null; const data = Base64.unsafeToSafe(sigil.slice(1, -7)); return `ssb:message/classic/${data}`; } exports.fromMessageSigil = fromMessageSigil; function fromBlobSigil(sigil) { if (typeof sigil !== 'string') return null; if (!sigil.startsWith('&')) return null; const data = Base64.unsafeToSafe(sigil.slice(1, -7)); return `ssb:blob/classic/${data}`; } exports.fromBlobSigil = fromBlobSigil; function fromMultiserverAddress(msaddr) { const encoded = encodeURIComponent(msaddr); return `ssb:address/multiserver?multiserverAddress=${encoded}`; } exports.fromMultiserverAddress = fromMultiserverAddress; function toFeedSigil(uri) { if (!isClassicFeedSSBURI(uri)) return null; const base64Data = extractBase64Data(new URL(uri).pathname); if (!base64Data) return null; return `@${base64Data}.ed25519`; } exports.toFeedSigil = toFeedSigil; function toMessageSigil(uri) { if (!isClassicMessageSSBURI(uri)) return null; const base64Data = extractBase64Data(new URL(uri).pathname); if (!base64Data) return null; return `%${base64Data}.sha256`; } exports.toMessageSigil = toMessageSigil; function toBlobSigil(uri) { if (!isClassicBlobSSBURI(uri)) return null; const base64Data = extractBase64Data(new URL(uri).pathname); if (!base64Data) return null; return `&${base64Data}.sha256`; } exports.toBlobSigil = toBlobSigil; function toMultiserverAddress(uri) { return new URL(uri).searchParams.get('multiserverAddress'); } exports.toMultiserverAddress = toMultiserverAddress; function checkTypeFormat(uri, ...args) { if (!uri) return false; const [type, format] = args; return ((uri.startsWith(`ssb:${type}:${format}:`) || uri.startsWith(`ssb:${type}/${format}/`) || uri.startsWith(`ssb://${type}/${format}/`)) && !!extractBase64Data(new URL(uri).pathname)); } function isClassicFeedSSBURI(uri) { return (checkTypeFormat(uri, 'feed', 'classic') || checkTypeFormat(uri, 'feed', 'ed25519')); } exports.isClassicFeedSSBURI = isClassicFeedSSBURI; function isBendyButtV1FeedSSBURI(uri) { return checkTypeFormat(uri, 'feed', 'bendybutt-v1'); } exports.isBendyButtV1FeedSSBURI = isBendyButtV1FeedSSBURI; function isGabbyGroveV1FeedSSBURI(uri) { return checkTypeFormat(uri, 'feed', 'gabbygrove-v1'); } exports.isGabbyGroveV1FeedSSBURI = isGabbyGroveV1FeedSSBURI; function isButtwooV1FeedSSBURI(uri) { return checkTypeFormat(uri, 'feed', 'buttwoo-v1'); } exports.isButtwooV1FeedSSBURI = isButtwooV1FeedSSBURI; function isIndexedV1FeedSSBURI(uri) { return checkTypeFormat(uri, 'feed', 'indexed-v1'); } exports.isIndexedV1FeedSSBURI = isIndexedV1FeedSSBURI; function isFeedSSBURI(uri) { return (isClassicFeedSSBURI(uri) || isBendyButtV1FeedSSBURI(uri) || isGabbyGroveV1FeedSSBURI(uri) || isButtwooV1FeedSSBURI(uri) || isIndexedV1FeedSSBURI(uri)); } exports.isFeedSSBURI = isFeedSSBURI; function isClassicMessageSSBURI(uri) { return (checkTypeFormat(uri, 'message', 'classic') || checkTypeFormat(uri, 'message', 'sha256')); } exports.isClassicMessageSSBURI = isClassicMessageSSBURI; function isBendyButtV1MessageSSBURI(uri) { return checkTypeFormat(uri, 'message', 'bendybutt-v1'); } exports.isBendyButtV1MessageSSBURI = isBendyButtV1MessageSSBURI; function isGabbyGroveV1MessageSSBURI(uri) { return checkTypeFormat(uri, 'message', 'gabbygrove-v1'); } exports.isGabbyGroveV1MessageSSBURI = isGabbyGroveV1MessageSSBURI; function isButtwooV1MessageSSBURI(uri) { return checkTypeFormat(uri, 'message', 'buttwoo-v1'); } exports.isButtwooV1MessageSSBURI = isButtwooV1MessageSSBURI; function isIndexedV1MessageSSBURI(uri) { return checkTypeFormat(uri, 'message', 'indexed-v1'); } exports.isIndexedV1MessageSSBURI = isIndexedV1MessageSSBURI; function isCloakedV1MessageSSBURI(uri) { return checkTypeFormat(uri, 'message', 'cloaked'); } exports.isCloakedV1MessageSSBURI = isCloakedV1MessageSSBURI; function isMessageSSBURI(uri) { return (isClassicMessageSSBURI(uri) || isBendyButtV1MessageSSBURI(uri) || isGabbyGroveV1MessageSSBURI(uri) || isButtwooV1MessageSSBURI(uri) || isIndexedV1MessageSSBURI(uri) || isCloakedV1MessageSSBURI(uri)); } exports.isMessageSSBURI = isMessageSSBURI; function isClassicBlobSSBURI(uri) { return (checkTypeFormat(uri, 'blob', 'classic') || checkTypeFormat(uri, 'blob', 'sha256')); } exports.isClassicBlobSSBURI = isClassicBlobSSBURI; function isAddressSSBURI(uri) { if (!uri) return false; return ((uri.startsWith('ssb:address:multiserver') || uri.startsWith('ssb:address/multiserver') || uri.startsWith('ssb://address/multiserver')) && !!new URL(uri).searchParams.get('multiserverAddress')); } exports.isAddressSSBURI = isAddressSSBURI; function isEncryptionKeyBox2DMDiffieHellmanSSBURI(uri) { return checkTypeFormat(uri, 'encryption-key', 'box2-dm-dh'); } exports.isEncryptionKeyBox2DMDiffieHellmanSSBURI = isEncryptionKeyBox2DMDiffieHellmanSSBURI; function isIdentityPOBoxSSBURI(uri) { return checkTypeFormat(uri, 'identity', 'po-box'); } exports.isIdentityPOBoxSSBURI = isIdentityPOBoxSSBURI; function isIdentityGroupSSBURI(uri) { return checkTypeFormat(uri, 'identity', 'group'); } exports.isIdentityGroupSSBURI = isIdentityGroupSSBURI; function isIdentityFusionSSBURI(uri) { return checkTypeFormat(uri, 'identity', 'fusion'); } exports.isIdentityFusionSSBURI = isIdentityFusionSSBURI; function isIdentitySSBURI(uri) { return (isIdentityPOBoxSSBURI(uri) || isIdentityGroupSSBURI(uri) || isIdentityFusionSSBURI(uri)); } exports.isIdentitySSBURI = isIdentitySSBURI; function isExperimentalSSBURI(uri) { if (!uri) return false; return (uri.startsWith('ssb:experimental') || uri.startsWith('ssb://experimental')); } exports.isExperimentalSSBURI = isExperimentalSSBURI; function isExperimentalSSBURIWithAction(action) { return (uri) => { if (!uri) return false; return (isExperimentalSSBURI(uri) && new URL(uri).searchParams.get('action') === action); }; } exports.isExperimentalSSBURIWithAction = isExperimentalSSBURIWithAction; function isSSBURI(uri) { return (isFeedSSBURI(uri) || isMessageSSBURI(uri) || isClassicBlobSSBURI(uri) || isAddressSSBURI(uri) || isEncryptionKeyBox2DMDiffieHellmanSSBURI(uri) || isIdentitySSBURI(uri) || isExperimentalSSBURI(uri)); } exports.isSSBURI = isSSBURI; function getFeedSSBURIRegex() { const type = 'feed'; const formatsWith3Parts = [ 'classic', 'ed25519', 'bendybutt-v1', 'gabbygrove-v1', 'buttwoo-v1', 'indexed-v1', ]; const formatsWith4Parts = ['buttwoo-v1']; const ruleWith3 = `ssb:(\/\/)?` + `${type}(\/|:)` + `(${formatsWith3Parts.join('|')})(\/|:)` + `[a-zA-Z0-9_\-]{43}=`; const ruleWith4 = `ssb:(\/\/)?` + `${type}(\/|:)` + `(${formatsWith4Parts.join('|')})(\/|:)` + `[a-zA-Z0-9_\-]{43}=(\/|:)` + `[a-zA-Z0-9_\-]{43}=`; return new RegExp(`(${ruleWith4}|${ruleWith3})`); } exports.getFeedSSBURIRegex = getFeedSSBURIRegex; function getMessageSSBURIRegex() { const type = 'message'; const format = [ 'classic', 'sha256', 'bendybutt-v1', 'gabbygrove-v1', 'buttwoo-v1', 'indexed-v1', 'cloaked', ]; return new RegExp(`ssb:(\/\/)?` + `${type}(\/|:)` + `(${format.join('|')})(\/|:)` + `[a-zA-Z0-9_\-]{43}=`); } exports.getMessageSSBURIRegex = getMessageSSBURIRegex; function validateParts({ type, format, data }) { if (!type) throw new Error('Missing required "type" property'); if (!format) throw new Error('Missing required "format" property'); if (!data) throw new Error('Missing required "data" property'); if (type === 'feed') { if (format !== 'classic' && format !== 'ed25519' && format !== 'bendybutt-v1' && format !== 'gabbygrove-v1' && format !== 'buttwoo-v1' && format !== 'indexed-v1') { throw new Error('Unknown format for type "feed": ' + format); } else return; } if (type === 'message') { if (format !== 'classic' && format !== 'sha256' && format !== 'bendybutt-v1' && format !== 'gabbygrove-v1' && format !== 'buttwoo-v1' && format !== 'indexed-v1' && format !== 'cloaked') { throw new Error('Unknown format for type "message": ' + format); } else return; } if (type === 'blob') { if (format !== 'classic' && format !== 'sha256') { throw new Error('Unknown format for type "blob": ' + format); } else return; } if (type === 'address') { if (format !== 'multiserver') { throw new Error('Unknown format for type "address": ' + format); } else return; } if (type === 'encryption-key') { if (format !== 'box2-dm-dh') { throw new Error('Unknown format for type "encryption-key": ' + format); } else return; } if (type === 'identity') { if (format !== 'po-box' && format !== 'group' && format !== 'fusion') { throw new Error('Unknown format for type "identity": ' + format); } else return; } } function fixParts(parts) { const { type, format } = parts; if (type === 'feed' && format === 'ed25519') { parts.format = 'classic'; return; } if (type === 'message' && format === 'sha256') { parts.format = 'classic'; return; } if (type === 'blob' && format === 'sha256') { parts.format = 'classic'; return; } } function compose(parts) { validateParts(parts); const { type, format, data, extraData } = parts; const safeData = Base64.unsafeToSafe(data); if (extraData) { const safeExtraData = Base64.unsafeToSafe(extraData); return `ssb:${type}/${format}/${safeData}/${safeExtraData}`; } else { return `ssb:${type}/${format}/${safeData}`; } } exports.compose = compose; function decompose(uri) { if (uri.length < 5) { throw new Error('Invalid SSB URI: ' + uri); } const pathname = uri.substring(4); const [type, format, safeData, safeExtraData] = pathname.split('/'); const data = Base64.safeToUnsafe(safeData); const parts = { type, format, data }; validateParts(parts); fixParts(parts); if (safeExtraData) parts.extraData = Base64.safeToUnsafe(safeExtraData); return parts; } exports.decompose = decompose;