Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial Telegram Stamper package #399

Merged
merged 39 commits into from
Dec 2, 2024
Merged

Add initial Telegram Stamper package #399

merged 39 commits into from
Dec 2, 2024

Conversation

zkharit
Copy link
Contributor

@zkharit zkharit commented Oct 17, 2024

Summary & Motivation

Add compatibility for storing Turnkey api keys in Telegram Cloud Storage

How I Tested These Changes

Did you add a changeset?

If updating one of our packages, you'll likely need to add a changeset to your PR. To do so, run pnpm changeset. pnpm changeset will generate a file where you should write a human friendly message about the changes. Note how this (example) includes the package name (should be auto added by the command) along with the type of semver change (major.minor.patch) (which you should set).

These changes will be used at release time to determine what packages to publish and how to bump their version. For more context see this comment.

Copy link

codesandbox-ci bot commented Oct 17, 2024

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@zkharit zkharit force-pushed the zane/telegram-stamper branch from 3c9c572 to ce1b7e4 Compare October 17, 2024 15:46
Copy link

socket-security bot commented Oct 17, 2024

New and removed dependencies detected. Learn more about Socket for GitHub ↗︎

Package New capabilities Transitives Size Publisher
npm/[email protected] Transitive: environment, eval, filesystem, network, shell, unsafe +107 9.92 MB simenb
npm/[email protected] None 0 20.6 kB clarkbw

View full report↗︎

@@ -0,0 +1,17 @@
import JSDOMEnvironment from "jest-environment-jsdom";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put this under __tests__ just to make its purpose extra clear :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I had it there, but jest thought it was a test file and yelled at me for it not having any tests inside of it.

@zkharit zkharit marked this pull request as ready for review October 23, 2024 19:51
@zkharit zkharit force-pushed the zane/telegram-stamper branch from 822cb69 to 240782a Compare October 23, 2024 19:54
packages/telegram-stamper/LICENSE Outdated Show resolved Hide resolved
packages/telegram-stamper/CHANGELOG.md Outdated Show resolved Hide resolved
Comment on lines 18 to 29
// create a new telegram stamper
const stamper = new TelegramStamper({
apiPublicKey: "...",
apiPrivateKey: "...",
});

