import { Injectable } from "@angular/core"; import { TwitterApiProvider } from "../twitter-api/twitter-api"; import { P2pDatabaseGunProvider } from "../p2p-database-gun/p2p-database-gun"; import { P2pStorageIpfsProvider } from "../p2p-storage-ipfs/p2p-storage-ipfs"; @Injectable() export class FeedProvider { friends; constructor( private twitter: TwitterApiProvider, private gun: P2pDatabaseGunProvider, private ipfs: P2pStorageIpfsProvider ) {} public async loadUserTimeline( userId, oldestPublicTweet?, oldestPrivateTweet? ) { const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined; // Fetch tweets from Twitter let tweets = await this.twitter.fetchUserTimeline(userId, maxId); tweets = tweets.filter(tweet => tweet.id_str != maxId); // Determine start and end of time interval to look for private tweets const intervalStart: Date = oldestPrivateTweet ? new Date(oldestPrivateTweet["created_at"]) : new Date(); const intervalEnd: Date = this.getOldestTweetTimestamp(tweets); // Fetch private tweet hashs from P2P DB for corresponding interval const privateTweetHashs: string[] = await this.gun.fetchPrivateTweetHashsForUserInInterval( userId, intervalStart, intervalEnd ); if (privateTweetHashs.length) { const privateTweets = await this.fetchPrivateTweets(privateTweetHashs); // Combine and sort tweets return tweets .concat(privateTweets) .sort((a, b) => this.sortByDateAsc(a, b)); } else { return tweets; } } public async loadHomeTimeline( userId, oldestPublicTweet?, oldestPrivateTweet? ) { // Fetch tweets from Twitter const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined; let tweets = await this.twitter.fetchHomeFeed(maxId); tweets = tweets.filter(tweet => tweet.id_str != maxId); // Determine start and end of time interval to look for private tweets const intervalStart: Date = oldestPrivateTweet ? new Date(oldestPrivateTweet["created_at"]) : new Date(); const intervalEnd: Date = this.getOldestTweetTimestamp(tweets); // Fetch user's friends const friends = await this.getCachedFriends(userId); let privateTweetHashs = []; friends.forEach(async friend => { privateTweetHashs = privateTweetHashs.concat( await this.gun.fetchPrivateTweetHashsForUserInInterval( friend.id_str, intervalStart, intervalEnd ) ); }); // Add users private tweets privateTweetHashs = privateTweetHashs.concat( await this.gun.fetchPrivateTweetHashsForUserInInterval( userId, intervalStart, intervalEnd ) ); if (privateTweetHashs.length) { const privateTweets = await this.fetchPrivateTweets(privateTweetHashs); // Combine and sort tweets return tweets .concat(privateTweets) .sort((a, b) => this.sortByDateAsc(a, b)); } else { return tweets; } } private async fetchPrivateTweets(privateTweetHashs) { // Load private tweets from P2P storage const privateTweets = await this.ipfs.fetchTweets(privateTweetHashs); // Add user object to private tweets return await Promise.all( privateTweets.map(async tweet => await this.addUserToTweet(tweet)) ); } private async addUserToTweet(tweet) { tweet.user = await this.twitter.fetchUser(tweet.user_id); return tweet; } private getOldestTweetTimestamp(tweets): Date { if (tweets.length < 15) { // End of timeline is reached - load all private tweets return new Date("2018-04-01T00:00:00"); } else { const lastTweetTimestamp = tweets[tweets.length - 1].created_at; return new Date(lastTweetTimestamp); } } private sortByDateAsc(a, b) { const dateA = new Date(a.created_at); const dateB = new Date(b.created_at); if (dateA > dateB) { return -1; } else if (dateA < dateB) { return 1; } else { return 0; } } private async getCachedFriends(userId) { // Cache friends for 15 minutes to avoid unnecessary API calls if (!this.friends || (Date.now() - this.friends.lastUpdate) / 900000 > 15) { this.friends = { friendList: await this.twitter.fetchFriends(userId), lastUpdate: Date.now() }; } return this.friends.friendList; } }