slate/node_common/managers/viewer.js

561 lines
15 KiB
JavaScript
Raw Normal View History

import * as Environment from "~/node_common/environment";
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Constants from "~/node_common/constants";
import * as Serializers from "~/node_common/serializers";
import * as Social from "~/node_common/social";
import * as Strings from "~/common/strings";
import * as Websocket from "~/node_common/nodejs-websocket";
2020-11-26 02:53:25 +03:00
import * as Filecoin from "~/common/filecoin";
const STAGING_DEAL_BUCKET = "stage-deal";
const delay = async (waitMs) => {
return await new Promise((resolve) => setTimeout(resolve, waitMs));
};
2020-10-31 02:12:20 +03:00
const websocketSend = async (type, data) => {
if (Strings.isEmpty(Environment.PUBSUB_SECRET)) {
return;
}
const ws = Websocket.get();
const encryptedData = await Utilities.encryptWithSecret(
JSON.stringify(data),
Environment.PUBSUB_SECRET
);
// NOTE(jim): Only allow this to be passed around encrypted.
ws.send(
JSON.stringify({
type,
iv: encryptedData.iv,
data: encryptedData.hex,
})
);
};
export const hydratePartialViewer = async (user) => {
const data = {
2020-10-31 02:12:20 +03:00
id: user.id,
username: user.username,
data: {
name: user.data.name ? user.data.name : "",
photo: user.data.photo ? user.data.photo : "",
body: user.data.body ? user.data.body : "",
},
type: "PARTIAL_VIEWER",
library: user.data.library,
onboarding: user.data.onboarding || {},
// TODO(jim): Move this elsewhere.
allow_filecoin_directory_listing: user.data.allow_filecoin_directory_listing
? user.data.allow_filecoin_directory_listing
: null,
allow_automatic_data_storage: user.data.allow_automatic_data_storage
? user.data.allow_automatic_data_storage
: null,
allow_encrypted_data_storage: user.data.allow_encrypted_data_storage
? user.data.allow_encrypted_data_storage
: null,
};
2020-10-31 02:12:20 +03:00
websocketSend("UPDATE", data);
};
export const hydratePartialOnboarding = async (onboarding, userId) => {
2020-11-30 02:47:08 +03:00
console.log("HYDRATE partial onboarding");
2020-10-31 02:12:20 +03:00
const data = {
id: userId,
onboarding,
};
websocketSend("UPDATE", data);
};
export const hydratePartialSubscriptions = async (updated, userId) => {
2020-11-30 02:47:08 +03:00
console.log("HYDRATE partial subscriptions");
2020-10-31 02:12:20 +03:00
const data = {
id: userId,
};
2020-11-21 23:56:24 +03:00
const user = await Data.getUserById({ id: userId });
2020-10-31 02:12:20 +03:00
if (!user) {
return null;
}
2020-10-31 02:12:20 +03:00
if (user.error) {
return null;
}
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
let mostRecent;
if (updated.subscriptions) {
const subscriptions = await Data.getSubscriptionsByUserId({ userId });
let r1 = await Serializers.doSubscriptions({
users: [],
slates: [],
subscriptions,
serializedUsersMap: { [user.id]: Serializers.user(user) },
serializedSlatesMap: {},
});
data.subscriptions = r1.serializedSubscriptions;
mostRecent = r1;
}
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
if (updated.subscribers) {
const subscribers = await Data.getSubscribersByUserId({ userId });
let r2 = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: mostRecent
? mostRecent.serializedUsersMap
: { [user.id]: Serializers.user(user) },
serializedSlatesMap: mostRecent ? mostRecent.serializedSlatesMap : {},
});
data.subscribers = r2.serializedSubscribers;
mostRecent = r2;
}
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
if (updated.trusted) {
const trusted = await Data.getTrustedRelationshipsByUserId({ userId });
let r3 = await Serializers.doTrusted({
users: [],
trusted,
serializedUsersMap: mostRecent
? mostRecent.serializedUsersMap
: { [user.id]: Serializers.user(user) },
serializedSlatesMap: mostRecent ? mostRecent.serializedSlatesMap : {},
});
data.trusted = r3.serializedTrusted;
mostRecent = r3;
}
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
if (updated.pendingTrusted) {
const pendingTrusted = await Data.getPendingTrustedRelationshipsByUserId({
userId,
});
let r4 = await Serializers.doPendingTrusted({
users: [userId],
pendingTrusted,
serializedUsersMap: mostRecent
? mostRecent.serializedUsersMap
: { [user.id]: Serializers.user(user) },
serializedSlatesMap: mostRecent ? mostRecent.serializedSlatesMap : {},
});
data.pendingTrusted = r4.serializedPendingTrusted;
}
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
websocketSend("UPDATE", data);
};
2020-10-31 02:12:20 +03:00
export const hydratePartialKeys = async (keys, userId) => {
2020-11-30 02:47:08 +03:00
console.log("HYDRATE partial keys");
2020-10-31 02:12:20 +03:00
const data = {
id: userId,
keys,
};
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
websocketSend("UPDATE", data);
};
export const hydratePartialLibrary = async (library, userId) => {
2020-11-30 02:47:08 +03:00
console.log("HYDRATE partial library");
2020-10-31 02:12:20 +03:00
const data = {
id: userId,
library,
};
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
websocketSend("UPDATE", data);
};
export const hydratePartialSlates = async (slates, userId) => {
2020-11-30 02:47:08 +03:00
console.log("HYDRATE partial slates");
2020-10-31 02:12:20 +03:00
const data = {
id: userId,
slates,
};
2020-11-21 23:56:24 +03:00
2020-10-31 02:12:20 +03:00
websocketSend("UPDATE", data);
};
// export const hydratePartialActivity = async (activity, userId) => {
2020-11-30 02:47:08 +03:00
// this one will need to be more complex like what is in hydrate subscriptions
2020-10-31 02:12:20 +03:00
// websocketSend("UPDATE", data);
// };
export const hydrate = async (id) => {
2020-11-30 02:47:08 +03:00
let data = getById({ id });
2020-10-31 02:12:20 +03:00
websocketSend("UPDATE", data);
};
2020-11-30 02:47:08 +03:00
//NOTE(martina): determines whether user is logged in and should be redirected to in-client view
2020-11-17 23:36:36 +03:00
export const shouldRedirect = async ({ id }) => {
if (Strings.isEmpty(id)) {
return false;
}
2020-11-17 23:36:36 +03:00
const user = await Data.getUserById({
id,
});
2020-11-17 23:46:54 +03:00
if (user && user.id) {
return true;
}
2020-11-17 23:36:36 +03:00
2020-11-17 23:46:54 +03:00
return false;
};
2020-08-30 22:56:45 +03:00
// TODO(jim): Work on better serialization when adoption starts occuring.
export const getById = async ({ id }) => {
const user = await Data.getUserById({
id,
});
if (!user) {
return null;
}
if (user.error) {
return null;
}
2020-08-30 22:56:45 +03:00
// TODO(jim): You can serialize this last because you will have all the information
// from subscriptionsed, trusted, and pendingTrusted most likely.
2020-12-08 22:42:19 +03:00
let activity = await Data.getActivityForUserId({ userId: id });
const slates = await Data.getSlatesByUserId({ userId: id });
const keys = await Data.getAPIKeysByUserId({ userId: id });
const subscriptions = await Data.getSubscriptionsByUserId({ userId: id });
2020-09-07 01:30:33 +03:00
const subscribers = await Data.getSubscribersByUserId({ userId: id });
let serializedUsersMap = { [user.id]: Serializers.user(user) };
let serializedSlatesMap = {};
2020-08-30 22:56:45 +03:00
// NOTE(jim): The most expensive call first.
const r1 = await Serializers.doSubscriptions({
users: [],
slates: [],
subscriptions,
serializedUsersMap,
serializedSlatesMap,
});
2020-09-07 01:30:33 +03:00
const r2 = await Serializers.doSubscribers({
users: [],
slates: [],
subscribers,
serializedUsersMap: r1.serializedUsersMap,
serializedSlatesMap: r1.serializedSlatesMap,
});
2020-08-30 22:56:45 +03:00
// NOTE(jim): If any trusted users are subscription users, this ends up being cheaper.
const trusted = await Data.getTrustedRelationshipsByUserId({ userId: id });
2020-09-07 01:30:33 +03:00
const r3 = await Serializers.doTrusted({
users: [],
trusted,
2020-09-07 01:30:33 +03:00
serializedUsersMap: r2.serializedUsersMap,
serializedSlatesMap: r2.serializedSlatesMap,
});
2020-08-30 22:56:45 +03:00
// NOTE(jim): This should be the cheapest call.
const pendingTrusted = await Data.getPendingTrustedRelationshipsByUserId({
userId: id,
});
2020-09-07 01:30:33 +03:00
const r4 = await Serializers.doPendingTrusted({
users: [id],
pendingTrusted,
2020-09-07 01:30:33 +03:00
serializedUsersMap: r3.serializedUsersMap,
serializedSlatesMap: r3.serializedSlatesMap,
});
let bytes = 0;
2020-10-05 00:30:28 +03:00
let imageBytes = 0;
let videoBytes = 0;
let audioBytes = 0;
let epubBytes = 0;
let pdfBytes = 0;
user.data.library[0].children.forEach((each) => {
2020-10-05 00:30:28 +03:00
if (each.type && each.type.startsWith("image/")) {
imageBytes += each.size;
} else if (each.type && each.type.startsWith("video/")) {
videoBytes += each.size;
} else if (each.type && each.type.startsWith("audio/")) {
audioBytes += each.size;
} else if (each.type && each.type.startsWith("application/epub")) {
epubBytes += each.size;
} else if (each.type && each.type.startsWith("application/pdf")) {
pdfBytes += each.size;
}
2020-11-30 02:47:08 +03:00
if (each.coverImage) {
imageBytes += each.coverImage.size;
}
2020-10-05 00:30:28 +03:00
bytes += each.size;
});
2020-12-08 22:42:19 +03:00
activity = await formatActivity(activity);
return {
...Serializers.user(user),
type: "VIEWER",
library: user.data.library,
2020-10-26 00:11:27 +03:00
onboarding: user.data.onboarding || {},
// TODO(jim): Move this elsewhere.
2020-09-29 00:30:12 +03:00
allow_filecoin_directory_listing: user.data.allow_filecoin_directory_listing
? user.data.allow_filecoin_directory_listing
: null,
2020-09-27 03:40:32 +03:00
allow_automatic_data_storage: user.data.allow_automatic_data_storage
? user.data.allow_automatic_data_storage
: null,
allow_encrypted_data_storage: user.data.allow_encrypted_data_storage
? user.data.allow_encrypted_data_storage
: null,
// NOTE(jim): Remaining data.
stats: {
bytes,
maximumBytes: Constants.TEXTILE_ACCOUNT_BYTE_LIMIT,
2020-10-05 00:30:28 +03:00
imageBytes,
videoBytes,
audioBytes,
epubBytes,
pdfBytes,
},
keys,
activity,
slates,
subscriptions: r1.serializedSubscriptions,
2020-09-07 01:30:33 +03:00
subscribers: r2.serializedSubscribers,
trusted: r3.serializedTrusted,
pendingTrusted: r4.serializedPendingTrusted,
};
};
2020-09-09 20:56:35 +03:00
2020-12-08 22:42:19 +03:00
const formatActivity = async (userActivity) => {
let activity = userActivity;
let slateIds = [];
if (activity && activity.length) {
activity = activity.filter((item) => {
if (item.data.type === "OTHER_USER_CREATE_SLATE") {
slateIds.push(item.data.context.slate.id);
}
return (
item.data.type === "OTHER_USER_CREATE_SLATE" ||
item.data.type === "OTHER_USER_CREATE_SLATE_OBJECT"
);
});
}
let slates = [];
if (slateIds && slateIds.length) {
slates = await Data.getSlatesByIds({ ids: slateIds });
}
let slateTable = {};
for (let slate of slates) {
slateTable[slate.id] = slate;
}
for (let item of activity) {
if (item.data.type === "OTHER_USER_CREATE_SLATE") {
let slate = slateTable[item.data.context.slate.id];
if (slate?.data?.objects?.length) {
item.data.context.slate = slate;
}
}
}
//NOTE(martina): remove empty slates
activity = activity.filter((item) => {
if (item.data.type === "OTHER_USER_CREATE_SLATE_OBJECT") return true;
let slate = item.data.context.slate;
return slate?.data?.objects?.length;
});
//NOTE(martina): rearrange order to always get an even row of 6 squares
let counter = 0;
for (let i = 0; i < activity.length; i++) {
let item = activity[i];
if (item.data.type === "OTHER_USER_CREATE_SLATE") {
counter += 2;
} else if (item.data.type === "OTHER_USER_CREATE_SLATE_OBJECT") {
counter += 1;
}
if (counter === 6) {
counter = 0;
} else if (counter > 6) {
let j = i - 1;
while (activity[j].data.type !== "OTHER_USER_CREATE_SLATE_OBJECT") {
j -= 1;
}
let temp = activity[j];
activity[j] = activity[i];
activity[i] = temp;
counter = 0;
i -= 1;
}
}
return activity;
};
export const getDealHistory = async ({ id }) => {
const user = await Data.getUserById({
id,
});
if (!user) {
return null;
}
if (user.error) {
return null;
}
let deals = [];
try {
const FilecoinSingleton = await Utilities.getFilecoinAPIFromUserToken({
user,
});
const { filecoin } = FilecoinSingleton;
const records = await filecoin.storageDealRecords({
ascending: false,
includePending: true,
includeFinal: true,
});
for (let i = 0; i < records.length; i++) {
const o = records[i];
deals.push({
dealId: o.dealInfo.dealId,
rootCid: o.rootCid,
proposalCid: o.dealInfo.proposalCid,
pieceCid: o.dealInfo.pieceCid,
addr: o.address,
miner: o.dealInfo.miner,
size: o.dealInfo.size,
// NOTE(jim): formatted size.
formattedSize: Strings.bytesToSize(o.dealInfo.size),
pricePerEpoch: o.dealInfo.pricePerEpoch,
startEpoch: o.dealInfo.startEpoch,
// NOTE(jim): just for point of reference on the total cost.
2020-11-26 02:53:25 +03:00
totalSpeculatedCost: Filecoin.formatAsFilecoinConversion(
o.dealInfo.pricePerEpoch * o.dealInfo.duration
),
duration: o.dealInfo.duration,
formattedDuration: Strings.getDaysFromEpoch(o.dealInfo.duration),
activationEpoch: o.dealInfo.activationEpoch,
time: o.time,
createdAt: Strings.toDateSinceEpoch(o.time),
pending: o.pending,
user: Serializers.user(user),
});
}
} catch (e) {
console.log(e);
Social.sendTextileSlackMessage({
file: "/node_common/managers/viewer.js",
user,
message: e.message,
code: e.code,
functionName: `filecoin.storageDealRecords`,
});
}
return { type: "VIEWER_FILECOIN_DEALS", deals };
};
2020-09-09 20:56:35 +03:00
export const getTextileById = async ({ id }) => {
const user = await Data.getUserById({
id,
});
if (!user) {
return null;
}
if (user.error) {
return null;
}
// NOTE(jim): This bucket is purely for staging data for other deals.
2020-09-23 22:46:56 +03:00
const stagingData = await Utilities.getBucketAPIFromUserToken({
user,
bucketName: STAGING_DEAL_BUCKET,
encrypted: false,
2020-09-23 22:46:56 +03:00
});
2020-09-09 20:56:35 +03:00
2020-12-04 11:09:24 +03:00
const FilecoinSingleton = await Utilities.getFilecoinAPIFromUserToken({
user,
});
const { filecoin } = FilecoinSingleton;
2020-09-23 12:46:59 +03:00
let r = null;
try {
r = await stagingData.buckets.list();
} catch (e) {
Social.sendTextileSlackMessage({
file: "/node_common/managers/viewer.js",
user,
message: e.message,
code: e.code,
functionName: `buckets.list`,
});
}
2020-12-04 11:09:24 +03:00
let addresses = null;
try {
addresses = await filecoin.addresses();
} catch (e) {
Social.sendTextileSlackMessage({
file: "/node_common/managers/viewer.js",
user,
message: e.message,
code: e.code,
functionName: `filecoin.addresses`,
});
}
let address = null;
if (addresses && addresses.length) {
address = {
name: addresses[0].name,
address: addresses[0].address,
type: addresses[0].type,
// TODO(jim): Serialize BigInt
// balance: addresses[0].balance,
};
}
2020-09-23 12:46:59 +03:00
let items = null;
const dealBucket = r.find((bucket) => bucket.name === STAGING_DEAL_BUCKET);
2020-09-23 12:46:59 +03:00
try {
const path = await stagingData.buckets.listPath(dealBucket.key, "/");
items = path.item.items;
2020-09-23 12:46:59 +03:00
} catch (e) {
Social.sendTextileSlackMessage({
file: "/node_common/managers/viewer.js",
user,
message: e.message,
code: e.code,
functionName: `buckets.listPath`,
2020-09-23 12:46:59 +03:00
});
}
2020-09-09 20:56:35 +03:00
const b = await Utilities.getBucketAPIFromUserToken({
user,
bucketName: "data",
encrypted: false,
});
const settings = await b.buckets.defaultArchiveConfig(b.bucketKey);
2020-09-09 20:56:35 +03:00
return {
type: "VIEWER_FILECOIN",
settings: {
...settings,
2020-12-04 11:09:24 +03:00
addr: addresses[0].address,
renewEnabled: settings.renew.enabled,
renewThreshold: settings.renew.threshold,
2020-09-09 20:56:35 +03:00
},
2020-12-04 11:09:24 +03:00
address,
deal: items ? items.filter((f) => f.name !== ".textileseed") : [],
2020-09-09 20:56:35 +03:00
};
};