// initialize the telegram stamper, this stores the api key credentials in Telegram Cloud Storage for the first time
try {
stamper.init();
} catch (err) {
throw new Error(`Failed initializing Telegram Stamper: ${err}`)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be good to simplify this interface to:

const stamper = new TelegramStamper({}); // defaults to creating API key + storing it in cloudstorage

And if you need to provide an already existing API key:

const stamper = new TelegramStamper({
  apiPublicKey: "...",
  apiPrivateKey: "...",
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats not actually whats happening here with an empty constructor. The empty constructor is for the case where they have stored a non-expiring api key in cloud storage, and want to use that. The stamper will get the api keys that are currently stored there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having said that, maybe its not super likely thats how the tgram stamper will be used? Maybe this should just be removed overall? (from the readme)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep yep, I get that. But it could create an API key if nothing's in cloudStorage right? From a dev's POV the construction becomes the same regardless of whether a user has a key or not.

Currently devs will be forced to branch:

if (hasApiKeyInCloudStorage) {
   const stamper = new TelegramStamper({});
} else {
   const stamper = new TelegramStamper({
     apiPublicKey: "...", // also need to worry about how to generate it
     apiPrivateKey: "...",
   }
}

Whereas if we had this behavior (generate key when storage is empty), the init simplifies to:

const stamper = new TelegramStamper({});

I guess this is only important if creating and persisting a new key is a common pattern. If the stamper is almost always initiated with an existing key from somewhere else, not worth worrying about.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, the tgram stamper does not create any api keys for users, they need to be passed to the stamper

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOp I wrote that comment before I reloaded and saw your response, but yeah Im not exactly sure it makes sense for the stamper to actually create the api keys, because those api keys will have to be added to the users account somehow meaning that the tgram stamper already needs a valid api key, or something needs to pass the tgram stamper a valid key

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I'm okay with that. Let's not bundle API key creation in there and always expect public/private key. As you build out the TG mini-app I think patterns will naturally emerge anyway. I suspect you'll need to create a stamper from a bundle (coming from oauth or email auth). That might be a convenience function we want to provide (TelegramStamper.fromBundle(...) would decrypt the bundle and store it in cloudstorage in one go)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think thatll be a common pattern for sure, that sounds like a nice convenience for sure!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by the way this brings up a tangential point: it's probably best not to merge this PR until the TG mini-app is built: that way you can get a "feel" for the interface you're building with this stamper, hammer out the bug, etc. The iteration loop I'd recommend is:

  1. TG stamper PR opened (what you have here)
  2. TG mini app repo or branch somewhere which imports the codesandbox version of this package
  3. Try your mini app in TG, look for bugs; look at the code of the mini-app, look for awkwardness. Go back to step 1 and edit to solve for bugs and awkwardness!

and when we're convinced this stamper is a fit for the mini-app: merge PR, publish to NPM, and import the final package in the mini app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫡

packages/telegram-stamper/README.md Outdated Show resolved Hide resolved

declare global {
interface Window {
Telegram: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we be more specific than "any" here? Let's bring this package: https://www.npmjs.com/package/telegram-webapps?

It has all the types we need for CloudStorage:
https://github.com/DavisDmitry/telegram-webapps/blob/d935b6aec22d9361982ba54667eb56faf76b474a/src/index.d.ts#L970

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this isnt an "official" Telegram package, and I've been going back and forth about including it. Telegram unfortunately only officially offers <script src=telegram-web-app.js />. Its really behind, it terms of how modern apps are developed, and other developers have created much better abstractions like the one you linked. However now were depending on random devs to keep it up to date rather than using the official Telegram source. Given that do you have a strong opinion here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what I linked wouldn't be included in the final build I don't think, it's only type definitions (typescript might export them if people import our ts code, but the point remains: it won't be new functionality; just types to guide our code / tests)

Copy link
Contributor Author

@zkharit zkharit Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, that makes sense. Ill look into that

packages/telegram-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-stamper/src/index.ts Outdated Show resolved Hide resolved
@r-n-o
Copy link
Contributor

r-n-o commented Oct 24, 2024

Don't forget to add this new package to https://github.com/tkhq/sdk/blob/main/.codesandbox/ci.json, this will let you build an importable version of it right from this branch so you can iterate and get feedback quickly for integration into a TG mini-app.

@zkharit
Copy link
Contributor Author

zkharit commented Oct 24, 2024

didnt know about that ^ awesome!!

Copy link
Contributor

@r-n-o r-n-o left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

Looking real real good!

packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-stamper/README.md Outdated Show resolved Hide resolved
.changeset/itchy-balloons-call.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved

[![npm](https://img.shields.io/npm/v/@turnkey/telegram-cloud-storage-stamper?color=%234C48FF)](https://www.npmjs.com/package/@turnkey/telegram-cloud-storage-stamper)

This package contains functions to store a Turnkey API public/private key within [Telegram Cloud Storage](https://core.telegram.org/bots/webapps#cloudstorage). This package also handles stamping a Turnkey request with that API key. It is meant to be used with [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to mention @turnkey/sdk-browser specifically here, it would be optimal to have some integration guide type docs to flesh that out.

Technically, if this implements the general stamper interface, it could be used with the lowest level @turnkey/http as well — we could mention that but generally recommend usage with @turnkey/sdk-browser. This also might make sense to... add to @turnkey/sdk-react as well?

In any case, a docs update to https://docs.turnkey.com/api-overview/stamps#stampers at the very least seems relevant

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the flip side, I can see the argument that we shouldn't because telegram is a pretty separate path and thus shouldn't be included with the other more vanilla browser use cases

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I specifically removed a reference to @turnkey/http here after our convo during standup yesterday 😅 to push users toward our higher level libraries. I do agree that extra doc will be helpful here, and I'm planning to add some :)

#### Argument Usage

The `.create()` and `.setSigningKey()` functions take one of the following 4 sets of arguments:
- No arguments: Use an API key at the default location within Telegram Cloud Storage `TURNKEY_API_KEY` and set that as the signing key
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the default location mean here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just before this section I tried to explain the idea of how storing is done in tg cloud storage and wrote "The Telegram Cloud Storage Stamper will, by default, store the API key used for signing in Telegram Cloud Storage under the key TURNKEY_API_KEY. A Cloud Storage "key" is the index under which a value is stored in Telegram Cloud Storage." Is there something more you think I should clarify here?

packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
Use an existing key that has been previously stored in Telegram Cloud Storage at the default API key location key location

```ts
import { TelegramCloudStorageStamper } from "@turnkey/telegram-cloud-storage-stamper";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts on calling this just @turnkey/telegram-stamper? Would that be pigeonholing too much? i.e. are there other types of telegram stampers that we could foresee having some day in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had this discussion with @r-n-o earlier. It was originally telegram-stamper. There are talks of including Login with Telegram outside of a telegram mini app context. If we want to create a specialized stamper for that, this one will not work because there will not be access to TG cloud storage in a typical web app context so it is possible there is a different telegram stamper that could be developed. But its possible wed just advise users to utilize one our existing stampers if they are not in a telegram context

packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
// the key used to index Telegram Cloud Storage
const telegramCloudStorageKey = "telegramCloudStorageKey";

// insert the API key in Telegram Cloud Storage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the email auth type use case I imagine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really a convenience method for developers, email auth is a perfectly good use case example, but really tried to be as flexible as possible for developers so they can have the tools they need

Copy link
Contributor

@r-n-o r-n-o left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, one more round of comments on this! We're getting closer and closer and closer I swear!

packages/telegram-cloud-storage-stamper/CHANGELOG.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/README.md Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
packages/telegram-cloud-storage-stamper/src/index.ts Outdated Show resolved Hide resolved
@zkharit zkharit force-pushed the zane/telegram-stamper branch from 385d7d1 to 648436c Compare December 2, 2024 14:16
Comment on lines 137 to 149
async getAPIKey(key: string = DEFAULT_TURNKEY_CLOUD_STORAGE_KEY) {
try {
const apiKey = await this.getItem(key);

if (!apiKey) {
return { apiPublicKey: "", apiPrivateKey: "" };
}

return this.parseAPIKey(apiKey as string);
} catch {
return { apiPublicKey: "", apiPrivateKey: "" };
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's be clearer to return null when the API key can't be found (instead of a truthy object with empty string props). Also, let's add type annotations here? (-> CloudStorageAPIKey | null)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed this to return null in the error case and added the type annotation

Comment on lines 188 to 191
isApiKey(apiKey: CloudStorageAPIKey) {
return (
typeof apiKey.apiPublicKey === "string" &&
typeof apiKey.apiPrivateKey === "string"
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The danger is that an empty string is still a string. Is it okay to consider an object with 2 empty keys an "ApiKey"?

This would make for a good unit test target by the way (assert true/false against a bunch of values)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage of this function comes from the need to determine if a value obtained and parsed from tg cloud storage is of type CloudStorageAPIKey, not necessarily that it is a "valid" api key. I changed the name to be isCloudStorageAPIKey and added a comment to be more clear

);
}

async getItem(key: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be useful to add type annotations to the return value? Or can Typescript infer it? (-> Promise<string>?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the type annotations, typescript could not infer it previously

@zkharit zkharit force-pushed the zane/telegram-stamper branch from a9ab5ef to 5be3dfc Compare December 2, 2024 19:07
Copy link
Contributor

@r-n-o r-n-o left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@zkharit zkharit merged commit 9e99433 into main Dec 2, 2024
5 checks passed
@zkharit zkharit deleted the zane/telegram-stamper branch December 2, 2024 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants