Browse Source

Retrieve relevant hashs from Gun DB

Carsten Porth 5 years ago
parent
commit
a2a7a1ef63

+ 51 - 103
app/package-lock.json

@@ -835,29 +835,21 @@
       }
     },
     "body-parser": {
-      "version": "1.18.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
-      "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
+      "version": "1.18.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+      "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
       "dev": true,
       "requires": {
         "bytes": "3.0.0",
         "content-type": "~1.0.4",
         "debug": "2.6.9",
-        "depd": "~1.1.1",
-        "http-errors": "~1.6.2",
-        "iconv-lite": "0.4.19",
+        "depd": "~1.1.2",
+        "http-errors": "~1.6.3",
+        "iconv-lite": "0.4.23",
         "on-finished": "~2.3.0",
-        "qs": "6.5.1",
-        "raw-body": "2.3.2",
-        "type-is": "~1.6.15"
-      },
-      "dependencies": {
-        "iconv-lite": {
-          "version": "0.4.19",
-          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-          "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
-          "dev": true
-        }
+        "qs": "6.5.2",
+        "raw-body": "2.3.3",
+        "type-is": "~1.6.16"
       }
     },
     "boom": {
@@ -1071,7 +1063,7 @@
     },
     "camelcase-keys": {
       "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
       "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
       "dev": true,
       "requires": {
@@ -1080,9 +1072,9 @@
       }
     },
     "caniuse-lite": {
-      "version": "1.0.30000890",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000890.tgz",
-      "integrity": "sha512-4NI3s4Y6ROm+SgZN5sLUG4k7nVWQnedis3c/RWkynV5G6cHSY7+a8fwFyn2yoBDE3E6VswhTNNwR3PvzGqlTkg==",
+      "version": "1.0.30000892",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000892.tgz",
+      "integrity": "sha512-X9rxMaWZNbJB5qjkDqPtNv/yfViTeUL6ILk0QJNxLV3OhKC5Acn5vxsuUvllR6B48mog8lmS+whwHq/QIYSL9w==",
       "dev": true
     },
     "caseless": {
@@ -1905,9 +1897,9 @@
       "dev": true
     },
     "electron-to-chromium": {
-      "version": "1.3.75",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.75.tgz",
-      "integrity": "sha512-nLo03Qpw++8R6BxDZL/B1c8SQvUe/htdgc5LWYHe5YotV2jVvRUMP5AlOmxOsyeOzgMiXrNln2mC05Ixz6vuUQ==",
+      "version": "1.3.79",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.79.tgz",
+      "integrity": "sha512-LQdY3j4PxuUl6xfxiFruTSlCniTrTrzAd8/HfsLEMi0PUpaQ0Iy+Pr4N4VllDYjs0Hyu2lkTbvzqlG+PX9NsNw==",
       "dev": true
     },
     "elliptic": {
@@ -2190,14 +2182,14 @@
       }
     },
     "express": {
-      "version": "4.16.3",
-      "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz",
-      "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+      "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
       "dev": true,
       "requires": {
         "accepts": "~1.3.5",
         "array-flatten": "1.1.1",
-        "body-parser": "1.18.2",
+        "body-parser": "1.18.3",
         "content-disposition": "0.5.2",
         "content-type": "~1.0.4",
         "cookie": "0.3.1",
@@ -2214,10 +2206,10 @@
         "on-finished": "~2.3.0",
         "parseurl": "~1.3.2",
         "path-to-regexp": "0.1.7",
-        "proxy-addr": "~2.0.3",
-        "qs": "6.5.1",
+        "proxy-addr": "~2.0.4",
+        "qs": "6.5.2",
         "range-parser": "~1.2.0",
-        "safe-buffer": "5.1.1",
+        "safe-buffer": "5.1.2",
         "send": "0.16.2",
         "serve-static": "1.13.2",
         "setprototypeof": "1.1.0",
@@ -2232,12 +2224,6 @@
           "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
           "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
           "dev": true
-        },
-        "safe-buffer": {
-          "version": "5.1.1",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
-          "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
-          "dev": true
         }
       }
     },
@@ -4572,9 +4558,9 @@
       "dev": true
     },
     "neo-async": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz",
-      "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==",
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz",
+      "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
       "dev": true
     },
     "next-tick": {
@@ -4643,13 +4629,13 @@
           "dev": true
         },
         "form-data": {
-          "version": "2.3.2",
-          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
-          "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+          "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
           "dev": true,
           "requires": {
             "asynckit": "^0.4.0",
-            "combined-stream": "1.0.6",
+            "combined-stream": "^1.0.6",
             "mime-types": "^2.1.12"
           }
         },
@@ -4695,12 +4681,6 @@
           "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
           "dev": true
         },
-        "qs": {
-          "version": "6.5.2",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
-          "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
-          "dev": true
-        },
         "request": {
           "version": "2.88.0",
           "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
@@ -4731,7 +4711,7 @@
         },
         "semver": {
           "version": "5.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
           "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
           "dev": true
         },
@@ -5283,9 +5263,9 @@
       }
     },
     "postcss-value-parser": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz",
-      "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
+      "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
       "dev": true
     },
     "preserve": {
@@ -5379,9 +5359,9 @@
       "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
     },
     "qs": {
-      "version": "6.5.1",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
-      "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
       "dev": true
     },
     "querystring": {
@@ -5444,47 +5424,15 @@
       "dev": true
     },
     "raw-body": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
-      "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+      "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
       "dev": true,
       "requires": {
         "bytes": "3.0.0",
-        "http-errors": "1.6.2",
-        "iconv-lite": "0.4.19",
+        "http-errors": "1.6.3",
+        "iconv-lite": "0.4.23",
         "unpipe": "1.0.0"
-      },
-      "dependencies": {
-        "depd": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
-          "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=",
-          "dev": true
-        },
-        "http-errors": {
-          "version": "1.6.2",
-          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
-          "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
-          "dev": true,
-          "requires": {
-            "depd": "1.1.1",
-            "inherits": "2.0.3",
-            "setprototypeof": "1.0.3",
-            "statuses": ">= 1.3.1 < 2"
-          }
-        },
-        "iconv-lite": {
-          "version": "0.4.19",
-          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-          "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
-          "dev": true
-        },
-        "setprototypeof": {
-          "version": "1.0.3",
-          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
-          "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=",
-          "dev": true
-        }
       }
     },
     "read-pkg": {
@@ -5857,9 +5805,9 @@
       }
     },
     "semver": {
-      "version": "5.5.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
-      "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==",
+      "version": "5.6.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+      "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
       "dev": true
     },
     "send": {
@@ -6100,9 +6048,9 @@
       }
     },
     "source-list-map": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
-      "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
       "dev": true
     },
     "source-map": {
@@ -6429,9 +6377,9 @@
       },
       "dependencies": {
         "debug": {
-          "version": "3.2.5",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
-          "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
           "dev": true,
           "requires": {
             "ms": "^2.1.1"

+ 1 - 1
app/package.json

@@ -51,7 +51,7 @@
     "zone.js": "0.8.26"
   },
   "devDependencies": {
-    "@ionic/app-scripts": "^3.2.0",
+    "@ionic/app-scripts": "3.2.0",
     "typescript": "~2.6.2"
   },
   "description": "HybridOSN is a Twitter client and a so called Dapp which allows its users to exchange data also via a P2P network using blockchain technology.",

+ 47 - 28
app/src/pages/profile/profile.ts

@@ -4,7 +4,8 @@ import {
   NavController,
   NavParams,
   InfiniteScroll,
-  Content
+  Content,
+  LoadingController
 } from "ionic-angular";
 import { TwitterApiProvider } from "../../providers/twitter-api/twitter-api";
 import { P2pStorageIpfsProvider } from "../../providers/p2p-storage-ipfs/p2p-storage-ipfs";
@@ -34,6 +35,7 @@ export class ProfilePage {
 
   constructor(
     public navCtrl: NavController,
+    private loadingCtrl: LoadingController,
     public navParams: NavParams,
     private twitter: TwitterApiProvider,
     private ipfs: P2pStorageIpfsProvider,
@@ -41,13 +43,20 @@ export class ProfilePage {
   ) {}
 
   ionViewDidLoad() {
+    // Show loading indicator
+    const loading = this.loadingCtrl.create();
+    loading.present();
+
+    // Read user id
     const userId = this.navParams.get("userId");
 
+    // Fetch user details from Twitter
     this.twitter.fetchUser(userId).then(res => (this.user = res));
 
-    this.loadTimeline(userId, null).then(res => {
+    // Load user's timeline from Twitter and P2P
+    this.loadTimeline(userId).then(res => {
       if (res.length > 0) {
-        // Save tweets
+        // Store tweets
         this.tweets = res;
 
         // Save oldest tweet's id for next load more
@@ -55,11 +64,13 @@ export class ProfilePage {
           .filter(tweet => !tweet.private_tweet)
           .reduce((acc, cur) => (acc.id < cur.id ? acc : cur))["id"];
       }
+      // Hide loading indicator
+      loading.dismiss();
     });
   }
 
   doRefresh(refresher) {
-    this.loadTimeline(this.user.id, null).then(res => {
+    this.loadTimeline(this.user.id).then(res => {
       if (res.length > 0) {
         // Replace tweets
         this.tweets = res;
@@ -76,23 +87,27 @@ export class ProfilePage {
   }
 
   loadMore(infiniteScroll: InfiniteScroll) {
-    this.loadTimeline(this.user.id, this.oldestLoadedTweetId).then(res => {
-      if (res.length > 0) {
-        // Append loaded tweets
-        this.tweets = this.tweets.concat(res);
-
-        // Save oldest tweet's id for next load more
-        this.oldestLoadedTweetId = res
-          .filter(tweet => !tweet.private_tweet)
-          .reduce((acc, cur) => (acc.id < cur.id ? acc : cur))["id"];
-      }
-
+    if (this.enableInfiniteScroll) {
+      this.loadTimeline(this.user.id, this.oldestLoadedTweetId).then(res => {
+        if (res.length > 0) {
+          // Append loaded tweets
+          this.tweets = this.tweets.concat(res);
+
+          // Save oldest tweet's id for next load more
+          this.oldestLoadedTweetId = res
+            .filter(tweet => !tweet.private_tweet)
+            .reduce((acc, cur) => (acc.id < cur.id ? acc : cur))["id"];
+        }
+        // Hide loading icon
+        infiniteScroll.complete();
+      });
+    } else {
       // Hide loading icon
       infiniteScroll.complete();
-    });
+    }
   }
 
-  private async loadTimeline(userId, oldestLoadedTweetId) {
+  private async loadTimeline(userId, oldestLoadedTweetId?) {
     // Fetch tweets from Twitter
     const tweets =
       oldestLoadedTweetId == null
@@ -103,7 +118,7 @@ export class ProfilePage {
           );
 
     // Determine end of time interval to look for private tweets
-    let intervalEnd;
+    let intervalEnd: Date;
     if (tweets.length < 20) {
       // End of timeline is reached
       this.enableInfiniteScroll = false;
@@ -120,18 +135,22 @@ export class ProfilePage {
       intervalEnd
     );
 
-    // Load private tweets from P2P storage
-    let privateTweets = await this.ipfs.fetchTweets(privateTweetHashs);
+    if (privateTweetHashs.length) {
+      // Load private tweets from P2P storage
+      let privateTweets = await this.ipfs.fetchTweets(privateTweetHashs);
 
-    // Add user object to private tweets
-    privateTweets = await Promise.all(
-      privateTweets.map(async tweet => await this.addUserToTweet(tweet))
-    );
+      // Add user object to private tweets
+      privateTweets = await Promise.all(
+        privateTweets.map(async tweet => await this.addUserToTweet(tweet))
+      );
 
-    // Combine and sort tweets
-    return tweets
-      .concat(privateTweets)
-      .sort((a, b) => this.sortByDateAsc(a, b));
+      // Combine and sort tweets
+      return tweets
+        .concat(privateTweets)
+        .sort((a, b) => this.sortByDateAsc(a, b));
+    } else {
+      return tweets;
+    }
   }
 
   private sortByDateAsc(a, b) {

+ 6 - 2
app/src/pages/write-tweet/write-tweet.ts

@@ -52,13 +52,17 @@ export class WriteTweetPage {
   }
 
   async submitTweet() {
-    let loading = this.loadingCtrl.create();
+    const loading = this.loadingCtrl.create();
     loading.present();
 
     if (this.tweet.value.p2p) {
       const tweet = await this.buildPrivateTweet();
       const res = await this.ipfs.storeTweet(tweet);
-      this.gun.storeLastTweetHashForUser("username", res["Hash"]);
+      this.gun.storeLastTweetHashForUser(
+        tweet.user_id,
+        res["Hash"],
+        tweet.created_at
+      );
     } else {
       await this.twitter.tweet(this.tweet.value["text"]);
     }

+ 40 - 10
app/src/providers/p2p-database-gun/p2p-database-gun.ts

@@ -1,5 +1,7 @@
 import { Injectable } from "@angular/core";
-import Gun from "gun";
+import Gun from "gun/gun";
+import "gun/lib/then";
+import "gun/lib/load";
 
 /*
   Generated class for the P2pDatabaseGunProvider provider.
@@ -10,30 +12,58 @@ import Gun from "gun";
 @Injectable()
 export class P2pDatabaseGunProvider {
   private gun;
+  osnPrefix: string = "hybridOSN-";
 
   constructor() {
     this.gun = Gun();
   }
 
-  public storeLastTweetHashForUser(userid, hash): void {
-    this.gun.get(userid).put({ lastTweet: hash });
+  public storeLastTweetHashForUser(userId, hash, timestamp): void {
+    const tweet = this.gun
+      .get(timestamp)
+      .put({ hash: hash, created_at: timestamp });
+    this.gun
+      .get(this.osnPrefix + userId)
+      .get("tweets")
+      .set(tweet);
   }
 
-  public async getLastTweetFromUser(userid) {
+  public async getLastTweetFromUser(userId) {
     return new Promise(resolve =>
       this.gun
-        .get(userid)
-        .get("lastTweet")
+        .get(this.osnPrefix + userId)
+        .get("tweets")
         .once(resolve)
     );
   }
 
-  public fetchPrivateTweetHashsForUserInInterval(
+  public async fetchPrivateTweetHashsForUserInInterval(
     userId,
     intervalStart,
     intervalEnd
-  ): string[] {
-    // return ["QmU37grV5ZoK4d5d2Cooaf429LuD31WyFbAMw2f1DHtvaS"];
-    return [];
+  ): Promise<string[]> {
+    const gunIds = await this.gun
+      .get(this.osnPrefix + userId)
+      .get("tweets")
+      .then();
+
+    if (gunIds) {
+      const entryPromises = [];
+      Object.keys(gunIds)
+        .filter(key => key !== "_")
+        .forEach(key => entryPromises.push(this.gun.get(key).then()));
+
+      const entries = await Promise.all(entryPromises);
+
+      return entries
+        .filter(
+          entry =>
+            new Date(entry["created_at"]) <= intervalStart &&
+            new Date(entry["created_at"]) > intervalEnd
+        )
+        .map(entry => entry["hash"]);
+    } else {
+      return [];
+    }
   }
 }

+ 5 - 5
app/src/providers/p2p-storage-ipfs/p2p-storage-ipfs.ts

@@ -27,11 +27,11 @@ export class P2pStorageIpfsProvider {
     return this.http.get(this.infuraUrl + "cat", options).toPromise();
   }
 
-  public async fetchTweets(hashs: Array<string>) {
-    let res = [];
-    hashs.forEach(element => {
-      res.push(this.fetchTweet(element));
+  public async fetchTweets(hashs: string[]) {
+    const tweetPromises = [];
+    hashs.forEach(hash => {
+      tweetPromises.push(this.fetchTweet(hash));
     });
-    return await Promise.all(res);
+    return await Promise.all(tweetPromises);
   }
 }