Преглед изворни кода

WIP use a different approach to handle promise canceling
Add a new webpack generation config.

Painor пре 2 година
родитељ
комит
472c60c9c1

+ 6 - 0
README.md

@@ -80,6 +80,12 @@ To get a browser bundle of GramJS, use the following command:
 NODE_ENV=production npx webpack
 ```
 
+You can also use the helpful script `generate_webpack.js`
+
+```bash
+node generate_webpack.js
+```
+
 ## Calling the raw API
 
 To use raw telegram API methods use [invoke function](https://gram.js.org/beta/classes/TelegramClient.html#invoke).

+ 138 - 0
generate_webpack.js

@@ -0,0 +1,138 @@
+const { exec } = require("child_process");
+const fs = require("fs");
+const path = require("path");
+const webpack = require("webpack");
+const webpackConfig = require("./webpack.config");
+webpackConfig.entry = path.resolve(__dirname, "browser/index.js");
+/**
+ * Generates a webpack build and put it in browser folder
+ */
+
+function addBuffer(dir) {
+  fs.readdirSync(dir).forEach((file) => {
+    let fullPath = path.join(dir, file);
+    if (fs.lstatSync(fullPath).isDirectory()) {
+      addBuffer(fullPath);
+    } else {
+      if (
+        (fullPath.endsWith(".ts") || fullPath.endsWith(".js")) &&
+        (!fullPath.endsWith(".d.ts") ||
+          fullPath.endsWith("api.d.ts") ||
+          fullPath.endsWith("define.d.ts"))
+      ) {
+        const tsFile = fs.readFileSync(fullPath, "utf8");
+        if (tsFile.includes("Buffer")) {
+          const newTsFile = 'import { Buffer } from "buffer/";\n' + tsFile;
+          fs.writeFileSync(fullPath, newTsFile, "utf8");
+        }
+      }
+    }
+  });
+}
+
+function renameFiles(dir, action) {
+  fs.readdirSync(dir).forEach((file) => {
+    let fullPath = path.join(dir, file);
+    if (fs.lstatSync(fullPath).isDirectory()) {
+      renameFiles(fullPath, action);
+    } else {
+      if (fullPath.includes("example")) {
+        fs.unlinkSync(fullPath);
+      }
+
+      if (fullPath.includes("-BROWSER")) {
+        console.log(action, fullPath);
+
+        if (action === "rename") {
+          fs.renameSync(fullPath, fullPath.replace("-BROWSER", ""));
+        } else if (action === "delete") {
+          fs.unlinkSync(fullPath);
+        }
+      }
+    }
+  });
+}
+
+function copyFolderSync(from, to) {
+  fs.mkdirSync(to);
+  fs.readdirSync(from).forEach((element) => {
+    if (fs.lstatSync(path.join(from, element)).isFile()) {
+      fs.copyFileSync(path.join(from, element), path.join(to, element));
+    } else {
+      copyFolderSync(path.join(from, element), path.join(to, element));
+    }
+  });
+}
+
+fs.rmSync("browser", { recursive: true, force: true });
+fs.rmSync("tempBrowser", { recursive: true, force: true });
+copyFolderSync("gramjs", "tempBrowser");
+addBuffer("tempBrowser");
+renameFiles("tempBrowser", "rename");
+
+const tsconfig = fs.readFileSync("tsconfig.json", "utf8");
+let newTsconfig = tsconfig.replace(/\.\/dist/g, "./browser");
+newTsconfig = newTsconfig.replace(/gramjs/g, "tempBrowser");
+fs.writeFileSync("tsconfig.json", newTsconfig, "utf8");
+const packageJSON = JSON.parse(fs.readFileSync("package.json", "utf8"));
+const oldValueStorage = packageJSON.dependencies["node-localstorage"];
+const oldValueSocks = packageJSON.dependencies["socks"];
+delete packageJSON.dependencies["node-localstorage"];
+delete packageJSON.dependencies["socks"];
+fs.writeFileSync(
+  "package.json",
+  JSON.stringify(packageJSON, null, "  "),
+  "utf8"
+);
+
+const npmi = exec("npm i");
+npmi.on("close", (code) => {
+  if (code !== 0) {
+    throw new Error("Error happened " + code);
+  }
+
+  const tsc = exec("tsc");
+  tsc.stdout.on("data", function (data) {
+    console.log("stdout: " + data.toString());
+  });
+
+  tsc.stderr.on("data", function (data) {
+    console.error("stderr: " + data.toString());
+  });
+  tsc.on("close", (code) => {
+    if (code !== 0) {
+      throw new Error("Error happened " + code);
+    }
+
+    fs.copyFileSync("package.json", "browser/package.json");
+    fs.copyFileSync("README.md", "browser/README.md");
+    fs.copyFileSync("LICENSE", "browser/LICENSE");
+    fs.copyFileSync("gramjs/tl/api.d.ts", "browser/tl/api.d.ts");
+    fs.copyFileSync("gramjs/define.d.ts", "browser/define.d.ts");
+    fs.rmSync("tempBrowser", { recursive: true, force: true });
+    const tsconfig = fs.readFileSync("tsconfig.json", "utf8");
+    let newTsconfig = tsconfig.replace(/\.\/browser/g, "./dist");
+    newTsconfig = newTsconfig.replace(/tempBrowser/g, "gramjs");
+    fs.writeFileSync("tsconfig.json", newTsconfig, "utf8");
+    const packageJSON = JSON.parse(fs.readFileSync("package.json", "utf8"));
+    packageJSON.dependencies["node-localstorage"] = oldValueStorage;
+    packageJSON.dependencies["socks"] = oldValueSocks;
+    fs.writeFileSync(
+      "package.json",
+      JSON.stringify(packageJSON, null, "  "),
+      "utf8"
+    );
+
+    webpack(webpackConfig, (err, stats) => {
+      if (err || stats.hasErrors()) {
+        console.log("SOME ERROR HAPPENED");
+        process.exit(0);
+      }
+      exec("npm i");
+      console.log(
+        "DONE!. File created at ",
+        path.resolve(__dirname, "browser/telegram.js")
+      );
+    });
+  });
+});

+ 1 - 1
gramjs/Version.ts

@@ -1 +1 @@
-export const version = "2.14.0";
+export const version = "2.14.9";

+ 0 - 3
gramjs/extensions/CancelHelper.ts

@@ -1,3 +0,0 @@
-export class CancelHelper {
-    length = 1;
-}

+ 0 - 1
gramjs/extensions/index.ts

@@ -5,4 +5,3 @@ export { PromisedWebSockets } from "./PromisedWebSockets";
 export { PromisedNetSockets } from "./PromisedNetSockets";
 export { MessagePacker } from "./MessagePacker";
 export { AsyncQueue } from "./AsyncQueue";
-export { CancelHelper } from "./CancelHelper";

+ 14 - 25
gramjs/network/MTProtoSender.ts

@@ -14,7 +14,7 @@
 import { AuthKey } from "../crypto/AuthKey";
 import { MTProtoState } from "./MTProtoState";
 
-import { BinaryReader, CancelHelper, Logger } from "../extensions";
+import { BinaryReader, Logger } from "../extensions";
 import { MessagePacker } from "../extensions";
 import { GZIPPacked, MessageContainer, RPCResult, TLMessage } from "../tl/core";
 import { Api } from "../tl";
@@ -34,6 +34,11 @@ import { Connection, UpdateConnectionState } from "./";
 import type { TelegramClient } from "..";
 import { LogLevel } from "../extensions/Logger";
 import { Mutex } from "async-mutex";
+import {
+    pseudoCancellable,
+    CancellablePromise,
+    Cancellation,
+} from "real-cancellable-promise";
 
 interface DEFAULT_OPTIONS {
     logger: any;
@@ -101,11 +106,8 @@ export class MTProtoSender {
     _authenticated: boolean;
     private _securityChecks: boolean;
     private _connectMutex: Mutex;
-    private _recvCancelPromise: Promise<CancelHelper>;
-    private _recvCancelResolve?: (value: CancelHelper) => void;
-    private _sendCancelPromise: Promise<CancelHelper>;
-    private _sendCancelResolve?: (value: CancelHelper) => void;
     private _cancelSend: boolean;
+    cancellableRecvLoopPromise?: CancellablePromise<any>;
 
     /**
      * @param authKey
@@ -135,12 +137,6 @@ export class MTProtoSender {
 
         this._connectMutex = new Mutex();
 
-        this._recvCancelPromise = new Promise((resolve) => {
-            this._recvCancelResolve = resolve;
-        });
-        this._sendCancelPromise = new Promise((resolve) => {
-            this._sendCancelResolve = resolve;
-        });
         /**
          * whether we disconnected ourself or telegram did it.
          */
@@ -438,14 +434,7 @@ export class MTProtoSender {
 
     _cancelLoops() {
         this._cancelSend = true;
-        this._recvCancelResolve!(new CancelHelper());
-        this._sendCancelResolve!(new CancelHelper());
-        this._recvCancelPromise = new Promise((resolve) => {
-            this._recvCancelResolve = resolve;
-        });
-        this._sendCancelPromise = new Promise((resolve) => {
-            this._sendCancelResolve = resolve;
-        });
+        this.cancellableRecvLoopPromise!.cancel();
     }
 
     /**
@@ -526,14 +515,14 @@ export class MTProtoSender {
         while (this._userConnected && !this._reconnecting) {
             this._log.debug("Receiving items from the network...");
             try {
-                body = await Promise.race([
-                    this._connection!.recv(),
-                    this._recvCancelPromise,
-                ]);
-                if (body instanceof CancelHelper) {
+                this.cancellableRecvLoopPromise = pseudoCancellable(
+                    this._connection!.recv()
+                );
+                body = await this.cancellableRecvLoopPromise;
+            } catch (e: any) {
+                if (e instanceof Cancellation) {
                     return;
                 }
-            } catch (e: any) {
                 this._log.error(e);
                 this._log.warn("Connection closed while receiving data...");
                 this._startReconnecting(e);

+ 18 - 34
gramjs/network/connection/Connection.ts

@@ -1,5 +1,4 @@
 import {
-    CancelHelper,
     Logger,
     PromisedNetSockets,
     PromisedWebSockets,
@@ -8,6 +7,11 @@ import { AsyncQueue } from "../../extensions";
 import { AbridgedPacketCodec } from "./TCPAbridged";
 import { FullPacketCodec } from "./TCPFull";
 import { ProxyInterface } from "./TCPMTProxy";
+import {
+    CancellablePromise,
+    Cancellation,
+    pseudoCancellable,
+} from "real-cancellable-promise";
 
 interface ConnectionInterfaceParams {
     ip: string;
@@ -44,11 +48,8 @@ class Connection {
     protected _obfuscation: any;
     _sendArray: AsyncQueue;
     _recvArray: AsyncQueue;
-    private _recvCancelPromise: Promise<CancelHelper>;
-    private _recvCancelResolve?: (value: CancelHelper) => void;
-    private _sendCancelPromise: Promise<CancelHelper>;
-    private _sendCancelResolve?: (value: CancelHelper) => void;
-
+    recvCancel?: CancellablePromise<any>;
+    sendCancel?: CancellablePromise<any>;
     socket: PromisedNetSockets | PromisedWebSockets;
     public _testServers: boolean;
 
@@ -75,13 +76,6 @@ class Connection {
         this._recvArray = new AsyncQueue();
         this.socket = new socket(proxy);
         this._testServers = testServers;
-
-        this._recvCancelPromise = new Promise((resolve) => {
-            this._recvCancelResolve = resolve;
-        });
-        this._sendCancelPromise = new Promise((resolve) => {
-            this._sendCancelResolve = resolve;
-        });
     }
 
     async _connect() {
@@ -102,14 +96,8 @@ class Connection {
     }
 
     _cancelLoops() {
-        this._recvCancelResolve!(new CancelHelper());
-        this._sendCancelResolve!(new CancelHelper());
-        this._recvCancelPromise = new Promise((resolve) => {
-            this._recvCancelResolve = resolve;
-        });
-        this._sendCancelPromise = new Promise((resolve) => {
-            this._sendCancelResolve = resolve;
-        });
+        this.recvCancel!.cancel();
+        this.sendCancel!.cancel();
     }
 
     async disconnect() {
@@ -143,19 +131,17 @@ class Connection {
     async _sendLoop() {
         try {
             while (this._connected) {
-                const data = await Promise.race([
-                    this._sendCancelPromise,
-                    this._sendArray.pop(),
-                ]);
-                if (data instanceof CancelHelper) {
-                    break;
-                }
+                this.sendCancel = pseudoCancellable(this._sendArray.pop());
+                const data = await this.sendCancel;
                 if (!data) {
                     continue;
                 }
                 await this._send(data);
             }
         } catch (e: any) {
+            if (e instanceof Cancellation) {
+                return;
+            }
             this._log.info("The server closed the connection while sending");
             await this.disconnect();
         }
@@ -165,14 +151,12 @@ class Connection {
         let data;
         while (this._connected) {
             try {
-                data = await Promise.race([
-                    this._recvCancelPromise,
-                    await this._recv(),
-                ]);
-                if (data instanceof CancelHelper) {
+                this.recvCancel = pseudoCancellable(this._recv());
+                data = await this.recvCancel;
+            } catch (e: any) {
+                if (e instanceof Cancellation) {
                     return;
                 }
-            } catch (e: any) {
                 this._log.info("The server closed the connection");
                 await this.disconnect();
                 if (!this._recvArray._queue.length) {

+ 13 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "telegram",
-  "version": "2.14.0",
+  "version": "2.14.9",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "telegram",
-      "version": "2.14.0",
+      "version": "2.14.9",
       "license": "MIT",
       "dependencies": {
         "@cryptography/aes": "^0.1.1",
@@ -18,6 +18,7 @@
         "node-localstorage": "^2.2.1",
         "pako": "^2.0.3",
         "path-browserify": "^1.0.1",
+        "real-cancellable-promise": "^1.1.1",
         "socks": "^2.6.2",
         "store2": "^2.13.0",
         "ts-custom-error": "^3.2.0",
@@ -6857,6 +6858,11 @@
         "util-deprecate": "~1.0.1"
       }
     },
+    "node_modules/real-cancellable-promise": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/real-cancellable-promise/-/real-cancellable-promise-1.1.1.tgz",
+      "integrity": "sha512-vxanUX4Aff5sRX6Rb1CSeCDWhO20L0hKQXWTLOYbtRo9WYFMjlhEBX0E75iz3+7ucrmFdPpDolwLC7L65P7hag=="
+    },
     "node_modules/rechoir": {
       "version": "0.7.1",
       "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
@@ -13937,6 +13943,11 @@
         "util-deprecate": "~1.0.1"
       }
     },
+    "real-cancellable-promise": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/real-cancellable-promise/-/real-cancellable-promise-1.1.1.tgz",
+      "integrity": "sha512-vxanUX4Aff5sRX6Rb1CSeCDWhO20L0hKQXWTLOYbtRo9WYFMjlhEBX0E75iz3+7ucrmFdPpDolwLC7L65P7hag=="
+    },
     "rechoir": {
       "version": "0.7.1",
       "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "telegram",
-  "version": "2.14.0",
+  "version": "2.14.9",
   "description": "NodeJS/Browser MTProto API Telegram client library,",
   "main": "index.js",
   "types": "index.d.ts",
@@ -61,6 +61,7 @@
     "mime": "^3.0.0",
     "pako": "^2.0.3",
     "path-browserify": "^1.0.1",
+    "real-cancellable-promise": "^1.1.1",
     "store2": "^2.13.0",
     "ts-custom-error": "^3.2.0",
     "websocket": "^1.0.34",

+ 1 - 0
tsconfig.json

@@ -13,6 +13,7 @@
     "moduleResolution": "node",
     "resolveJsonModule": true,
     "declaration": true,
+    "skipLibCheck": true,
     "outDir": "./dist"
   },
   "exclude": ["gramjs/tl/types-generator", "node_modules"],