PageSourceSearch

https://app.curbviews.com/js/uploader.js?v=582

js curbviews.com collected 2026-10-09 23:21:39 UTC 11,696 bytes, 299 lines download raw bytes

1window.uploader = (() => {
2    const API_URL = '/api/v2';
3    const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024; // 5MB minimum for R2 multipart
4    const DEFAULT_CONCURRENCY = 4;
5    const MAX_CHUNK_SIZE = 10 * 1024 * 1024;
6    const MIN_CONCURRENCY = 2;
7    const MAX_CONCURRENCY = 2;
8
9    async function getApiToken() {
10        if (!api.apitoken) {
11            return await api.authorize(hd.sessionkey());
12        }
13        return api.apitoken;
14    }
15
16    function getAdaptiveSettings() {
17        const storedSpeed = parseFloat(localStorage.getItem('uploadSpeed') || '0') || 0;
18        const chunkSize = Math.max(DEFAULT_CHUNK_SIZE, Math.min(MAX_CHUNK_SIZE, storedSpeed * 5 * 1024 * 1024));
19        const concurrency = Math.max(MIN_CONCURRENCY, Math.min(MAX_CONCURRENCY, Math.floor(storedSpeed / 2)));
20        return { chunkSize, concurrency: storedSpeed > 0 ? concurrency : DEFAULT_CONCURRENCY };
21    }
22
23    async function getJwtToken(params) {
24        const token = await getApiToken();
25        const response = await fetch(`${API_URL}/Upload/Token`, {
26            method: 'POST',
27            headers: {
28                'Authorization': `Bearer ${token}`,
29                'Content-Type': 'application/json',
30            },
31            body: JSON.stringify(params),
32        });
33        if (!response.ok) {
34            throw new Error(await response.text() || `Failed to get JWT: ${response.status}`);
35        }
36        const data = await response.json();
37        return data.jwt;
38    }
39
40    function getWorkerUrl() {
41        //return 'https://uploadbucket.hd.pics';
42        return window.hd.isDev ? 'https://uploadbucket-dev.hd.pics' : 'https://uploadbucket.hd.pics';
43    };
44
45    // Custom Promise Queue for concurrency
46    class PromiseQueue {
47        constructor(concurrency = 4) {
48            this.concurrency = concurrency;
49            this.queue = [];
50            this.active = 0;
51        }
52
53        add(task) {
54            return new Promise((resolve, reject) => {
55                this.queue.push(async () => {
56                    try {
57                        const result = await task();
58                        resolve(result);
59                    } catch (err) {
60                        reject(err);
61                    } finally {
62                        this.active--;
63                        this.next();
64                    }
65                });
66                this.next();
67            });
68        }
69
70        next() {
71            if (this.active >= this.concurrency || this.queue.length === 0) return;
72            this.active++;
73            const task = this.queue.shift();
74            task();
75        }
76
77        onIdle() {
78            return Promise.allSettled(this.queue.map(() => new Promise(resolve => setTimeout(resolve, 0))));
79        }
80    }
81
82    // Retry utility with exponential backoff
83    async function retryFetch(url, options, maxRetries = 3, baseDelay = 1000) {
84        for (let attempt = 1; attempt <= maxRetries; attempt++) {
85            try {
86                const res = await fetch(url, options);
87                if (res.ok) {
88                    return res;
89                }
90                throw new Error(`Fetch failed with status ${res.status}`);
91            } catch (err) {
92                if (attempt === maxRetries) {
93                    throw err;
94                }
95                const delay = baseDelay * Math.pow(2, attempt - 1);
96                console.warn(`Retry ${attempt}/${maxRetries} for ${url}: ${err.message}. Retrying after ${delay}ms`);
97                await new Promise(resolve => setTimeout(resolve, delay));
98            }
99        }
100    }
101
102    // CHUNK PUT UPLOAD
103    async function uploadFile(file, params = {}, success = () => {}, progress = () => {}, error = () => {}) {
104        // Use single-part upload for small files (< 5MB)
105        if (file.size < DEFAULT_CHUNK_SIZE) {
106            try {
107                const response = await uploadFileSimple(file, params);
108                if (response.mediaId) {
109                    success(response);
110                } else {
111                    console.error('Single-part upload failed:', response);
112                    error(new Error('Single-part upload failed'), response);
113                }
114                return;
115            } catch (e) {
116                error(e);
117                return;
118            }
119        }
120
121        // Ensure consistent chunk size for multipart
122        const { chunkSize: rawChunkSize, concurrency } = getAdaptiveSettings();
123        const chunkSize = Math.max(DEFAULT_CHUNK_SIZE, Math.round(rawChunkSize / DEFAULT_CHUNK_SIZE) * DEFAULT_CHUNK_SIZE);
124        const numChunks = Math.ceil(file.size / chunkSize);
125        const speeds = [];
126
127        // Validate chunk sizes
128        for (let i = 0; i < numChunks - 1; i++) {
129            const start = i * chunkSize;
130            const end = Math.min(start + chunkSize, file.size);
131            if (end - start < DEFAULT_CHUNK_SIZE && i < numChunks - 1) {
132                error(new Error('Non-trailing chunk too small'));
133                return;
134            }
135        }
136
137        // Get file info
138        if (!params.Name) { params.Name = file.name.split('.')[0]; }
139        if (!params.Extension) { params.Extension = (file.name.split('.').pop() || '').replace('jpeg', 'jpg'); }
140
141        // Get JWT from API
142        try {
143            const jwt = await getJwtToken(params);
144
145            // Get presigned URLs
146            const presignResponse = await fetch(`${getWorkerUrl()}/presigns`, {
147                method: 'POST',
148                headers: {
149                    'Authorization': `Bearer ${jwt}`,
150                    'Content-Type': 'application/json',
151                },
152                body: JSON.stringify({ contentType: file.type, numParts: numChunks }),
153            });
154            if (!presignResponse.ok) {
155                error(new Error(await presignResponse.text() || `Presign failed: ${presignResponse.status}`));
156                return;
157            }
158            const { uploadId, urls } = await presignResponse.json();
159
160            // Upload chunks with custom queue
161            const queue = new PromiseQueue(concurrency);
162            const parts = [];
163            const uploadPromises = [];
164            let hasError = false;
165
166            for (let i = 0; i < numChunks; i++) {
167                uploadPromises.push(
168                    queue.add(async () => {
169                        if (hasError) return;
170                        const start = i * chunkSize;
171                        const end = Math.min(start + chunkSize, file.size);
172                        const chunk = file.slice(start, end);
173                        const tsStart = Date.now();
174                        try {
175                            const res = await retryFetch(urls[i], { method: 'PUT', body: chunk }, 3);
176                            const tsEnd = Date.now();
177                            const speed = (end - start) / 1024 / 1024 / ((tsEnd - tsStart) / 1000);
178                            speeds.push(speed);
179                            parts.push({ PartNumber: i + 1, ETag: res.headers.get('ETag').replace(/"/g, '') });
180                            progress(parts.length / numChunks);
181                        } catch (err) {
182                            hasError = true;
183                            console.error(`Chunk ${i + 1} failed after retries: ${err.message}`);
184                            // Abort upload
185                            try {
186                                const abortResponse = await fetch(`${getWorkerUrl()}/abort`, {
187                                    method: 'POST',
188                                    headers: {
189                                        'Authorization': `Bearer ${jwt}`,
190                                        'Content-Type': 'application/json',
191                                    },
192                                    body: JSON.stringify({ uploadId }),
193                                });
194                                if (!abortResponse.ok) {
195                                    console.error('Abort failed:', await abortResponse.text());
196                                }
197                            } catch (abortErr) {
198                                console.error('Abort request failed:', abortErr.message);
199                            }
200                            error(err);
201                            throw err;
202                        }
203                    })
204                );
205            }
206
207            try {
208                await Promise.all(uploadPromises);
209                if (hasError) {
210                    throw new Error('Upload aborted due to chunk failure');
211                }
212            } catch (e) {
213                error(e);
214                return;
215            }
216
217            // Update upload speed
218            if (speeds.length > 0) {
219                const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length;
220                localStorage.setItem('uploadSpeed', avgSpeed.toString());
221            }
222
223            // Complete upload
224            try {
225                const completeResponse = await fetch(`${getWorkerUrl()}/complete`, {
226                    method: 'POST',
227                    headers: {
228                        'Authorization': `Bearer ${jwt}`,
229                        'Content-Type': 'application/json',
230                    },
231                    body: JSON.stringify({ uploadId, parts }),
232                });
233                if (completeResponse.status === 200) {
234                    const responseData = await completeResponse.json();
235                    success({ ...responseData, status: completeResponse.status });
236                } else {
237                    error(new Error(await completeResponse.text() || `Complete failed: ${completeResponse.status}`));
238                }
239            } catch (e) {
240                error(e);
241            }
242        } catch (e) {
243            error(e);
244        }
245    }
246
247    // SINGLE PUT UPLOAD
248    async function uploadFileSimple(file, params = {}, nodup = false) {
249        try {
250            params.Name = file.name.split('.')[0];
251            params.Extension = (file.name.split('.').pop() || '').replace('jpeg', 'jpg');
252            const jwt = await getJwtToken(params);
253            if (!jwt) return false;
254
255            const res = await fetch(`${getWorkerUrl()}/upload${nodup ? '?nodup=1' : ''}`, {
256                method: 'PUT',
257                headers: { 'Authorization': `Bearer ${jwt}` },
258                body: file,
259            });
260            return await res.json();
261        } catch (e) {
262            console.error('Single PUT upload failed:', e.message);
263            return {};
264        }
265    }
266
267    // REMOTE PULL UPLOAD
268    async function uploadFileRemote(url, params = {}, nodup = false) {
269        try {
270            params.remoteUrl = url;
271            const [name, ext] = url.split('?')[0].split('/').reverse()[0].split('.');
272            if (name) params.Name = name;
273            if (ext) {
274                const extl = ext.toLowerCase().replace('jpeg', 'jpg');
275                if (['bmp', 'gif', 'jpg', 'png'].includes(extl)) {
276                    params.Extension = extl;
277                }
278            }
279
280            const jwt = await getJwtToken(params);
281            if (!jwt) return false;
282
283            const res = await fetch(`${getWorkerUrl()}/remote-upload${nodup ? '?nodup=1' : ''}`, {
284                method: 'POST',
285                headers: { 'Authorization': `Bearer ${jwt}` },
286            });
287            return res.ok ? await res.json() : {};
288        } catch (e) {
289            console.error('Remote pull upload failed:', e.message);
290            return {};
291        }
292    }
293
294    return {
295        uploadFile,
296        uploadFileSimple,
297        uploadFileRemote,
298    };
299})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.