From ecfbaea8b1f821a698419b8a92c7100481f847e4 Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 12 Jul 2022 11:43:49 -0400 Subject: [PATCH] lbrysync demo initial - register / auth boilerplate --- config.js | 1 + ui/component/header/index.js | 2 + ui/component/settingSystem/index.js | 2 +- ui/constants/action_types.js | 8 +++ ui/lbrysync.js | 83 +++++++++++++++++++++++++++++ ui/reducers.js | 2 + ui/redux/actions/lbrysync.js | 49 +++++++++++++++++ ui/redux/reducers/lbrysync.js | 47 ++++++++++++++++ ui/redux/selectors/lbrysync.js | 16 ++++++ 9 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 ui/lbrysync.js create mode 100644 ui/redux/actions/lbrysync.js create mode 100644 ui/redux/reducers/lbrysync.js create mode 100644 ui/redux/selectors/lbrysync.js diff --git a/config.js b/config.js index c98f4dac1..24b5dbecb 100644 --- a/config.js +++ b/config.js @@ -13,6 +13,7 @@ const config = { LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com', LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //player.odysee.com LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API, + LBRYSYNC_API: process.env.LBRYSYNC_API, SEARCH_SERVER_API: process.env.SEARCH_SERVER_API, CLOUD_CONNECT_SITE_NAME: process.env.CLOUD_CONNECT_SITE_NAME, COMMENT_SERVER_API: process.env.COMMENT_SERVER_API, diff --git a/ui/component/header/index.js b/ui/component/header/index.js index f3b1df29b..63e4108fa 100644 --- a/ui/component/header/index.js +++ b/ui/component/header/index.js @@ -7,6 +7,7 @@ import { selectGetSyncErrorMessage } from 'redux/selectors/sync'; import { selectHasNavigated } from 'redux/selectors/app'; import { selectTotalBalance, selectBalance } from 'redux/selectors/wallet'; import { selectEmailToVerify, selectUser } from 'redux/selectors/user'; +import { doLbrysyncRegister } from 'redux/actions/lbrysync'; import * as SETTINGS from 'constants/settings'; import Header from './view'; @@ -24,6 +25,7 @@ const select = (state) => ({ const perform = (dispatch) => ({ clearEmailEntry: () => dispatch(doClearEmailEntry()), clearPasswordEntry: () => dispatch(doClearPasswordEntry()), + lbrysyncRegister: (username, password) => dispatch(doLbrysyncRegister(username, password)), signOut: () => dispatch(doSignOut()), }); diff --git a/ui/component/settingSystem/index.js b/ui/component/settingSystem/index.js index 1c011735c..5d9e7cef5 100644 --- a/ui/component/settingSystem/index.js +++ b/ui/component/settingSystem/index.js @@ -20,7 +20,7 @@ const select = (state) => ({ ffmpegStatus: selectFfmpegStatus(state), findingFFmpeg: selectFindingFFmpeg(state), walletEncrypted: selectWalletIsEncrypted(state), - isAuthenticated: selectUserVerifiedEmail(state), + isAuthenticated: selectUserVerifiedEmail(state), // odysee allowAnalytics: selectAllowAnalytics(state), }); diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index f3baef23b..bfa9e048e 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -497,3 +497,11 @@ export const UPDATE_UPLOAD_PROGRESS = 'UPDATE_UPLOAD_PROGRESS'; export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE'; export const GENERATE_AUTH_TOKEN_STARTED = 'GENERATE_AUTH_TOKEN_STARTED'; export const GENERATE_AUTH_TOKEN_SUCCESS = 'GENERATE_AUTH_TOKEN_SUCCESS'; + +// Lbry Sync +export const LSYNC_REGISTER_STARTED = 'LSYNC_REGISTER_STARTED'; +export const LSYNC_REGISTER_COMPLETED = 'LSYNC_REGISTER_COMPLETED'; // created account +export const LSYNC_REGISTER_FAILED = 'LSYNC_REGISTER_FAILED'; +export const LSYNC_AUTH_STARTED = 'LSYNC_AUTH_STARTED'; +export const LSYNC_AUTH_COMPLETED = 'LSYNC_AUTH_COMPLETED'; // got token +export const LSYNC_AUTH_FAILED = 'LSYNC_AUTH_FAILED'; diff --git a/ui/lbrysync.js b/ui/lbrysync.js new file mode 100644 index 000000000..acc4fb268 --- /dev/null +++ b/ui/lbrysync.js @@ -0,0 +1,83 @@ +// @flow +/* +DeriveSecrets +POST / + */ +import { LBRYSYNC_API as BASE_URL } from 'config'; +const SYNC_API_DOWN = 'sync_api_down'; +const DUPLICATE_EMAIL = 'duplicate_email'; +const UNKNOWN_ERROR = 'unknown_api_error'; +const API_VERSION = 2; +// const API_URL = `${BASE_URL}/api/${API_VERSION}`; +const AUTH_ENDPOINT = '/auth/full'; +const REGISTER_ENDPOINT = '/signup'; +// const WALLET_ENDPOINT = '/wallet'; +const Lbrysync = { + apiRequestHeaders: { 'Content-Type': 'application/json' }, + apiUrl: `${BASE_URL}/api/${API_VERSION}`, + setApiHeader: (key: string, value: string) => { + Lbrysync.apiRequestHeaders = Object.assign(Lbrysync.apiRequestHeaders, { [key]: value }); + }, + // store state "registered email: email" + register: async (email: string, password: string) => { + try { + const result = await callWithResult(REGISTER_ENDPOINT, { email, password }); + return result; + } catch (e) { + return e.message; + } + }, + // store state "lbrysynctoken: token" + getAuthToken: async (email: string, password: string, deviceId: string) => { + try { + const result = await callWithResult(AUTH_ENDPOINT, { email, password, deviceId }); + return { token: result }; + } catch (e) { + return { error: e.message }; + } + }, +}; + +function callWithResult(endpoint: string, params: ?{} = {}) { + return new Promise((resolve, reject) => { + apiCall( + endpoint, + params, + (result) => { + resolve(result); + }, + reject + ); + }); +} + +function apiCall(endpoint: string, params: ?{}, resolve: Function, reject: Function) { + const options = { + method: 'POST', + body: JSON.stringify(params), + }; + + return fetch(`${Lbrysync.apiUrl}${endpoint}`, options) + .then(handleResponse) + .then((response) => { + return resolve(response.result); + }) + .catch(reject); +} + +function handleResponse(response) { + if (response.status >= 200 && response.status < 300) { + return Promise.resolve(response.json()); + } + + if (response.status === 500) { + return Promise.reject(SYNC_API_DOWN); + } + + if (response.status === 409) { + return Promise.reject(DUPLICATE_EMAIL); + } + return Promise.reject(UNKNOWN_ERROR); +} + +export default Lbrysync; diff --git a/ui/reducers.js b/ui/reducers.js index e41b6d894..c6f5dd4ec 100644 --- a/ui/reducers.js +++ b/ui/reducers.js @@ -1,6 +1,7 @@ import { combineReducers } from 'redux'; import { connectRouter } from 'connected-react-router'; import { costInfoReducer, blacklistReducer, filteredReducer, statsReducer } from 'lbryinc'; +import { lbrysyncReducer } from 'redux/reducers/lbrysync'; import { claimsReducer } from 'redux/reducers/claims'; import { fileInfoReducer } from 'redux/reducers/file_info'; import { walletReducer } from 'redux/reducers/wallet'; @@ -49,4 +50,5 @@ export default (history) => wallet: walletReducer, sync: syncReducer, collections: collectionsReducer, + lbrysync: lbrysyncReducer, }); diff --git a/ui/redux/actions/lbrysync.js b/ui/redux/actions/lbrysync.js new file mode 100644 index 000000000..ec69e3589 --- /dev/null +++ b/ui/redux/actions/lbrysync.js @@ -0,0 +1,49 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; +import Lbrysync from 'lbrysync'; + +// register an email (eventually username) +export const doLbrysyncRegister = (email: string, password: string) => async (dispatch: Dispatch) => { + const { register } = Lbrysync; + // started + dispatch({ + type: ACTIONS.LSYNC_REGISTER_STARTED, + }); + const resultIfError = await register(email, password); + + if (!resultIfError) { + dispatch({ + type: ACTIONS.LSYNC_REGISTER_COMPLETED, + data: email, + }); + } else { + dispatch({ + type: ACTIONS.LSYNC_REGISTER_FAILED, + data: resultIfError, + }); + } +}; + +// get token given username/password +export const doLbrysyncAuthenticate = + (email: string, password: string, deviceId: string) => async (dispatch: Dispatch) => { + const { getAuthToken } = Lbrysync; + + // started + dispatch({ + type: ACTIONS.LSYNC_AUTH_STARTED, + }); + const result: { token?: string, error?: string } = await getAuthToken(email, password, deviceId); + + if (result.token) { + dispatch({ + type: ACTIONS.LSYNC_AUTH_COMPLETED, + data: result.token, + }); + } else if (result.error) { + dispatch({ + type: ACTIONS.LSYNC_AUTH_FAILED, + data: result.error, + }); + } + }; diff --git a/ui/redux/reducers/lbrysync.js b/ui/redux/reducers/lbrysync.js new file mode 100644 index 000000000..50150164b --- /dev/null +++ b/ui/redux/reducers/lbrysync.js @@ -0,0 +1,47 @@ +import * as ACTIONS from 'constants/action_types'; +import { handleActions } from 'util/redux-utils'; + +const defaultState = { + registering: false, + registeredEmail: null, + registerError: null, + syncProvider: null, + isAuthenticating: false, + authError: null, + authToken: null, // store this elsewhere? +}; + +export const lbrysyncReducer = handleActions( + { + // Register + [ACTIONS.LSYNC_REGISTER_STARTED]: (state) => ({ + ...state, + registering: true, + }), + [ACTIONS.LSYNC_REGISTER_COMPLETED]: (state, action) => ({ + ...state, + registeredEmail: action.data, + }), + [ACTIONS.LSYNC_REGISTER_FAILED]: (state) => ({ + ...state, + registeredEmail: null, + registering: false, + }), + // Auth + [ACTIONS.LSYNC_AUTH_STARTED]: (state) => ({ + ...state, + isAuthenticating: true, + }), + [ACTIONS.LSYNC_AUTH_COMPLETED]: (state, action) => ({ + ...state, + authToken: action.data, + }), + [ACTIONS.LSYNC_AUTH_FAILED]: (state, action) => ({ + ...state, + authError: action.data, + isAuthenticating: false, + }), + // ... + }, + defaultState +); diff --git a/ui/redux/selectors/lbrysync.js b/ui/redux/selectors/lbrysync.js new file mode 100644 index 000000000..a344cb3f5 --- /dev/null +++ b/ui/redux/selectors/lbrysync.js @@ -0,0 +1,16 @@ +import { createSelector } from 'reselect'; + +const selectState = (state) => state.lbrysync || {}; + +export const selectLbrySyncRegistering = createSelector(selectState, (state) => state.registering); + +export const selectLbrySyncEmail = createSelector(selectState, (state) => state.registeredEmail); + +export const selectLbrySyncRegisterError = createSelector(selectState, (state) => state.registerError); + +// probably shouldn't store this here. +export const selectLbrySyncToken = createSelector(selectState, (state) => state.registering); + +export const selectLbrySyncIsAuthenticating = createSelector(selectState, (state) => state.isAuthenticating); + +export const selectLbrySyncAuthError = createSelector(selectState, (state) => state.authError);