492b1601f6
update subscription types update changelog Simplify subscriptions sync logic add claim type use let over const change spinner color based on theme clean up subscriptions
262 lines
7.6 KiB
JavaScript
262 lines
7.6 KiB
JavaScript
// @flow
|
|
import * as ACTIONS from 'constants/action_types';
|
|
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
|
import type {
|
|
Dispatch,
|
|
SubscriptionState,
|
|
SubscriptionNotifications,
|
|
} from 'redux/reducers/subscriptions';
|
|
import type { Subscription } from 'types/subscription';
|
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
|
import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
|
import { doPurchaseUri } from 'redux/actions/content';
|
|
import Promise from 'bluebird';
|
|
import Lbryio from 'lbryio';
|
|
|
|
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
|
|
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
|
|
|
export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
|
const {
|
|
subscriptions: subscriptionState,
|
|
settings: { daemonSettings },
|
|
} = getState();
|
|
const { subscriptions: reduxSubscriptions } = subscriptionState;
|
|
const { share_usage_data: isSharingData } = daemonSettings;
|
|
|
|
if (!isSharingData && isSharingData !== undefined) {
|
|
// They aren't sharing their data, subscriptions will be handled by persisted redux state
|
|
return;
|
|
}
|
|
|
|
// most of this logic comes from scenarios where the db isn't synced with redux
|
|
// this will happen if the user stops sharing data
|
|
dispatch({ type: ACTIONS.FETCH_SUBSCRIPTIONS_START });
|
|
|
|
Lbryio.call('subscription', 'list')
|
|
.then(dbSubscriptions => {
|
|
const storedSubscriptions = dbSubscriptions || [];
|
|
|
|
// User has no subscriptions in db or redux
|
|
if (!storedSubscriptions.length && (!reduxSubscriptions || !reduxSubscriptions.length)) {
|
|
return [];
|
|
}
|
|
|
|
// There is some mismatch between redux state and db state
|
|
// If something is in the db, but not in redux, add it to redux
|
|
// If something is in redux, but not in the db, add it to the db
|
|
if (storedSubscriptions.length !== reduxSubscriptions.length) {
|
|
const dbSubMap = {};
|
|
const reduxSubMap = {};
|
|
const subsNotInDB = [];
|
|
const subscriptionsToReturn = reduxSubscriptions.slice();
|
|
|
|
storedSubscriptions.forEach(sub => {
|
|
dbSubMap[sub.claim_id] = 1;
|
|
});
|
|
|
|
reduxSubscriptions.forEach(sub => {
|
|
const { claimId } = parseURI(sub.uri);
|
|
reduxSubMap[claimId] = 1;
|
|
|
|
if (!dbSubMap[claimId]) {
|
|
subsNotInDB.push({
|
|
claim_id: claimId,
|
|
channel_name: sub.channelName,
|
|
});
|
|
}
|
|
});
|
|
|
|
storedSubscriptions.forEach(sub => {
|
|
if (!reduxSubMap[sub.claim_id]) {
|
|
const uri = `lbry://${sub.channel_name}#${sub.claim_id}`;
|
|
subscriptionsToReturn.push({ uri, channelName: sub.channel_name });
|
|
}
|
|
});
|
|
|
|
return Promise.all(subsNotInDB.map(payload => Lbryio.call('subscription', 'new', payload)))
|
|
.then(() => subscriptionsToReturn)
|
|
.catch(
|
|
() =>
|
|
// let it fail, we will try again when the navigate to the subscriptions page
|
|
subscriptionsToReturn
|
|
);
|
|
}
|
|
|
|
// DB is already synced, just return the subscriptions in redux
|
|
return reduxSubscriptions;
|
|
})
|
|
.then(subscriptions => {
|
|
dispatch({
|
|
type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
|
|
data: subscriptions,
|
|
});
|
|
})
|
|
.catch(() => {
|
|
dispatch({
|
|
type: ACTIONS.FETCH_SUBSCRIPTIONS_FAIL,
|
|
});
|
|
});
|
|
};
|
|
|
|
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
|
|
dispatch: Dispatch
|
|
) =>
|
|
dispatch({
|
|
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
|
|
data: {
|
|
subscription,
|
|
uri,
|
|
},
|
|
});
|
|
|
|
export const setSubscriptionNotification = (
|
|
subscription: Subscription,
|
|
uri: string,
|
|
notificationType: string
|
|
) => (dispatch: Dispatch) =>
|
|
dispatch({
|
|
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
|
|
data: {
|
|
subscription,
|
|
uri,
|
|
type: notificationType,
|
|
},
|
|
});
|
|
|
|
export const doCheckSubscription = (subscription: Subscription, notify?: boolean) => (
|
|
dispatch: Dispatch
|
|
) => {
|
|
dispatch({
|
|
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
|
|
data: subscription,
|
|
});
|
|
|
|
Lbry.claim_list_by_channel({ uri: subscription.uri, page: 1 }).then(result => {
|
|
const claimResult = result[subscription.uri] || {};
|
|
const { claims_in_channel: claimsInChannel } = claimResult;
|
|
|
|
if (claimsInChannel) {
|
|
if (notify) {
|
|
claimsInChannel.reduce((prev, cur, index) => {
|
|
const uri = buildURI({ contentName: cur.name, claimId: cur.claim_id }, false);
|
|
if (prev === -1 && uri !== subscription.latest) {
|
|
dispatch(
|
|
setSubscriptionNotification(
|
|
subscription,
|
|
uri,
|
|
index < SUBSCRIPTION_DOWNLOAD_LIMIT && !cur.value.stream.metadata.fee
|
|
? NOTIFICATION_TYPES.DOWNLOADING
|
|
: NOTIFICATION_TYPES.NOTIFY_ONLY
|
|
)
|
|
);
|
|
if (index < SUBSCRIPTION_DOWNLOAD_LIMIT && !cur.value.stream.metadata.fee) {
|
|
dispatch(doPurchaseUri(uri, { cost: 0 }));
|
|
}
|
|
}
|
|
return uri === subscription.latest || !subscription.latest ? index : prev;
|
|
}, -1);
|
|
}
|
|
|
|
dispatch(
|
|
setSubscriptionLatest(
|
|
{
|
|
channelName: claimsInChannel[0].channel_name,
|
|
uri: buildURI(
|
|
{
|
|
channelName: claimsInChannel[0].channel_name,
|
|
claimId: claimsInChannel[0].claim_id,
|
|
},
|
|
false
|
|
),
|
|
},
|
|
buildURI(
|
|
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
|
|
false
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
dispatch({
|
|
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
|
|
data: subscription,
|
|
});
|
|
});
|
|
};
|
|
|
|
export const setSubscriptionNotifications = (notifications: SubscriptionNotifications) => (
|
|
dispatch: Dispatch
|
|
) =>
|
|
dispatch({
|
|
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS,
|
|
data: {
|
|
notifications,
|
|
},
|
|
});
|
|
|
|
export const doChannelSubscribe = (subscription: Subscription) => (
|
|
dispatch: Dispatch,
|
|
getState: () => any
|
|
) => {
|
|
const {
|
|
settings: { daemonSettings },
|
|
} = getState();
|
|
const { share_usage_data: isSharingData } = daemonSettings;
|
|
|
|
dispatch({
|
|
type: ACTIONS.CHANNEL_SUBSCRIBE,
|
|
data: subscription,
|
|
});
|
|
|
|
// if the user isn't sharing data, keep the subscriptions entirely in the app
|
|
if (isSharingData) {
|
|
const { claimId } = parseURI(subscription.uri);
|
|
// They are sharing data, we can store their subscriptions in our internal database
|
|
Lbryio.call('subscription', 'new', {
|
|
channel_name: subscription.channelName,
|
|
claim_id: claimId,
|
|
});
|
|
}
|
|
|
|
dispatch(doCheckSubscription(subscription, true));
|
|
};
|
|
|
|
export const doChannelUnsubscribe = (subscription: Subscription) => (
|
|
dispatch: Dispatch,
|
|
getState: () => any
|
|
) => {
|
|
const {
|
|
settings: { daemonSettings },
|
|
} = getState();
|
|
const { share_usage_data: isSharingData } = daemonSettings;
|
|
|
|
dispatch({
|
|
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
|
|
data: subscription,
|
|
});
|
|
|
|
if (isSharingData) {
|
|
const { claimId } = parseURI(subscription.uri);
|
|
Lbryio.call('subscription', 'delete', {
|
|
claim_id: claimId,
|
|
});
|
|
}
|
|
};
|
|
|
|
export const doCheckSubscriptions = () => (
|
|
dispatch: Dispatch,
|
|
getState: () => SubscriptionState
|
|
) => {
|
|
const checkSubscriptionsTimer = setInterval(
|
|
() =>
|
|
selectSubscriptions(getState()).map((subscription: Subscription) =>
|
|
dispatch(doCheckSubscription(subscription, true))
|
|
),
|
|
CHECK_SUBSCRIPTIONS_INTERVAL
|
|
);
|
|
dispatch({
|
|
type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
|
|
data: { checkSubscriptionsTimer },
|
|
});
|
|
};
|