Telegram-Mini-Apps / telegram-apps

Made from scratch TypeScript packages, examples and documentation you will surely need to start developing on Telegram Mini Apps.
https://docs.telegram-mini-apps.com/
MIT License
691 stars 191 forks source link

[Bug]: `@telegram-apps/init-data-node/web` does not support non-Node.js environment #552

Open alexkuang0 opened 3 days ago

alexkuang0 commented 3 days ago

Telegram Application

Telegram for macOS

Describe the Bug

@telegram-apps/init-data-node/web does not support non-Node.js environment as expected from the documentation.

The reason appears to be that it uses Buffer, a Node.js API in its code.

To Reproduce

I'm using Convex, which has a non-Node.js environment that appears to be supported by Deno.

Calling validate() from the non-Node.js environment yields an error of [ReferenceError: Buffer is not defined].

Here's the sample code:

import { validate } from "@telegram-apps/init-data-node/web";
import { v } from "convex/values";
import { action } from "./_generated/server";

export const verify = action({
  args: {
    initData: v.string(),
  },
  handler: async (_ctx, { initData }) => {
    try {
      await validate(initData, process.env.TELEGRAM_BOT_TOKEN!);
      return { success: true };
    } catch (e) {
      console.log(e);
      return { success: false };
    }
  },
});

Expected Behavior

Expect the sample code to work without adding "use node"; at the beginning of the file, which will opt into using Node.js environment.

mehrab-wj commented 1 day ago

I'm facing the same issue while using Cloudflare workers.

Looks like Buffer is directly related to NodeJS, so it's not available in non-nodejs environments like CF Workers, meanwhile in the Web Crypto API, the crypto.subtle.sign returns Promise that resolves to an ArrayBuffer.

mehrab-wj commented 1 day ago

For the past three hours, I have been trying to recreate the HMAC function, which works and generates the same results as the Node.js crypto library. Here is the result.

Dear devs can use the function below before the patch to this package gets merged 😉

export async function createHmacHash(
    data: string,
    key: string
): Promise<string> {
    const encoder = new TextEncoder();

    // Convert hex key to bytes if it's a hex string
    const keyBytes = key.length === 64 ? 
        new Uint8Array(key.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))) :
        encoder.encode(key);

    // Import the key using bytes
    const cryptoKey = await crypto.subtle.importKey(
        "raw",
        keyBytes,
        { name: "HMAC", hash: "SHA-256" },
        false,
        ["sign"]
    );

    // Sign the data
    const signature = await crypto.subtle.sign(
        "HMAC",
        cryptoKey,
        encoder.encode(data)
    );

    // Convert the signature to hex
    return Array.from(new Uint8Array(signature))
        .map((byte) => byte.toString(16).padStart(2, "0"))
        .join("");
}