| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- function keyOf(name) {
- return name.trim().toLowerCase();
- }
- function toError(error) {
- return error instanceof Error ? error : new Error(String(error));
- }
- function removeConverter(converters, primaryNames, converter) {
- for (const alias of [converter.name, ...(converter.aliases ?? [])]) {
- converters.delete(keyOf(alias));
- }
- primaryNames.delete(keyOf(converter.name));
- }
- function requireCapability(converter, name, capability) {
- const method = converter[capability];
- if (typeof method !== "function") {
- throw new Error(`Converter '${name}' does not support ${capability}()`);
- }
- return method;
- }
- function detectConfidence(name, text, converter) {
- const normalizedName = keyOf(converter.name || name);
- const trimmed = text.trim();
- if (!trimmed) {
- return 0;
- }
- let accepted = false;
- if (converter.is) {
- accepted = converter.is(text);
- }
- let decodable = false;
- try {
- converter.decode(text);
- decodable = true;
- }
- catch {
- decodable = false;
- }
- if (!accepted && !decodable) {
- return 0;
- }
- switch (normalizedName) {
- case "pem":
- return /-----BEGIN [^-]+-----/.test(text) ? 1 : 0;
- case "hex": {
- const compact = trimmed.replace(/^0x/i, "").replace(/[\s:.-]/g, "");
- if (!compact || /[^0-9a-f]/i.test(compact) || compact.length % 2 !== 0) {
- return 0;
- }
- if (/^0x/i.test(trimmed) || /[:\s.-]/.test(trimmed)) {
- return 0.95;
- }
- if (/[a-f]/.test(trimmed) || /[A-F]/.test(trimmed)) {
- return 0.8;
- }
- return 0.45;
- }
- case "base64url":
- if (/[-_]/.test(trimmed)) {
- return 0.95;
- }
- if (/=/.test(trimmed)) {
- return 0.1;
- }
- return 0.6;
- case "base64":
- if (/[+/=]/.test(trimmed)) {
- return 0.9;
- }
- return 0.55;
- case "binary":
- case "utf8":
- case "utf16be":
- case "utf16le":
- return 0;
- default:
- return accepted && decodable ? 0.75 : 0.5;
- }
- }
- export function createConverterRegistry(initialConverters = []) {
- const converters = new Map();
- const primaryNames = new Set();
- const api = {
- register(converter, options = {}) {
- if (!converter.name || !keyOf(converter.name)) {
- throw new TypeError("Converter name is required");
- }
- const names = [...new Set([converter.name, ...(converter.aliases ?? [])].map(keyOf))];
- const conflicts = new Set();
- for (const name of names) {
- const existing = converters.get(name);
- if (!existing) {
- continue;
- }
- if (!options.override) {
- throw new Error(`Converter '${name}' is already registered`);
- }
- conflicts.add(existing);
- }
- for (const conflicting of conflicts) {
- removeConverter(converters, primaryNames, conflicting);
- }
- for (const name of names) {
- converters.set(name, converter);
- }
- primaryNames.add(keyOf(converter.name));
- return this;
- },
- unregister(name) {
- const converter = converters.get(keyOf(name));
- if (!converter) {
- return false;
- }
- removeConverter(converters, primaryNames, converter);
- return true;
- },
- has(name) {
- return converters.has(keyOf(name));
- },
- get(name) {
- const converter = converters.get(keyOf(name));
- if (!converter) {
- throw new Error(`Converter '${name}' is not registered`);
- }
- return converter;
- },
- list() {
- return [...primaryNames].map((name) => this.get(name));
- },
- encode(name, data, options) {
- return this.get(name).encode(data, options);
- },
- decode(name, text, options) {
- return this.get(name).decode(text, options);
- },
- tryDecode(name, text, options) {
- try {
- return { ok: true, bytes: this.decode(name, text, options) };
- }
- catch (error) {
- return { ok: false, error: toError(error) };
- }
- },
- normalize(name, text, options) {
- const converter = this.get(name);
- return requireCapability(converter, name, "normalize").call(converter, text, options);
- },
- parse(name, text, options) {
- const converter = this.get(name);
- return requireCapability(converter, name, "parse").call(converter, text, options);
- },
- format(name, data, format) {
- const converter = this.get(name);
- return requireCapability(converter, name, "format").call(converter, data, format);
- },
- transcode(text, options) {
- const bytes = this.decode(options.from, text, options.fromOptions);
- return this.encode(options.to, bytes, options.toOptions);
- },
- detect(text, options = {}) {
- const formatNames = options.formats?.length
- ? options.formats.map((name) => String(name))
- : this.list()
- .map((converter) => converter.name)
- .filter((name) => !["binary", "utf8", "utf16be", "utf16le"].includes(keyOf(name)));
- const detections = new Map();
- for (const requestedName of formatNames) {
- const converter = this.get(requestedName);
- const confidence = detectConfidence(requestedName, text, converter);
- if (confidence <= 0) {
- continue;
- }
- const format = converter.name;
- const current = detections.get(format);
- if (!current || confidence > current.confidence) {
- detections.set(format, { format, confidence });
- }
- }
- return [...detections.values()].sort((left, right) => right.confidence - left.confidence);
- },
- };
- for (const converter of initialConverters) {
- api.register(converter);
- }
- return api;
- }
|