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:
parent
38f511c2fb
commit
77087d2916
9 changed files with 248 additions and 129 deletions
|
@ -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
16
flow-typed/publish.js
vendored
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
: '',
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
70
web/setup/publish-v1.js
Normal 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
100
web/setup/publish-v2.js
Normal 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 })));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue