mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 21:02:28 +00:00
update
This commit is contained in:
1 parent
589925f1fe
commit
ef29bb6eb3
97 files changed
+46855
No files matched your search
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
import { devLogger } from "./logger.js";
|
||||
|
||||
/**
|
||||
* Maps over an iterable with a concurrency limit.
|
||||
*
|
||||
* @param {Iterable} iterable - The items to map over.
|
||||
* @param {Function} mapper - The async mapping function.
|
||||
* @param {Object} options - Options object.
|
||||
* @param {number} options.concurrency - The concurrency limit (default: Infinity).
|
||||
* @returns {Promise<Array>} - The results of the mapping function in original order.
|
||||
*/
|
||||
export async function pMap(iterable, mapper, { concurrency = Infinity } = {}) {
|
||||
const results = [];
|
||||
const iterator = iterable[Symbol.iterator]();
|
||||
let index = 0;
|
||||
let active = 0;
|
||||
let done = false;
|
||||
let rejected = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
function next() {
|
||||
if (rejected) return;
|
||||
if (done && active === 0) {
|
||||
resolve(results);
|
||||
return;
|
||||
}
|
||||
|
||||
while (active < concurrency && !done) {
|
||||
const nextItem = iterator.next();
|
||||
if (nextItem.done) {
|
||||
done = true;
|
||||
if (active === 0) resolve(results);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = index++;
|
||||
active++;
|
||||
|
||||
Promise.resolve(nextItem.value)
|
||||
.then((value) => mapper(value, currentIndex))
|
||||
.then((result) => {
|
||||
results[currentIndex] = result;
|
||||
active--;
|
||||
next();
|
||||
})
|
||||
.catch((err) => {
|
||||
rejected = true;
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to pMap, but returns { status: 'fulfilled', value } or { status: 'rejected', reason }
|
||||
* for each item, ensuring all items are processed (like Promise.allSettled).
|
||||
*
|
||||
* @param {Iterable} iterable - The items to map over.
|
||||
* @param {Function} mapper - The async mapping function.
|
||||
* @param {Object} options - Options object.
|
||||
* @param {number} options.concurrency - The concurrency limit (default: Infinity).
|
||||
* @returns {Promise<Array>} - The settled results.
|
||||
*/
|
||||
export async function pMapSettled(iterable, mapper, options) {
|
||||
return pMap(
|
||||
iterable,
|
||||
async (item, index) => {
|
||||
try {
|
||||
const value = await mapper(item, index);
|
||||
return { status: "fulfilled", value };
|
||||
} catch (reason) {
|
||||
return { status: "rejected", reason };
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
export function withRequestTimeout(promise, timeoutMs, onTimeout, message = "Request timed out") {
|
||||
const resolvedTimeout = Number(timeoutMs);
|
||||
const effectiveTimeout =
|
||||
Number.isFinite(resolvedTimeout) && resolvedTimeout > 0
|
||||
? Math.floor(resolvedTimeout)
|
||||
: 4000;
|
||||
|
||||
let timeoutId = null;
|
||||
let settled = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
if (typeof onTimeout === "function") {
|
||||
try {
|
||||
onTimeout();
|
||||
} catch (cleanupError) {
|
||||
devLogger.warn("[asyncUtils] Timeout cleanup failed:", cleanupError);
|
||||
}
|
||||
}
|
||||
reject(new Error(message));
|
||||
}, effectiveTimeout);
|
||||
|
||||
Promise.resolve(promise)
|
||||
.then((value) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
resolve(value);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
import { bytesToHex, sha256 } from "../../vendor/crypto-helpers.bundle.min.js";
|
||||
import { devLogger } from "./logger.js";
|
||||
|
||||
let sharedTextEncoder = null;
|
||||
|
||||
export function getSharedTextEncoder() {
|
||||
if (!sharedTextEncoder && typeof TextEncoder !== "undefined") {
|
||||
sharedTextEncoder = new TextEncoder();
|
||||
}
|
||||
return sharedTextEncoder;
|
||||
}
|
||||
|
||||
const BlobConstructor = typeof Blob !== "undefined" ? Blob : null;
|
||||
|
||||
export async function valueToUint8Array(value) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
BlobConstructor &&
|
||||
value instanceof BlobConstructor &&
|
||||
typeof value.arrayBuffer === "function"
|
||||
) {
|
||||
const buffer = await value.arrayBuffer();
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostr] Failed to read Blob while computing hash:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof ArrayBuffer !== "undefined") {
|
||||
if (value instanceof ArrayBuffer) {
|
||||
return new Uint8Array(value);
|
||||
}
|
||||
if (ArrayBuffer.isView?.(value)) {
|
||||
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof Buffer !== "undefined" && Buffer.isBuffer?.(value)) {
|
||||
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const encoder = getSharedTextEncoder();
|
||||
return encoder ? encoder.encode(value) : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function computeSha256HexFromValue(value) {
|
||||
const data = await valueToUint8Array(value);
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
const digest = sha256(data);
|
||||
const hex = typeof digest === "string" ? digest : bytesToHex(digest);
|
||||
return hex ? hex.toLowerCase() : "";
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostr] Failed to compute SHA-256 for mirror payload:", error);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
export const EXTENSION_MIME_MAP = Object.freeze(
|
||||
Object.fromEntries(
|
||||
Object.entries({
|
||||
mp4: "video/mp4",
|
||||
m4v: "video/x-m4v",
|
||||
webm: "video/webm",
|
||||
mkv: "video/x-matroska",
|
||||
mov: "video/quicktime",
|
||||
avi: "video/x-msvideo",
|
||||
ogv: "video/ogg",
|
||||
ogg: "video/ogg",
|
||||
m3u8: "application/x-mpegurl",
|
||||
mpd: "application/dash+xml",
|
||||
ts: "video/mp2t",
|
||||
mpg: "video/mpeg",
|
||||
mpeg: "video/mpeg",
|
||||
flv: "video/x-flv",
|
||||
"3gp": "video/3gpp",
|
||||
}).map(([extension, mimeType]) => [
|
||||
extension,
|
||||
typeof mimeType === "string" ? mimeType.toLowerCase() : "",
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
export function inferMimeTypeFromUrl(url) {
|
||||
if (!url || typeof url !== "string") {
|
||||
return "";
|
||||
}
|
||||
|
||||
let pathname = "";
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
pathname = parsed.pathname || "";
|
||||
} catch (err) {
|
||||
const sanitized = url.split("?")[0].split("#")[0];
|
||||
pathname = sanitized || "";
|
||||
}
|
||||
|
||||
const lastSegment = pathname.split("/").pop() || "";
|
||||
const match = lastSegment.match(/\.([a-z0-9]+)$/i);
|
||||
if (!match) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const extension = match[1].toLowerCase();
|
||||
const mimeType = EXTENSION_MIME_MAP[extension];
|
||||
return typeof mimeType === "string" ? mimeType : "";
|
||||
}
|
||||
Vendored
+102
@@ -0,0 +1,102 @@
|
||||
import { HEX64_REGEX } from "./hex.js";
|
||||
|
||||
/**
|
||||
* Safely encode a hex pubkey to npub.
|
||||
*
|
||||
* @param {string} pubkey - The hex public key.
|
||||
* @returns {string|null} The npub string or null if encoding failed.
|
||||
*/
|
||||
export function safeEncodeNpub(pubkey) {
|
||||
if (typeof pubkey !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmed = pubkey.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("npub1")) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.NostrTools &&
|
||||
window.NostrTools.nip19 &&
|
||||
typeof window.NostrTools.nip19.npubEncode === "function"
|
||||
) {
|
||||
return window.NostrTools.nip19.npubEncode(trimmed);
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely decode an npub to hex pubkey.
|
||||
*
|
||||
* @param {string} npub - The npub string.
|
||||
* @returns {string|null} The hex public key or null if decoding failed.
|
||||
*/
|
||||
export function safeDecodeNpub(npub) {
|
||||
if (typeof npub !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmed = npub.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.NostrTools &&
|
||||
window.NostrTools.nip19 &&
|
||||
typeof window.NostrTools.nip19.decode === "function"
|
||||
) {
|
||||
const decoded = window.NostrTools.nip19.decode(trimmed);
|
||||
if (decoded.type === "npub" && typeof decoded.data === "string") {
|
||||
return decoded.data;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a public key to hex format, handling npub inputs.
|
||||
*
|
||||
* @param {string} pubkey - The public key (hex or npub).
|
||||
* @returns {string|null} The normalized hex public key or null.
|
||||
*/
|
||||
export function normalizeHexPubkey(pubkey) {
|
||||
if (typeof pubkey !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trimmed = pubkey.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (HEX64_REGEX.test(trimmed)) {
|
||||
return trimmed.toLowerCase();
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("npub1")) {
|
||||
const decoded = safeDecodeNpub(trimmed);
|
||||
if (decoded && HEX64_REGEX.test(decoded)) {
|
||||
return decoded.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
import { devLogger } from "./logger.js";
|
||||
import { getCachedNostrTools } from "../nostr/toolkit.js";
|
||||
|
||||
export function pointerKey(pointer) {
|
||||
if (!pointer) {
|
||||
return "";
|
||||
}
|
||||
const type = pointer.type === "a" ? "a" : "e";
|
||||
const value = typeof pointer.value === "string" ? pointer.value.trim().toLowerCase() : "";
|
||||
if (!type || !value) {
|
||||
return "";
|
||||
}
|
||||
return `${type}:${value}`;
|
||||
}
|
||||
|
||||
export function normalizePointerTag(tag) {
|
||||
if (!Array.isArray(tag) || tag.length < 2) {
|
||||
return null;
|
||||
}
|
||||
const type = tag[0] === "a" ? "a" : tag[0] === "e" ? "e" : "";
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
const value = typeof tag[1] === "string" ? tag[1].trim() : "";
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const relay =
|
||||
tag.length > 2 && typeof tag[2] === "string" && tag[2].trim()
|
||||
? tag[2].trim()
|
||||
: null;
|
||||
return { type, value, relay };
|
||||
}
|
||||
|
||||
function clonePointerItem(pointer) {
|
||||
if (!pointer || typeof pointer !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cloned = {
|
||||
type: pointer.type === "a" ? "a" : "e",
|
||||
value: typeof pointer.value === "string" ? pointer.value.trim() : "",
|
||||
};
|
||||
|
||||
if (!cloned.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof pointer.relay === "string" && pointer.relay.trim()) {
|
||||
cloned.relay = pointer.relay.trim();
|
||||
}
|
||||
|
||||
if (Number.isFinite(pointer.watchedAt)) {
|
||||
cloned.watchedAt = Math.max(0, Math.floor(pointer.watchedAt));
|
||||
}
|
||||
|
||||
if (Number.isFinite(pointer.resumeAt)) {
|
||||
cloned.resumeAt = Math.max(0, Math.floor(pointer.resumeAt));
|
||||
}
|
||||
|
||||
if (pointer.completed === true) {
|
||||
cloned.completed = true;
|
||||
}
|
||||
|
||||
if (pointer.session === true) {
|
||||
cloned.session = true;
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
export { clonePointerItem };
|
||||
|
||||
export function mergePointerDetails(target, source) {
|
||||
if (!target || typeof target !== "object" || !source || typeof source !== "object") {
|
||||
return target;
|
||||
}
|
||||
if (source.session === true) {
|
||||
target.session = true;
|
||||
}
|
||||
if (Number.isFinite(source.resumeAt)) {
|
||||
target.resumeAt = Math.max(0, Math.floor(source.resumeAt));
|
||||
}
|
||||
if (source.completed === true) {
|
||||
target.completed = true;
|
||||
}
|
||||
if (Number.isFinite(source.watchedAt)) {
|
||||
target.watchedAt = Math.max(0, Math.floor(source.watchedAt));
|
||||
}
|
||||
if (typeof source.relay === "string" && source.relay.trim()) {
|
||||
if (!target.relay || !target.relay.trim()) {
|
||||
target.relay = source.relay.trim();
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
export function normalizePointerInput(pointer) {
|
||||
if (!pointer) {
|
||||
return null;
|
||||
}
|
||||
if (Array.isArray(pointer)) {
|
||||
return normalizePointerTag(pointer);
|
||||
}
|
||||
if (typeof pointer === "object") {
|
||||
if (typeof pointer.type === "string" && typeof pointer.value === "string") {
|
||||
return clonePointerItem(pointer);
|
||||
}
|
||||
if (Array.isArray(pointer.tag)) {
|
||||
return normalizePointerTag(pointer.tag);
|
||||
}
|
||||
}
|
||||
if (typeof pointer !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = pointer.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
if (trimmed.startsWith("naddr") || trimmed.startsWith("nevent")) {
|
||||
try {
|
||||
const decoder = getCachedNostrTools()?.nip19?.decode;
|
||||
if (typeof decoder === "function") {
|
||||
const decoded = decoder(trimmed);
|
||||
if (decoded?.type === "naddr" && decoded.data) {
|
||||
const { kind, pubkey, identifier, relays } = decoded.data;
|
||||
if (
|
||||
typeof kind === "number" &&
|
||||
typeof pubkey === "string" &&
|
||||
typeof identifier === "string"
|
||||
) {
|
||||
const relay =
|
||||
Array.isArray(relays) && relays.length && typeof relays[0] === "string"
|
||||
? relays[0]
|
||||
: null;
|
||||
return {
|
||||
type: "a",
|
||||
value: `${kind}:${pubkey}:${identifier}`,
|
||||
relay,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (decoded?.type === "nevent" && decoded.data) {
|
||||
const { id, relays } = decoded.data;
|
||||
if (typeof id === "string" && id.trim()) {
|
||||
const relay =
|
||||
Array.isArray(relays) && relays.length && typeof relays[0] === "string"
|
||||
? relays[0]
|
||||
: null;
|
||||
return {
|
||||
type: "e",
|
||||
value: id.trim(),
|
||||
relay,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn(`[nostr] Failed to decode pointer ${trimmed}:`, error);
|
||||
}
|
||||
}
|
||||
const type = trimmed.includes(":") ? "a" : "e";
|
||||
return { type, value: trimmed, relay: null };
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
export class SimpleEventEmitter {
|
||||
constructor(logger = null) {
|
||||
this.logger = typeof logger === "function" ? logger : null;
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
on(eventName, handler) {
|
||||
if (typeof handler !== "function") {
|
||||
return () => {};
|
||||
}
|
||||
if (!this.listeners.has(eventName)) {
|
||||
this.listeners.set(eventName, new Set());
|
||||
}
|
||||
const handlers = this.listeners.get(eventName);
|
||||
handlers.add(handler);
|
||||
return () => {
|
||||
handlers.delete(handler);
|
||||
if (!handlers.size) {
|
||||
this.listeners.delete(eventName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
emit(eventName, detail) {
|
||||
const handlers = this.listeners.get(eventName);
|
||||
if (!handlers || !handlers.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const handler of Array.from(handlers)) {
|
||||
try {
|
||||
handler(detail);
|
||||
} catch (error) {
|
||||
if (this.logger) {
|
||||
this.logger(`Listener for "${eventName}" threw`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user