Restore v1 code for livestream replay, etc.

v2 (tus) does not handle `remote_url`, so the app still needs v1 for that. Since we'll still have v1 code, use v1 for previews as well.
This commit is contained in:
infinite-persistence 2021-11-05 10:48:36 +08:00
parent 38f511c2fb
commit 77087d2916
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
9 changed files with 248 additions and 129 deletions

View file

@ -8,6 +8,7 @@ const config = {
WEB_SERVER_PORT: process.env.WEB_SERVER_PORT,
LBRY_WEB_API: process.env.LBRY_WEB_API, //api.na-backend.odysee.com',
LBRY_WEB_PUBLISH_API: process.env.LBRY_WEB_PUBLISH_API,
LBRY_WEB_PUBLISH_API_V2: process.env.LBRY_WEB_PUBLISH_API_V2,
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,

16
flow-typed/publish.js vendored
View file

@ -53,11 +53,23 @@ declare type PublishParams = {
tags: Array<Tag>,
};
declare type TusUploader = any;
declare type FileUploadSdkParams = {
file_path: string,
name: ?string,
preview?: boolean,
remote_url?: string,
thumbnail_url?: string,
title?: string,
};
declare type FileUploadItem = {
params: UpdatePublishFormData,
params: FileUploadSdkParams,
file: File,
fileFingerprint: string,
progress: string,
status?: string,
tusUploader?: any,
uploader?: TusUploader | XMLHttpRequest,
resumable: boolean,
};

View file

@ -15,7 +15,7 @@ type Props = {
export default function WebUploadItem(props: Props) {
const { uploadItem, doPublishResume, doUpdateUploadRemove, doOpenModal } = props;
const { params, file, fileFingerprint, progress, status, tusUploader } = uploadItem;
const { params, file, fileFingerprint, progress, status, resumable, uploader } = uploadItem;
const [showFileSelector, setShowFileSelector] = useState(false);
@ -40,8 +40,13 @@ export default function WebUploadItem(props: Props) {
subtitle: __('Cancel and remove the selected upload?'),
body: params.name ? <p className="empty">{`lbry://${params.name}`}</p> : undefined,
onConfirm: (closeModal) => {
if (tusUploader) {
tusUploader.abort(true);
if (uploader) {
if (resumable) {
// $FlowFixMe - couldn't resolve to TusUploader manually.
uploader.abort(true); // TUS
} else {
uploader.abort(); // XHR
}
}
doUpdateUploadRemove(params);
closeModal();
@ -50,25 +55,39 @@ export default function WebUploadItem(props: Props) {
}
function resolveProgressStr() {
if (!tusUploader) {
if (!uploader) {
return __('Stopped.');
} else if (status) {
switch (status) {
case 'retry':
return __('Retrying...');
case 'error':
return __('Failed.');
default:
return status;
}
if (resumable) {
if (status) {
switch (status) {
case 'retry':
return __('Retrying...');
case 'error':
return __('Failed.');
default:
return status;
}
} else {
const progressInt = parseInt(progress);
return progressInt === 100 ? __('Processing...') : __('Uploading...');
}
} else {
const progressInt = parseInt(progress);
return progressInt === 100 ? __('Processing...') : __('Uploading...');
return __('Uploading...');
}
}
function getRetryButton() {
if (!tusUploader) {
if (!resumable) {
return null;
}
if (uploader) {
// Should still be uploading. Don't show.
return null;
} else {
// Refreshed or connection broken.
const isFileActive = file instanceof File;
return (
<Button
@ -84,8 +103,6 @@ export default function WebUploadItem(props: Props) {
disabled={showFileSelector}
/>
);
} else {
return null;
}
}

View file

@ -35,7 +35,7 @@ import {
doAuthTokenRefresh,
} from 'util/saved-passwords';
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
import { LBRY_WEB_API, DEFAULT_LANGUAGE, LBRY_API_URL, LBRY_WEB_PUBLISH_API } from 'config';
import { LBRY_WEB_API, DEFAULT_LANGUAGE, LBRY_API_URL } from 'config';
// Import 3rd-party styles before ours for the current way we are code-splitting.
import 'scss/third-party.scss';
@ -69,7 +69,6 @@ sdkAPIHost = LBRY_WEB_API;
export const SDK_API_PATH = `${sdkAPIHost}/api/v1`;
const proxyURL = `${SDK_API_PATH}/proxy`;
const publishURL = LBRY_WEB_PUBLISH_API; // || `${SDK_API_PATH}/proxy`;
Lbry.setDaemonConnectionString(proxyURL);
@ -79,7 +78,6 @@ Lbry.setOverride(
new Promise((resolve, reject) => {
apiPublishCallViaWeb(
apiCall,
publishURL,
Lbry.getApiRequestHeaders() && Object.keys(Lbry.getApiRequestHeaders()).includes(X_LBRY_AUTH_TOKEN)
? Lbry.getApiRequestHeaders()[X_LBRY_AUTH_TOKEN]
: '',

View file

@ -689,11 +689,15 @@ export const doCheckReflectingFiles = () => (dispatch: Dispatch, getState: GetSt
}
};
export function doUpdateUploadAdd(file: File | string, params: { [key: string]: any }, tusUploader: any) {
export function doUpdateUploadAdd(
file: File | string,
params: { [key: string]: any },
uploader: TusUploader | XMLHttpRequest
) {
return (dispatch: Dispatch, getState: GetState) => {
dispatch({
type: ACTIONS.UPDATE_UPLOAD_ADD,
data: { file, params, tusUploader },
data: { file, params, uploader },
});
};
}

View file

@ -137,16 +137,17 @@ export const publishReducer = handleActions(
};
},
[ACTIONS.UPDATE_UPLOAD_ADD]: (state: PublishState, action) => {
const { file, params, tusUploader } = action.data;
const { file, params, uploader } = action.data;
const key = getKeyFromParam(params);
const currentUploads = Object.assign({}, state.currentUploads);
currentUploads[key] = {
file,
fileFingerprint: serializeFileObj(file), // TODO: get hash instead?
fileFingerprint: file ? serializeFileObj(file) : undefined, // TODO: get hash instead?
progress: '0',
params,
tusUploader,
uploader,
resumable: !(uploader instanceof XMLHttpRequest),
};
return { ...state, currentUploads };
@ -156,13 +157,17 @@ export const publishReducer = handleActions(
const key = getKeyFromParam(params);
const currentUploads = Object.assign({}, state.currentUploads);
if (!currentUploads[key]) {
return state;
}
if (progress) {
currentUploads[key].progress = progress;
delete currentUploads[key].status;
} else if (status) {
currentUploads[key].status = status;
if (status === 'error') {
delete currentUploads[key].tusUploader;
delete currentUploads[key].uploader;
}
}
@ -184,7 +189,12 @@ export const publishReducer = handleActions(
// Cleanup for 'publish::currentUploads'
if (newPublish.currentUploads) {
Object.keys(newPublish.currentUploads).forEach((key) => {
delete newPublish.currentUploads[key].tusUploader;
const params = newPublish.currentUploads[key].params;
if (!params || Object.keys(params).length === 0) {
delete newPublish.currentUploads[key];
} else {
delete newPublish.currentUploads[key].uploader;
}
});
}

70
web/setup/publish-v1.js Normal file
View file

@ -0,0 +1,70 @@
// @flow
// https://api.na-backend.odysee.com/api/v1/proxy currently expects publish to
// consist of a multipart/form-data POST request with:
// - 'file' binary
// - 'json_payload' publish params to be passed to the server's sdk.
import { X_LBRY_AUTH_TOKEN } from '../../ui/constants/token';
import { doUpdateUploadAdd, doUpdateUploadProgress, doUpdateUploadRemove } from '../../ui/redux/actions/publish';
import { LBRY_WEB_PUBLISH_API } from 'config';
const ENDPOINT = LBRY_WEB_PUBLISH_API;
const ENDPOINT_METHOD = 'publish';
export function makeUploadRequest(
token: string,
params: FileUploadSdkParams,
file: File | string,
isPreview?: boolean
) {
const { remote_url: remoteUrl } = params;
const body = new FormData();
if (file) {
body.append('file', file);
delete params['remote_url'];
} else if (remoteUrl) {
body.append('remote_url', remoteUrl);
delete params['remote_url'];
}
const jsonPayload = JSON.stringify({
jsonrpc: '2.0',
method: ENDPOINT_METHOD,
params,
id: new Date().getTime(),
});
// no fileData? do the livestream remote publish
body.append('json_payload', jsonPayload);
return new Promise<any>((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('POST', ENDPOINT);
xhr.setRequestHeader(X_LBRY_AUTH_TOKEN, token);
xhr.responseType = 'json';
xhr.upload.onprogress = (e) => {
const percentage = ((e.loaded / e.total) * 100).toFixed(2);
window.store.dispatch(doUpdateUploadProgress({ params, progress: percentage }));
};
xhr.onload = () => {
window.store.dispatch(doUpdateUploadRemove(params));
resolve(xhr);
};
xhr.onerror = () => {
window.store.dispatch(doUpdateUploadProgress({ params, status: 'error' }));
reject(new Error(__('There was a problem with your upload. Please try again.')));
};
xhr.onabort = () => {
window.store.dispatch(doUpdateUploadRemove(params));
};
if (!isPreview) {
window.store.dispatch(doUpdateUploadAdd(file, params, xhr));
}
xhr.send(body);
});
}

100
web/setup/publish-v2.js Normal file
View file

@ -0,0 +1,100 @@
// @flow
import * as tus from 'tus-js-client';
import { X_LBRY_AUTH_TOKEN } from '../../ui/constants/token';
import { doUpdateUploadAdd, doUpdateUploadProgress, doUpdateUploadRemove } from '../../ui/redux/actions/publish';
import { LBRY_WEB_PUBLISH_API_V2 } from 'config';
const RESUMABLE_ENDPOINT = LBRY_WEB_PUBLISH_API_V2;
const RESUMABLE_ENDPOINT_METHOD = 'publish';
const UPLOAD_CHUNK_SIZE_BYTE = 100000000;
export function makeResumableUploadRequest(
token: string,
params: FileUploadSdkParams,
file: File | string,
isPreview?: boolean
) {
return new Promise<any>((resolve, reject) => {
if (!RESUMABLE_ENDPOINT) {
reject(new Error('Publish: endpoint undefined'));
}
// @if NODE_ENV!='production'
if (params.remote_url) {
reject(new Error('Publish: v2 does not support remote_url'));
}
// @endif
const jsonPayload = JSON.stringify({
jsonrpc: '2.0',
method: RESUMABLE_ENDPOINT_METHOD,
params,
id: new Date().getTime(),
});
const uploader = new tus.Upload(file, {
endpoint: RESUMABLE_ENDPOINT,
chunkSize: UPLOAD_CHUNK_SIZE_BYTE,
retryDelays: [0, 3000, 3000],
parallelUploads: 1,
removeFingerprintOnSuccess: true,
headers: { [X_LBRY_AUTH_TOKEN]: token },
metadata: {
filename: file instanceof File ? file.name : file,
filetype: file instanceof File ? file.type : undefined,
},
onShouldRetry: (err, retryAttempt, options) => {
window.store.dispatch(doUpdateUploadProgress({ params, status: 'retry' }));
const FORBIDDEN_ERROR = 403;
const status = err.originalResponse ? err.originalResponse.getStatus() : 0;
return status !== FORBIDDEN_ERROR;
},
onError: (error) => {
window.store.dispatch(doUpdateUploadProgress({ params, status: 'error' }));
reject(new Error(error));
},
onProgress: (bytesUploaded, bytesTotal) => {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
window.store.dispatch(doUpdateUploadProgress({ params, progress: percentage }));
},
onSuccess: () => {
// Notify lbrynet server
const xhr = new XMLHttpRequest();
xhr.open('POST', `${uploader.url}/notify`);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Tus-Resumable', '1.0.0');
xhr.setRequestHeader(X_LBRY_AUTH_TOKEN, token);
xhr.responseType = 'json';
xhr.onload = () => {
window.store.dispatch(doUpdateUploadRemove(params));
resolve(xhr);
};
xhr.onerror = () => {
reject(new Error(__('There was a problem with your upload. Please try again.')));
};
xhr.onabort = () => {
window.store.dispatch(doUpdateUploadRemove(params));
};
xhr.send(jsonPayload);
},
});
uploader
.findPreviousUploads()
.then((previousUploads) => {
if (previousUploads.length > 0) {
uploader.resumeFromPreviousUpload(previousUploads[0]);
}
if (!isPreview) {
window.store.dispatch(doUpdateUploadAdd(file, params, uploader));
}
uploader.start();
})
.catch((err) => {
reject(new Error(__('Failed to initiate upload (%err%)', { err })));
});
});
}

View file

@ -1,27 +1,16 @@
// @flow
/*
https://api.na-backend.odysee.com/api/v1/proxy currently expects publish to consist
of a multipart/form-data POST request with:
- 'file' binary
- 'json_payload' collection of publish params to be passed to the server's sdk.
v2 no longer uses 'multipart/form-data'. It uses TUS to support resummable upload.
*/
import * as tus from 'tus-js-client';
import { X_LBRY_AUTH_TOKEN } from '../../ui/constants/token';
import { doUpdateUploadAdd, doUpdateUploadProgress, doUpdateUploadRemove } from '../../ui/redux/actions/publish';
const UPLOAD_CHUNK_SIZE_BYTE = 100000000;
import { makeUploadRequest } from './publish-v1';
import { makeResumableUploadRequest } from './publish-v2';
// A modified version of Lbry.apiCall that allows
// to perform calling methods at arbitrary urls
// and pass form file fields
export default function apiPublishCallViaWeb(
apiCall: (any, any, any, any) => any,
connectionString: string,
token: string,
method: string,
params: { file_path: string, preview: boolean, remote_url?: string }, // new param for remoteUrl
params: FileUploadSdkParams,
resolve: Function,
reject: Function
) {
@ -31,12 +20,6 @@ export default function apiPublishCallViaWeb(
return apiCall(method, params, resolve, reject);
}
if (!tus.isSupported) {
reject(new Error(__('Uploading is not supported with this browser.')));
return;
}
const counter = new Date().getTime();
let fileField = filePath;
if (preview) {
@ -47,92 +30,16 @@ export default function apiPublishCallViaWeb(
// Putting a dummy value here, the server is going to process the POSTed file
// and set the file_path itself
if (fileField) {
params.file_path = '__POST_FILE__';
delete params['remote_url'];
} else if (remoteUrl) {
delete params['remote_url'];
}
const jsonPayload = JSON.stringify({
jsonrpc: '2.0',
method,
params,
id: counter,
});
const useV1 = remoteUrl || preview || !tus.isSupported;
// no fileData? do the livestream remote publish
// Note: both function signature (params) should match.
const makeRequest = useV1 ? makeUploadRequest : makeResumableUploadRequest;
function makeRequest(connectionString, token, params, file: File | string) {
const metadata = {
filename: file instanceof File ? file.name : file,
filetype: file instanceof File ? file.type : undefined,
};
return new Promise((resolve, reject) => {
const uploader = new tus.Upload(fileField, {
endpoint: connectionString,
chunkSize: UPLOAD_CHUNK_SIZE_BYTE,
retryDelays: [0, 3000, 3000],
parallelUploads: 1,
removeFingerprintOnSuccess: true,
headers: { [X_LBRY_AUTH_TOKEN]: token },
metadata: metadata,
onShouldRetry: (err, retryAttempt, options) => {
window.store.dispatch(doUpdateUploadProgress({ params, status: 'retry' }));
const FORBIDDEN_ERROR = 403;
const status = err.originalResponse ? err.originalResponse.getStatus() : 0;
return status !== FORBIDDEN_ERROR;
},
onError: (error) => {
window.store.dispatch(doUpdateUploadProgress({ params, status: 'error' }));
reject(new Error(error));
},
onProgress: (bytesUploaded, bytesTotal) => {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
window.store.dispatch(doUpdateUploadProgress({ params, progress: percentage }));
},
onSuccess: () => {
// Notify lbrynet server
const xhr = new XMLHttpRequest();
xhr.open('POST', `${uploader.url}/notify`);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Tus-Resumable', '1.0.0');
xhr.setRequestHeader(X_LBRY_AUTH_TOKEN, token);
xhr.responseType = 'json';
xhr.onload = () => {
window.store.dispatch(doUpdateUploadRemove(params));
resolve(xhr);
};
xhr.onerror = () => {
reject(new Error(__('There was a problem with your upload. Please try again.')));
};
xhr.onabort = () => {
window.store.dispatch(doUpdateUploadRemove(params));
};
xhr.send(jsonPayload);
},
});
uploader
.findPreviousUploads()
.then((previousUploads) => {
if (previousUploads.length > 0) {
uploader.resumeFromPreviousUpload(previousUploads[0]);
}
window.store.dispatch(doUpdateUploadAdd(fileField, params, uploader));
uploader.start();
})
.catch((err) => {
reject(new Error(__('Failed to initiate upload (%err%)', { err })));
});
});
}
return makeRequest(connectionString, token, params, fileField)
return makeRequest(token, params, fileField, preview)
.then((xhr) => {
let error;
if (xhr && xhr.response) {