@atproto/oauth-scopes
Version:
A library for manipulating and validating ATproto OAuth scopes in TypeScript.
121 lines • 4.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScopeStringSyntax = void 0;
const util_js_1 = require("./util.js");
/**
* Translates a scope string into a {@link ScopeSyntax}.
*/
class ScopeStringSyntax {
constructor(prefix, positional, params) {
Object.defineProperty(this, "prefix", {
enumerable: true,
configurable: true,
writable: true,
value: prefix
});
Object.defineProperty(this, "positional", {
enumerable: true,
configurable: true,
writable: true,
value: positional
});
Object.defineProperty(this, "params", {
enumerable: true,
configurable: true,
writable: true,
value: params
});
}
*keys() {
if (this.params)
yield* this.params.keys();
}
getSingle(key) {
if (!this.params?.has(key))
return undefined;
const value = this.params.getAll(key);
if (value.length > 1)
return null;
return value[0];
}
getMulti(key) {
if (!this.params?.has(key))
return undefined;
return this.params.getAll(key);
}
toString() {
let scope = this.prefix;
if (this.positional !== undefined) {
scope += `:${normalizeURIComponent(encodeURIComponent(this.positional))}`;
}
if (this.params?.size) {
scope += `?${normalizeURIComponent(this.params.toString())}`;
}
return scope;
}
static fromString(scopeValue) {
const paramIdx = scopeValue.indexOf('?');
const colonIdx = scopeValue.indexOf(':');
const prefixEnd = (0, util_js_1.minIdx)(paramIdx, colonIdx);
// No param or positional
if (prefixEnd === -1) {
return new ScopeStringSyntax(scopeValue);
}
const prefix = scopeValue.slice(0, prefixEnd);
// Parse the positional parameter if present
const positional = colonIdx !== -1
? paramIdx === -1
? decodeURIComponent(scopeValue.slice(colonIdx + 1))
: colonIdx < paramIdx
? decodeURIComponent(scopeValue.slice(colonIdx + 1, paramIdx))
: undefined
: undefined;
// Parse the query string if present and non empty
const params = paramIdx !== -1 && paramIdx < scopeValue.length - 1
? new URLSearchParams(scopeValue.slice(paramIdx + 1))
: undefined;
return new ScopeStringSyntax(prefix, positional, params);
}
}
exports.ScopeStringSyntax = ScopeStringSyntax;
/**
* Set of characters that are allowed in scope components without encoding. This
* is used to normalize scope components.
*/
const ALLOWED_SCOPE_CHARS = new Set(
// @NOTE This list must not contain "?" or "&" as it would interfere with
// query string parsing.
[':', '/', '+', ',', '@', '%']);
const NORMALIZABLE_CHARS_MAP = new Map(Array.from(ALLOWED_SCOPE_CHARS, (c) => [encodeURIComponent(c), c]).filter(([encoded, c]) =>
// Make sure that any char added to ALLOWED_SCOPE_CHARS that is a char
// that indeed needs encoding. Also, the normalizeURIComponent only
// supports three-character percent-encoded sequences.
encoded !== c && encoded.length === 3 && encoded.startsWith('%')));
/**
* Assumes a properly url-encoded string.
*/
function normalizeURIComponent(value) {
// No need to read the last two characters since percent encoded characters
// are always three characters long.
let end = value.length - 2;
for (let i = 0; i < end; i++) {
// Check if the character is a percent-encoded character
if (value.charCodeAt(i) === 0x25 /* % */) {
// Read the next encoded char. Current version only supports
// three-character percent-encoded sequences.
const encodedChar = value.slice(i, i + 3);
// Check if the encoded character is in the normalization map
const normalizedChar = NORMALIZABLE_CHARS_MAP.get(encodedChar);
if (normalizedChar) {
// Replace the encoded character with its normalized version
value = `${value.slice(0, i)}${normalizedChar}${value.slice(i + encodedChar.length)}`;
// Adjust index to account for the length change
i += normalizedChar.length - 1;
// Adjust end index since we replaced encoded char with normalized char
end -= encodedChar.length - normalizedChar.length;
}
}
}
return value;
}
//# sourceMappingURL=syntax-string.js.map