feed.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { Injectable } from "@angular/core";
  2. import { TwitterApiProvider } from "../twitter-api/twitter-api";
  3. import { P2pDatabaseGunProvider } from "../p2p-database-gun/p2p-database-gun";
  4. import { P2pStorageIpfsProvider } from "../p2p-storage-ipfs/p2p-storage-ipfs";
  5. import { CryptoProvider } from "../crypto/crypto";
  6. @Injectable()
  7. export class FeedProvider {
  8. friends;
  9. constructor(
  10. private twitter: TwitterApiProvider,
  11. private gun: P2pDatabaseGunProvider,
  12. private ipfs: P2pStorageIpfsProvider,
  13. private cryptoUtils: CryptoProvider
  14. ) {}
  15. public async loadUserTimeline(
  16. userId,
  17. oldestPublicTweet?,
  18. oldestPrivateTweet?
  19. ) {
  20. const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
  21. // Fetch tweets from Twitter
  22. let tweets = await this.twitter.fetchUserTimeline(userId, maxId);
  23. tweets = tweets.filter(tweet => tweet.id_str != maxId);
  24. // Determine start and end of time interval to look for private tweets
  25. const intervalStart: Date = oldestPrivateTweet
  26. ? new Date(oldestPrivateTweet["created_at"])
  27. : new Date();
  28. const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);
  29. // Fetch private tweet hashs from P2P DB for corresponding interval
  30. const privateTweetHashs: string[] = await this.gun.fetchPrivateTweetHashsForUserInInterval(
  31. userId,
  32. intervalStart,
  33. intervalEnd
  34. );
  35. if (privateTweetHashs.length) {
  36. const privateTweets = await this.fetchPrivateTweets(
  37. privateTweetHashs,
  38. userId
  39. );
  40. // Combine and sort tweets
  41. return tweets
  42. .concat(privateTweets)
  43. .sort((a, b) => this.sortByDateAsc(a, b));
  44. } else {
  45. return tweets;
  46. }
  47. }
  48. public async loadHomeTimeline(
  49. userId,
  50. oldestPublicTweet?,
  51. oldestPrivateTweet?
  52. ) {
  53. // Fetch tweets from Twitter
  54. const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
  55. let tweets = await this.twitter.fetchHomeFeed(maxId);
  56. tweets = tweets.filter(tweet => tweet.id_str != maxId);
  57. // Determine start and end of time interval to look for private tweets
  58. const intervalStart: Date = oldestPrivateTweet
  59. ? new Date(oldestPrivateTweet["created_at"])
  60. : new Date();
  61. const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);
  62. // Fetch user's friends
  63. const friends = await this.getCachedFriends(userId);
  64. let privateTweetHashs = [];
  65. friends.forEach(async friend => {
  66. privateTweetHashs = privateTweetHashs.concat(
  67. await this.gun.fetchPrivateTweetHashsForUserInInterval(
  68. friend.id_str,
  69. intervalStart,
  70. intervalEnd
  71. )
  72. );
  73. });
  74. // Add users private tweets
  75. privateTweetHashs = privateTweetHashs.concat(
  76. await this.gun.fetchPrivateTweetHashsForUserInInterval(
  77. userId,
  78. intervalStart,
  79. intervalEnd
  80. )
  81. );
  82. if (privateTweetHashs.length) {
  83. const privateTweets = await this.fetchPrivateTweets(
  84. privateTweetHashs,
  85. userId
  86. );
  87. // Combine and sort tweets
  88. return tweets
  89. .concat(privateTweets)
  90. .sort((a, b) => this.sortByDateAsc(a, b));
  91. } else {
  92. return tweets;
  93. }
  94. }
  95. private async fetchPrivateTweets(privateTweetHashs, userId: string) {
  96. // Load private tweets from P2P storage
  97. const privateEncryptedTweets = await this.ipfs.fetchTweets(
  98. privateTweetHashs
  99. );
  100. // Fetch public key
  101. const publicKey = await this.cryptoUtils.fetchPublicKeyForUser(userId);
  102. // Decrypt tweets
  103. const privateTweets = privateEncryptedTweets.map(encryptedTweet =>
  104. JSON.parse(this.cryptoUtils.decrypt(encryptedTweet, publicKey))
  105. );
  106. // Add user object to private tweets
  107. return await Promise.all(
  108. privateTweets.map(async tweet => await this.addUserToTweet(tweet))
  109. );
  110. }
  111. private async addUserToTweet(tweet) {
  112. tweet.user = await this.twitter.fetchUser(tweet.user_id);
  113. return tweet;
  114. }
  115. private getOldestTweetTimestamp(tweets): Date {
  116. if (tweets.length < 15) {
  117. // End of timeline is reached - load all private tweets
  118. return new Date("2018-04-01T00:00:00");
  119. } else {
  120. const lastTweetTimestamp = tweets[tweets.length - 1].created_at;
  121. return new Date(lastTweetTimestamp);
  122. }
  123. }
  124. private sortByDateAsc(a, b) {
  125. const dateA = new Date(a.created_at);
  126. const dateB = new Date(b.created_at);
  127. if (dateA > dateB) {
  128. return -1;
  129. } else if (dateA < dateB) {
  130. return 1;
  131. } else {
  132. return 0;
  133. }
  134. }
  135. private async getCachedFriends(userId) {
  136. // Cache friends for 15 minutes to avoid unnecessary API calls
  137. if (!this.friends || (Date.now() - this.friends.lastUpdate) / 900000 > 15) {
  138. this.friends = {
  139. friendList: await this.twitter.fetchFriends(userId),
  140. lastUpdate: Date.now()
  141. };
  142. }
  143. return this.friends.friendList;
  144. }
  145. }