mega-stream-proxy 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/android/build.gradle +31 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/dist/Player/native.d.ts +3 -0
- package/dist/Player/native.js +3 -0
- package/dist/Player/playerAndroid/MegaVideoPlayerAndroid.d.ts +47 -0
- package/dist/Player/playerAndroid/MegaVideoPlayerAndroid.js +624 -0
- package/dist/Player/playerAndroid/PlayerControlsAndroid.d.ts +54 -0
- package/dist/Player/playerAndroid/PlayerControlsAndroid.js +370 -0
- package/dist/Player/playerAndroid/SubtitleDisplayAndroid.d.ts +21 -0
- package/dist/Player/playerAndroid/SubtitleDisplayAndroid.js +55 -0
- package/dist/Player/playerAndroid/index.d.ts +3 -0
- package/dist/Player/playerAndroid/index.js +3 -0
- package/dist/Player/playerweb/MegaVideoPlayer.d.ts +92 -0
- package/dist/Player/playerweb/MegaVideoPlayer.js +368 -0
- package/dist/Player/playerweb/PlayerControls.d.ts +52 -0
- package/dist/Player/playerweb/PlayerControls.js +346 -0
- package/dist/Player/playerweb/SubtitleDisplay.d.ts +15 -0
- package/dist/Player/playerweb/SubtitleDisplay.js +32 -0
- package/dist/Player/playerweb/index.d.ts +3 -0
- package/dist/Player/playerweb/index.js +3 -0
- package/dist/Player/web.d.ts +3 -0
- package/dist/Player/web.js +3 -0
- package/dist/accounts/async-storage.d.ts +10 -0
- package/dist/accounts/async-storage.js +46 -0
- package/dist/accounts/index.d.ts +4 -0
- package/dist/accounts/index.js +3 -0
- package/dist/accounts/local-storage.d.ts +3 -0
- package/dist/accounts/local-storage.js +63 -0
- package/dist/accounts/manager.d.ts +39 -0
- package/dist/accounts/manager.js +149 -0
- package/dist/accounts/types.d.ts +35 -0
- package/dist/accounts/types.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/megaFile.d.ts +7 -0
- package/dist/megaFile.js +31 -0
- package/dist/proxy/native.d.ts +25 -0
- package/dist/proxy/native.js +336 -0
- package/dist/proxy/sw/bundle.d.ts +21 -0
- package/dist/proxy/sw/bundle.js +32 -0
- package/dist/proxy/sw/megajs.browser.js +27 -0
- package/dist/proxy/sw/sw.js +212 -0
- package/dist/proxy/types.d.ts +15 -0
- package/dist/proxy/types.js +2 -0
- package/dist/proxy/web.d.ts +24 -0
- package/dist/proxy/web.js +231 -0
- package/dist/ranges.d.ts +30 -0
- package/dist/ranges.js +63 -0
- package/dist/stream.d.ts +9 -0
- package/dist/stream.js +100 -0
- package/dist/subtitles/index.d.ts +9 -0
- package/dist/subtitles/index.js +2 -0
- package/dist/subtitles/load.d.ts +18 -0
- package/dist/subtitles/load.js +58 -0
- package/dist/subtitles/parse.d.ts +19 -0
- package/dist/subtitles/parse.js +95 -0
- package/dist/swDefine.d.ts +5 -0
- package/dist/swDefine.js +5 -0
- package/package.json +112 -0
- package/src/Player/playerAndroid/MegaVideoPlayerAndroid.tsx +825 -0
- package/src/Player/playerAndroid/PlayerControlsAndroid.tsx +824 -0
- package/src/Player/playerAndroid/SubtitleDisplayAndroid.tsx +80 -0
- package/src/Player/playerAndroid/index.ts +7 -0
- package/src/Player/playerweb/MegaVideoPlayer.tsx +551 -0
- package/src/Player/playerweb/PlayerControls.tsx +798 -0
- package/src/Player/playerweb/SubtitleDisplay.tsx +48 -0
- package/src/Player/playerweb/index.ts +3 -0
- package/src/accounts/async-storage.ts +55 -0
- package/src/accounts/index.ts +9 -0
- package/src/accounts/local-storage.ts +66 -0
- package/src/accounts/manager.ts +174 -0
- package/src/accounts/types.ts +38 -0
- package/src/index.ts +10 -0
- package/src/megaFile.ts +48 -0
- package/src/proxy/native.ts +396 -0
- package/src/proxy/sw/bundle.ts +37 -0
- package/src/proxy/sw/megajs.browser.js +27 -0
- package/src/proxy/sw/sw.js +212 -0
- package/src/proxy/types.ts +17 -0
- package/src/proxy/web.ts +266 -0
- package/src/ranges.ts +72 -0
- package/src/stream.ts +117 -0
- package/src/subtitles/index.ts +15 -0
- package/src/subtitles/load.ts +83 -0
- package/src/subtitles/parse.ts +105 -0
- package/src/swDefine.ts +5 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
2
|
+
|
|
3
|
+
export type SubCueShapeWeb = { start: number; end: number; text: string };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renders the active subtitle cue over the video surface. Positioned
|
|
7
|
+
* absolutely with zIndex 1 (below the controls at zIndex 2, above the
|
|
8
|
+
* video background at zIndex 0).
|
|
9
|
+
*/
|
|
10
|
+
export function SubtitleDisplayWeb({
|
|
11
|
+
cue,
|
|
12
|
+
enabled,
|
|
13
|
+
fontSize,
|
|
14
|
+
}: {
|
|
15
|
+
cue: SubCueShapeWeb | null;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
fontSize: number;
|
|
18
|
+
}) {
|
|
19
|
+
if (!enabled || !cue?.text) return null;
|
|
20
|
+
return (
|
|
21
|
+
<View pointerEvents="none" style={styles.wrap}>
|
|
22
|
+
<Text style={[styles.text, { fontSize, lineHeight: fontSize * 1.35 }]}>
|
|
23
|
+
{cue.text}
|
|
24
|
+
</Text>
|
|
25
|
+
</View>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const styles = StyleSheet.create({
|
|
30
|
+
wrap: {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
left: 0,
|
|
33
|
+
right: 0,
|
|
34
|
+
bottom: 56,
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
paddingHorizontal: 24,
|
|
37
|
+
zIndex: 1,
|
|
38
|
+
},
|
|
39
|
+
text: {
|
|
40
|
+
backgroundColor: "rgba(0,0,0,0.65)",
|
|
41
|
+
color: "#fff",
|
|
42
|
+
textAlign: "center",
|
|
43
|
+
paddingHorizontal: 8,
|
|
44
|
+
paddingVertical: 3,
|
|
45
|
+
borderRadius: 4,
|
|
46
|
+
overflow: "hidden",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
2
|
+
import type { MegaAccount, MegaAccountStore } from "./types.js";
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = "mega-stream-proxy.accounts";
|
|
5
|
+
|
|
6
|
+
/** Structural subset of AsyncStorage (decoupled from its type surface). */
|
|
7
|
+
type AsyncStoreLike = {
|
|
8
|
+
getItem: (key: string) => Promise<string | null>;
|
|
9
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
10
|
+
removeItem: (key: string) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** React Native account store backed by @react-native-async-storage/async-storage. */
|
|
14
|
+
export function createAsyncStorageStore(
|
|
15
|
+
storage?: AsyncStoreLike,
|
|
16
|
+
): MegaAccountStore {
|
|
17
|
+
const s: AsyncStoreLike = (storage ?? AsyncStorage) as unknown as AsyncStoreLike;
|
|
18
|
+
|
|
19
|
+
const parseStored = (raw: string | null): MegaAccount[] => {
|
|
20
|
+
if (!raw) return [];
|
|
21
|
+
try {
|
|
22
|
+
const arr = JSON.parse(raw) as unknown[];
|
|
23
|
+
if (!Array.isArray(arr)) return [];
|
|
24
|
+
return arr
|
|
25
|
+
.filter((v): v is Record<string, unknown> => typeof v === "object" && v !== null)
|
|
26
|
+
.map((v) => ({
|
|
27
|
+
id: String(v.id ?? ""),
|
|
28
|
+
email: String(v.email ?? ""),
|
|
29
|
+
password: String(v.password ?? ""),
|
|
30
|
+
label: String(v.label ?? ""),
|
|
31
|
+
createdAt: Number(v.createdAt ?? 0),
|
|
32
|
+
}))
|
|
33
|
+
.sort((a, b) => b.createdAt - a.createdAt);
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
async list() {
|
|
41
|
+
return parseStored(await s.getItem(STORAGE_KEY));
|
|
42
|
+
},
|
|
43
|
+
async save(account) {
|
|
44
|
+
const accounts = parseStored(await s.getItem(STORAGE_KEY));
|
|
45
|
+
const idx = accounts.findIndex((a) => a.id === account.id);
|
|
46
|
+
if (idx >= 0) accounts[idx] = account;
|
|
47
|
+
else accounts.push(account);
|
|
48
|
+
await s.setItem(STORAGE_KEY, JSON.stringify(accounts));
|
|
49
|
+
},
|
|
50
|
+
async remove(id) {
|
|
51
|
+
const accounts = parseStored(await s.getItem(STORAGE_KEY));
|
|
52
|
+
await s.setItem(STORAGE_KEY, JSON.stringify(accounts.filter((a) => a.id !== id)));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
MegaAccount,
|
|
3
|
+
MegaAccountStore,
|
|
4
|
+
MegaTreeNode,
|
|
5
|
+
AccountTestResult,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
export { MegaAccountManager } from "./manager.js";
|
|
8
|
+
export { createLocalStorageStore } from "./local-storage.js";
|
|
9
|
+
export { createAsyncStorageStore } from "./async-storage.js";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { MegaAccount, MegaAccountStore } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = "mega-stream-proxy.accounts";
|
|
4
|
+
|
|
5
|
+
function parseStored(raw: string | null): MegaAccount[] {
|
|
6
|
+
if (!raw) return [];
|
|
7
|
+
try {
|
|
8
|
+
const arr = JSON.parse(raw) as unknown[];
|
|
9
|
+
if (!Array.isArray(arr)) return [];
|
|
10
|
+
return arr
|
|
11
|
+
.filter((v): v is Record<string, unknown> => typeof v === "object" && v !== null)
|
|
12
|
+
.map((v) => ({
|
|
13
|
+
id: String(v.id ?? ""),
|
|
14
|
+
email: String(v.email ?? ""),
|
|
15
|
+
password: String(v.password ?? ""),
|
|
16
|
+
label: String(v.label ?? ""),
|
|
17
|
+
createdAt: Number(v.createdAt ?? 0),
|
|
18
|
+
}));
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sortByNewest(a: MegaAccount, b: MegaAccount): number {
|
|
25
|
+
return b.createdAt - a.createdAt;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Persists accounts in `localStorage` (web). Subscribe listens to cross-tab `storage` events. */
|
|
29
|
+
export function createLocalStorageStore(
|
|
30
|
+
storage: Pick<Storage, "getItem" | "setItem" | "removeItem"> | null = null,
|
|
31
|
+
): MegaAccountStore {
|
|
32
|
+
const ls = storage ?? (typeof localStorage !== "undefined" ? localStorage : null);
|
|
33
|
+
const key = (): string => (ls ? STORAGE_KEY : "__noop__");
|
|
34
|
+
|
|
35
|
+
const readAll = async (): Promise<MegaAccount[]> =>
|
|
36
|
+
ls ? parseStored(ls.getItem(key())).sort(sortByNewest) : [];
|
|
37
|
+
|
|
38
|
+
const writeAll = async (accounts: MegaAccount[]): Promise<void> => {
|
|
39
|
+
if (!ls) return;
|
|
40
|
+
ls.setItem(key(), JSON.stringify(accounts));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
list: readAll,
|
|
45
|
+
async save(account) {
|
|
46
|
+
const accounts = await readAll();
|
|
47
|
+
const idx = accounts.findIndex((a) => a.id === account.id);
|
|
48
|
+
if (idx >= 0) accounts[idx] = account;
|
|
49
|
+
else accounts.push(account);
|
|
50
|
+
await writeAll(accounts);
|
|
51
|
+
},
|
|
52
|
+
async remove(id) {
|
|
53
|
+
const accounts = await readAll();
|
|
54
|
+
await writeAll(accounts.filter((a) => a.id !== id));
|
|
55
|
+
},
|
|
56
|
+
subscribe(cb, subscriptionKey = STORAGE_KEY) {
|
|
57
|
+
if (typeof window === "undefined" || !ls) return () => {};
|
|
58
|
+
const onStorage = (e: StorageEvent) => {
|
|
59
|
+
if (e.key !== subscriptionKey && e.key !== null) return;
|
|
60
|
+
void readAll().then(cb);
|
|
61
|
+
};
|
|
62
|
+
window.addEventListener("storage", onStorage);
|
|
63
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { AccountTestResult, MegaAccount, MegaAccountStore, MegaTreeNode } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const VIDEO_EXT = /\.(mp4|mkv|avi|mov|webm|m4v|ts|flv|wmv|mpg|mpeg|3gp)$/i;
|
|
4
|
+
const SUBTITLE_EXT = /\.(srt|ass|ssa|vtt)$/i;
|
|
5
|
+
|
|
6
|
+
type MegaStorageLike = {
|
|
7
|
+
ready: Promise<unknown>;
|
|
8
|
+
name?: string | null;
|
|
9
|
+
root?: unknown;
|
|
10
|
+
files?: Record<string, FileNodeLike>;
|
|
11
|
+
close: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type FileNodeLike = {
|
|
15
|
+
nodeId?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
size?: number;
|
|
18
|
+
directory?: boolean;
|
|
19
|
+
children?: FileNodeLike[];
|
|
20
|
+
link?: (cb: (err: Error | null, link?: string) => void) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function nextId(): string {
|
|
24
|
+
try {
|
|
25
|
+
return globalThis.crypto?.randomUUID?.() ?? fallbackId();
|
|
26
|
+
} catch {
|
|
27
|
+
return fallbackId();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fallbackId(): string {
|
|
32
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Owns the MEGA accounts list (via a pluggable store) plus the live megajs
|
|
37
|
+
* `Storage` sessions used while browsing a saved account's cloud drive.
|
|
38
|
+
*/
|
|
39
|
+
export class MegaAccountManager {
|
|
40
|
+
private readonly store: MegaAccountStore;
|
|
41
|
+
private sessions = new Map<string, Promise<MegaStorageLike>>();
|
|
42
|
+
|
|
43
|
+
constructor(store: MegaAccountStore) {
|
|
44
|
+
this.store = store;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ------------------------- account CRUD ------------------------- */
|
|
48
|
+
|
|
49
|
+
async listAccounts(): Promise<MegaAccount[]> {
|
|
50
|
+
return this.store.list();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
subscribe(cb: (accounts: MegaAccount[]) => void): () => void {
|
|
54
|
+
return this.store.subscribe?.(cb) ?? (() => {});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async addAccount(input: { email: string; password: string; label?: string }): Promise<MegaAccount> {
|
|
58
|
+
const account: MegaAccount = {
|
|
59
|
+
id: nextId(),
|
|
60
|
+
email: input.email.trim(),
|
|
61
|
+
password: input.password,
|
|
62
|
+
label: (input.label ?? "").trim(),
|
|
63
|
+
createdAt: Date.now(),
|
|
64
|
+
};
|
|
65
|
+
await this.store.save(account);
|
|
66
|
+
return account;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async deleteAccount(id: string): Promise<void> {
|
|
70
|
+
this.closeSession(id);
|
|
71
|
+
await this.store.remove(id);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ------------------------- sessions ------------------------- */
|
|
75
|
+
|
|
76
|
+
private async openStorage(account: { id: string; email: string; password: string }): Promise<MegaStorageLike> {
|
|
77
|
+
const existing = this.sessions.get(account.id);
|
|
78
|
+
if (existing) return existing;
|
|
79
|
+
const p = (async () => {
|
|
80
|
+
const { Storage } = (await import("megajs")) as unknown as {
|
|
81
|
+
Storage: new (opts: Record<string, unknown>) => MegaStorageLike;
|
|
82
|
+
};
|
|
83
|
+
const storage = new Storage({
|
|
84
|
+
email: account.email.trim(),
|
|
85
|
+
password: account.password,
|
|
86
|
+
userAgent: "mega-stream-proxy",
|
|
87
|
+
});
|
|
88
|
+
await storage.ready;
|
|
89
|
+
return storage;
|
|
90
|
+
})();
|
|
91
|
+
this.sessions.set(account.id, p);
|
|
92
|
+
p.catch(() => this.sessions.delete(account.id));
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
closeSession(accountId: string): void {
|
|
97
|
+
const p = this.sessions.get(accountId);
|
|
98
|
+
this.sessions.delete(accountId);
|
|
99
|
+
p?.then((s) => {
|
|
100
|
+
try {
|
|
101
|
+
s.close();
|
|
102
|
+
} catch {}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
closeAll(): void {
|
|
107
|
+
for (const id of [...this.sessions.keys()]) this.closeSession(id);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** login only — verifies credentials and counts the cloud drive. */
|
|
111
|
+
async testAccount(account: { id?: string; email: string; password: string }): Promise<AccountTestResult> {
|
|
112
|
+
const { Storage } = (await import("megajs")) as unknown as {
|
|
113
|
+
Storage: new (opts: Record<string, unknown>) => MegaStorageLike;
|
|
114
|
+
};
|
|
115
|
+
const storage = new Storage({
|
|
116
|
+
email: account.email.trim(),
|
|
117
|
+
password: account.password,
|
|
118
|
+
userAgent: "mega-stream-proxy",
|
|
119
|
+
});
|
|
120
|
+
await storage.ready;
|
|
121
|
+
let folders = 0;
|
|
122
|
+
let files = 0;
|
|
123
|
+
for (const node of Object.values(storage.files ?? {})) {
|
|
124
|
+
if (node.directory) folders += 1;
|
|
125
|
+
else files += 1;
|
|
126
|
+
}
|
|
127
|
+
const result = { name: storage.name ?? account.email, folders, files };
|
|
128
|
+
try {
|
|
129
|
+
storage.close();
|
|
130
|
+
} catch {}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Full Cloud-Drive tree of a saved account, root first. */
|
|
135
|
+
async loadTree(account: { id: string; email: string; password: string }): Promise<MegaTreeNode[]> {
|
|
136
|
+
const storage = await this.openStorage(account);
|
|
137
|
+
const out: MegaTreeNode[] = [];
|
|
138
|
+
const walk = (node: FileNodeLike, parentPath: string) => {
|
|
139
|
+
const isFolder = Boolean(node.directory);
|
|
140
|
+
const name = String(node.name ?? "");
|
|
141
|
+
const path = parentPath ? `${parentPath}/${name}` : name;
|
|
142
|
+
out.push({
|
|
143
|
+
handle: String(node.nodeId ?? name),
|
|
144
|
+
name,
|
|
145
|
+
isFolder,
|
|
146
|
+
isVideo: !isFolder && VIDEO_EXT.test(name),
|
|
147
|
+
isSubtitle: !isFolder && SUBTITLE_EXT.test(name),
|
|
148
|
+
size: Number(node.size ?? 0),
|
|
149
|
+
path,
|
|
150
|
+
});
|
|
151
|
+
if (!isFolder || !Array.isArray(node.children)) return;
|
|
152
|
+
for (const child of node.children) walk(child, path);
|
|
153
|
+
};
|
|
154
|
+
const root = storage.root as FileNodeLike | undefined;
|
|
155
|
+
if (root && Array.isArray(root.children)) {
|
|
156
|
+
for (const child of root.children) walk(child, "");
|
|
157
|
+
}
|
|
158
|
+
return out.filter((n) => n.isFolder || n.isVideo || n.isSubtitle);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Export a shareable link (with key) for a file inside the logged-in account. */
|
|
162
|
+
async exportLink(account: { id: string; email: string; password: string }, handle: string): Promise<string> {
|
|
163
|
+
const storage = await this.openStorage(account);
|
|
164
|
+
const node = storage.files?.[handle];
|
|
165
|
+
if (!node) throw new Error("File not found in this MEGA account.");
|
|
166
|
+
if (typeof node.link !== "function") throw new Error("Node has no link method.");
|
|
167
|
+
return new Promise<string>((resolve, reject) => {
|
|
168
|
+
node.link?.((err, link) => {
|
|
169
|
+
if (err || !link) reject(err ?? new Error("Failed to generate link"));
|
|
170
|
+
else resolve(link);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** A saved MEGA account (credentials + label). Passwords are handled by the store. */
|
|
2
|
+
export type MegaAccount = {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
password: string;
|
|
6
|
+
label: string;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pluggable persistence for MEGA accounts. Bring your own backend (Firestore,
|
|
12
|
+
* AsyncStorage, localStorage, …) by implementing this interface.
|
|
13
|
+
*/
|
|
14
|
+
export type MegaAccountStore = {
|
|
15
|
+
list: () => Promise<MegaAccount[]>;
|
|
16
|
+
/** Upsert by id. */
|
|
17
|
+
save: (account: MegaAccount) => Promise<void>;
|
|
18
|
+
remove: (id: string) => Promise<void>;
|
|
19
|
+
/** Optional live updates; return an unsubscribe function. */
|
|
20
|
+
subscribe?: (cb: (accounts: MegaAccount[]) => void, key?: string) => () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type MegaTreeNode = {
|
|
24
|
+
/** MEGA node handle */
|
|
25
|
+
handle: string;
|
|
26
|
+
name: string;
|
|
27
|
+
isFolder: boolean;
|
|
28
|
+
isVideo: boolean;
|
|
29
|
+
isSubtitle: boolean;
|
|
30
|
+
size: number;
|
|
31
|
+
path: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type AccountTestResult = {
|
|
35
|
+
name: string;
|
|
36
|
+
folders: number;
|
|
37
|
+
files: number;
|
|
38
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { loadMegaFile, fileSizeOf, withTimeout } from "./megaFile.js";
|
|
2
|
+
export { collectMegaFileStream } from "./stream.js";
|
|
3
|
+
export {
|
|
4
|
+
parseRange,
|
|
5
|
+
toMegaRange,
|
|
6
|
+
rangeHeaders,
|
|
7
|
+
type RangeResult,
|
|
8
|
+
} from "./ranges.js";
|
|
9
|
+
export { SW_MARKER } from "./swDefine.js";
|
|
10
|
+
export type { MegaFileLike, StreamLike } from "./proxy/types.js";
|
package/src/megaFile.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { MegaFileLike } from "./proxy/types.js";
|
|
2
|
+
|
|
3
|
+
const ATTRS_TIMEOUT_MS = 30_000;
|
|
4
|
+
|
|
5
|
+
/** Lazily resolves megajs `File.fromURL` + attributes (incl. folder-link child). */
|
|
6
|
+
export async function loadMegaFile(url: string): Promise<MegaFileLike> {
|
|
7
|
+
const { File } = (await import("megajs")) as {
|
|
8
|
+
File: { fromURL: (u: string) => MegaFileLike };
|
|
9
|
+
};
|
|
10
|
+
const file = File.fromURL(url.trim());
|
|
11
|
+
await withTimeout<unknown>(
|
|
12
|
+
file.loadAttributes(),
|
|
13
|
+
ATTRS_TIMEOUT_MS,
|
|
14
|
+
"MEGA did not respond. Check your connection and try again.",
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Folder links: loadAttributes resolves to the folder itself — pick the
|
|
18
|
+
// first non-directory child instead (mirrors the megajs docs sample).
|
|
19
|
+
const group = file as unknown as {
|
|
20
|
+
children?: unknown[];
|
|
21
|
+
find?: (fn: (n: any) => boolean, recursive: boolean) => MegaFileLike | undefined;
|
|
22
|
+
};
|
|
23
|
+
if (group.children?.length) {
|
|
24
|
+
const child = group.find?.((n: any) => !n.children, true);
|
|
25
|
+
if (child) return child;
|
|
26
|
+
}
|
|
27
|
+
return file;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function fileSizeOf(file: { size?: number }): number {
|
|
31
|
+
return Number(file.size ?? 0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function withTimeout<T>(p: Promise<T>, ms: number, msg: string): Promise<T> {
|
|
35
|
+
return new Promise<T>((resolve, reject) => {
|
|
36
|
+
const t = setTimeout(() => reject(new Error(msg)), ms);
|
|
37
|
+
p.then(
|
|
38
|
+
(v) => {
|
|
39
|
+
clearTimeout(t);
|
|
40
|
+
resolve(v);
|
|
41
|
+
},
|
|
42
|
+
(e) => {
|
|
43
|
+
clearTimeout(t);
|
|
44
|
+
reject(e);
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|