@lacrous/omnicloud 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1604,6 +1604,23 @@ declare class TelegramStorageProvider implements StorageProvider {
1604
1604
  }
1605
1605
 
1606
1606
  declare const TELEGRAM_PROVIDER = "telegram";
1607
+ interface QrLoginToken {
1608
+ /** The `tg://login?token=…` link to render as a QR code. */
1609
+ url: string;
1610
+ /** Unix time in milliseconds when this token stops being valid. */
1611
+ expiresAt: number;
1612
+ }
1613
+ type QrStatus = {
1614
+ status: "waiting";
1615
+ token: QrLoginToken;
1616
+ } | {
1617
+ status: "approved";
1618
+ user: UserRecord;
1619
+ } | {
1620
+ status: "password_required";
1621
+ } | {
1622
+ status: "expired";
1623
+ };
1607
1624
  type VerifyCodeResult = {
1608
1625
  status: "ok";
1609
1626
  user: UserRecord;
@@ -1620,6 +1637,7 @@ declare class TelegramConnectionService {
1620
1637
  private readonly clientManager;
1621
1638
  private readonly credentials;
1622
1639
  private flows;
1640
+ private qrFlows;
1623
1641
  constructor(repos: Repos, clientManager: TelegramClientManager, credentials: TelegramCredentials);
1624
1642
  /** Sends the Telegram confirmation code for the phone number. */
1625
1643
  startLogin(phone: string): Promise<void>;
@@ -1627,6 +1645,35 @@ declare class TelegramConnectionService {
1627
1645
  verifyCode(phone: string, code: string): Promise<VerifyCodeResult>;
1628
1646
  /** Completes sign-in for accounts with two-factor authentication enabled. */
1629
1647
  verifyPassword(phone: string, password: string): Promise<VerifyCodeResult>;
1648
+ /**
1649
+ * Starts a QR sign-in. Returns a flow id and the first login token. The phone
1650
+ * approves the token in the Telegram app; `pollQrLogin` reports the result.
1651
+ */
1652
+ startQrLogin(): Promise<{
1653
+ flowId: string;
1654
+ token: QrLoginToken;
1655
+ }>;
1656
+ /**
1657
+ * Reports where a QR sign-in stands. While waiting, returns a fresh token
1658
+ * (Telegram rotates them every few seconds). On approval, completes the login.
1659
+ */
1660
+ pollQrLogin(flowId: string): Promise<QrStatus>;
1661
+ /** Completes a QR sign-in for an account with two-factor authentication. */
1662
+ submitQrPassword(flowId: string, password: string): Promise<{
1663
+ status: "approved";
1664
+ user: UserRecord;
1665
+ }>;
1666
+ private exportQrToken;
1667
+ /** Waits for the phone to approve the token, then marks the flow approved. */
1668
+ private watchQrApproval;
1669
+ /**
1670
+ * Asks Telegram whether the token was approved. Approval is observed by
1671
+ * `ExportLoginToken` returning `LoginTokenSuccess` (or a DC migration).
1672
+ */
1673
+ private probeQrApproval;
1674
+ private completeQrLogin;
1675
+ private requireQrFlow;
1676
+ private sweepExpiredQrFlows;
1630
1677
  /** Aborts a pending login flow (disconnects the client). */
1631
1678
  cancelLogin(phone: string): Promise<void>;
1632
1679
  /**
@@ -1637,6 +1684,12 @@ declare class TelegramConnectionService {
1637
1684
  ensureStorageForClient(userId: string, client: TelegramClient): Promise<StorageRecord>;
1638
1685
  private requireFlow;
1639
1686
  private finalizeLogin;
1687
+ /**
1688
+ * Saves an authorized client's session, upserts the user and creates storage.
1689
+ * `phone` is the number the user typed; QR sign-in passes null and relies on
1690
+ * the number Telegram reports.
1691
+ */
1692
+ private finalizeConnectedClient;
1640
1693
  private sweepExpiredFlows;
1641
1694
  private disconnectQuietly;
1642
1695
  }
package/dist/index.d.ts CHANGED
@@ -1604,6 +1604,23 @@ declare class TelegramStorageProvider implements StorageProvider {
1604
1604
  }
1605
1605
 
1606
1606
  declare const TELEGRAM_PROVIDER = "telegram";
1607
+ interface QrLoginToken {
1608
+ /** The `tg://login?token=…` link to render as a QR code. */
1609
+ url: string;
1610
+ /** Unix time in milliseconds when this token stops being valid. */
1611
+ expiresAt: number;
1612
+ }
1613
+ type QrStatus = {
1614
+ status: "waiting";
1615
+ token: QrLoginToken;
1616
+ } | {
1617
+ status: "approved";
1618
+ user: UserRecord;
1619
+ } | {
1620
+ status: "password_required";
1621
+ } | {
1622
+ status: "expired";
1623
+ };
1607
1624
  type VerifyCodeResult = {
1608
1625
  status: "ok";
1609
1626
  user: UserRecord;
@@ -1620,6 +1637,7 @@ declare class TelegramConnectionService {
1620
1637
  private readonly clientManager;
1621
1638
  private readonly credentials;
1622
1639
  private flows;
1640
+ private qrFlows;
1623
1641
  constructor(repos: Repos, clientManager: TelegramClientManager, credentials: TelegramCredentials);
1624
1642
  /** Sends the Telegram confirmation code for the phone number. */
1625
1643
  startLogin(phone: string): Promise<void>;
@@ -1627,6 +1645,35 @@ declare class TelegramConnectionService {
1627
1645
  verifyCode(phone: string, code: string): Promise<VerifyCodeResult>;
1628
1646
  /** Completes sign-in for accounts with two-factor authentication enabled. */
1629
1647
  verifyPassword(phone: string, password: string): Promise<VerifyCodeResult>;
1648
+ /**
1649
+ * Starts a QR sign-in. Returns a flow id and the first login token. The phone
1650
+ * approves the token in the Telegram app; `pollQrLogin` reports the result.
1651
+ */
1652
+ startQrLogin(): Promise<{
1653
+ flowId: string;
1654
+ token: QrLoginToken;
1655
+ }>;
1656
+ /**
1657
+ * Reports where a QR sign-in stands. While waiting, returns a fresh token
1658
+ * (Telegram rotates them every few seconds). On approval, completes the login.
1659
+ */
1660
+ pollQrLogin(flowId: string): Promise<QrStatus>;
1661
+ /** Completes a QR sign-in for an account with two-factor authentication. */
1662
+ submitQrPassword(flowId: string, password: string): Promise<{
1663
+ status: "approved";
1664
+ user: UserRecord;
1665
+ }>;
1666
+ private exportQrToken;
1667
+ /** Waits for the phone to approve the token, then marks the flow approved. */
1668
+ private watchQrApproval;
1669
+ /**
1670
+ * Asks Telegram whether the token was approved. Approval is observed by
1671
+ * `ExportLoginToken` returning `LoginTokenSuccess` (or a DC migration).
1672
+ */
1673
+ private probeQrApproval;
1674
+ private completeQrLogin;
1675
+ private requireQrFlow;
1676
+ private sweepExpiredQrFlows;
1630
1677
  /** Aborts a pending login flow (disconnects the client). */
1631
1678
  cancelLogin(phone: string): Promise<void>;
1632
1679
  /**
@@ -1637,6 +1684,12 @@ declare class TelegramConnectionService {
1637
1684
  ensureStorageForClient(userId: string, client: TelegramClient): Promise<StorageRecord>;
1638
1685
  private requireFlow;
1639
1686
  private finalizeLogin;
1687
+ /**
1688
+ * Saves an authorized client's session, upserts the user and creates storage.
1689
+ * `phone` is the number the user typed; QR sign-in passes null and relies on
1690
+ * the number Telegram reports.
1691
+ */
1692
+ private finalizeConnectedClient;
1640
1693
  private sweepExpiredFlows;
1641
1694
  private disconnectQuietly;
1642
1695
  }
package/dist/index.js CHANGED
@@ -3095,10 +3095,13 @@ var TelegramStorageProvider = class {
3095
3095
  };
3096
3096
 
3097
3097
  // ../telegram/src/connection.ts
3098
+ import { randomUUID } from "crypto";
3098
3099
  import { Api as Api3 } from "telegram";
3099
3100
  import { computeCheck } from "telegram/Password.js";
3100
3101
  var TELEGRAM_PROVIDER = "telegram";
3101
3102
  var LOGIN_FLOW_TTL_MS = 5 * 60 * 1e3;
3103
+ var QR_FLOW_TTL_MS = 5 * 60 * 1e3;
3104
+ var QR_APPROVAL_POLL_MS = 2e3;
3102
3105
  var TelegramConnectionService = class {
3103
3106
  constructor(repos, clientManager, credentials) {
3104
3107
  this.repos = repos;
@@ -3109,6 +3112,7 @@ var TelegramConnectionService = class {
3109
3112
  clientManager;
3110
3113
  credentials;
3111
3114
  flows = /* @__PURE__ */ new Map();
3115
+ qrFlows = /* @__PURE__ */ new Map();
3112
3116
  /** Sends the Telegram confirmation code for the phone number. */
3113
3117
  async startLogin(phone) {
3114
3118
  this.sweepExpiredFlows();
@@ -3157,6 +3161,167 @@ var TelegramConnectionService = class {
3157
3161
  }
3158
3162
  return this.finalizeLogin(flow);
3159
3163
  }
3164
+ /**
3165
+ * Starts a QR sign-in. Returns a flow id and the first login token. The phone
3166
+ * approves the token in the Telegram app; `pollQrLogin` reports the result.
3167
+ */
3168
+ async startQrLogin() {
3169
+ this.sweepExpiredQrFlows();
3170
+ const client = newTelegramClient(this.credentials);
3171
+ try {
3172
+ await client.connect();
3173
+ const flow = {
3174
+ client,
3175
+ createdAt: Date.now(),
3176
+ approved: false,
3177
+ needsPassword: false,
3178
+ failure: null
3179
+ };
3180
+ const flowId = randomUUID();
3181
+ const token = await this.exportQrToken(client);
3182
+ this.qrFlows.set(flowId, flow);
3183
+ this.watchQrApproval(flowId, flow);
3184
+ return { flowId, token };
3185
+ } catch (error) {
3186
+ await this.disconnectQuietly(client);
3187
+ throw mapTelegramError(error, "Could not start Telegram QR sign-in");
3188
+ }
3189
+ }
3190
+ /**
3191
+ * Reports where a QR sign-in stands. While waiting, returns a fresh token
3192
+ * (Telegram rotates them every few seconds). On approval, completes the login.
3193
+ */
3194
+ async pollQrLogin(flowId) {
3195
+ const flow = this.requireQrFlow(flowId);
3196
+ if (flow.failure) {
3197
+ this.qrFlows.delete(flowId);
3198
+ await this.disconnectQuietly(flow.client);
3199
+ throw new TelegramConnectionError(flow.failure);
3200
+ }
3201
+ if (flow.needsPassword) return { status: "password_required" };
3202
+ if (!flow.approved) {
3203
+ const token = await this.exportQrToken(flow.client).catch((error) => {
3204
+ this.qrFlows.delete(flowId);
3205
+ void this.disconnectQuietly(flow.client);
3206
+ throw mapTelegramError(error, "Could not refresh the Telegram QR code");
3207
+ });
3208
+ return { status: "waiting", token };
3209
+ }
3210
+ return this.completeQrLogin(flowId, flow);
3211
+ }
3212
+ /** Completes a QR sign-in for an account with two-factor authentication. */
3213
+ async submitQrPassword(flowId, password) {
3214
+ const flow = this.requireQrFlow(flowId);
3215
+ try {
3216
+ const passwordInfo = await flow.client.invoke(new Api3.account.GetPassword());
3217
+ const check = await computeCheck(passwordInfo, password);
3218
+ await flow.client.invoke(new Api3.auth.CheckPassword({ password: check }));
3219
+ } catch (error) {
3220
+ throw mapTelegramError(error, "Telegram sign-in failed");
3221
+ }
3222
+ return this.completeQrLogin(flowId, flow);
3223
+ }
3224
+ async exportQrToken(client) {
3225
+ const result = await client.invoke(
3226
+ new Api3.auth.ExportLoginToken({
3227
+ apiId: this.credentials.apiId,
3228
+ apiHash: this.credentials.apiHash,
3229
+ exceptIds: []
3230
+ })
3231
+ );
3232
+ if (!(result instanceof Api3.auth.LoginToken)) {
3233
+ throw new TelegramConnectionError("Telegram did not issue a QR login token");
3234
+ }
3235
+ const tokenBase64 = Buffer.from(result.token).toString("base64url");
3236
+ return {
3237
+ url: `tg://login?token=${tokenBase64}`,
3238
+ expiresAt: result.expires * 1e3
3239
+ };
3240
+ }
3241
+ /** Waits for the phone to approve the token, then marks the flow approved. */
3242
+ watchQrApproval(flowId, flow) {
3243
+ const timer = setInterval(() => {
3244
+ if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {
3245
+ clearInterval(timer);
3246
+ this.qrFlows.delete(flowId);
3247
+ void this.disconnectQuietly(flow.client);
3248
+ return;
3249
+ }
3250
+ if (flow.approved || flow.needsPassword || flow.failure) {
3251
+ clearInterval(timer);
3252
+ return;
3253
+ }
3254
+ this.probeQrApproval(flow).then(
3255
+ (state) => {
3256
+ if (state === "approved") {
3257
+ flow.approved = true;
3258
+ clearInterval(timer);
3259
+ } else if (state === "password") {
3260
+ flow.needsPassword = true;
3261
+ clearInterval(timer);
3262
+ }
3263
+ },
3264
+ (error) => {
3265
+ flow.failure = mapTelegramError(error, "Telegram QR sign-in failed").message;
3266
+ clearInterval(timer);
3267
+ }
3268
+ );
3269
+ }, QR_APPROVAL_POLL_MS);
3270
+ timer.unref();
3271
+ }
3272
+ /**
3273
+ * Asks Telegram whether the token was approved. Approval is observed by
3274
+ * `ExportLoginToken` returning `LoginTokenSuccess` (or a DC migration).
3275
+ */
3276
+ async probeQrApproval(flow) {
3277
+ try {
3278
+ const result = await flow.client.invoke(
3279
+ new Api3.auth.ExportLoginToken({
3280
+ apiId: this.credentials.apiId,
3281
+ apiHash: this.credentials.apiHash,
3282
+ exceptIds: []
3283
+ })
3284
+ );
3285
+ if (result instanceof Api3.auth.LoginTokenSuccess) return "approved";
3286
+ if (result instanceof Api3.auth.LoginTokenMigrateTo) {
3287
+ await flow.client._switchDC(result.dcId);
3288
+ const migrated = await flow.client.invoke(
3289
+ new Api3.auth.ImportLoginToken({ token: result.token })
3290
+ );
3291
+ if (migrated instanceof Api3.auth.LoginTokenSuccess) return "approved";
3292
+ }
3293
+ return "pending";
3294
+ } catch (error) {
3295
+ if (isPasswordRequiredError(error)) return "password";
3296
+ throw error;
3297
+ }
3298
+ }
3299
+ async completeQrLogin(flowId, flow) {
3300
+ this.qrFlows.delete(flowId);
3301
+ const user = await this.finalizeConnectedClient(flow.client, null);
3302
+ return { status: "approved", user };
3303
+ }
3304
+ requireQrFlow(flowId) {
3305
+ const flow = this.qrFlows.get(flowId);
3306
+ if (!flow) {
3307
+ throw new NotFoundError("This Telegram QR sign-in has ended. Start again.");
3308
+ }
3309
+ if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {
3310
+ this.qrFlows.delete(flowId);
3311
+ void this.disconnectQuietly(flow.client);
3312
+ throw new ValidationError("The QR sign-in expired. Start again.");
3313
+ }
3314
+ return flow;
3315
+ }
3316
+ sweepExpiredQrFlows() {
3317
+ const now = Date.now();
3318
+ for (const [flowId, flow] of this.qrFlows) {
3319
+ if (now - flow.createdAt > QR_FLOW_TTL_MS) {
3320
+ this.qrFlows.delete(flowId);
3321
+ void this.disconnectQuietly(flow.client);
3322
+ }
3323
+ }
3324
+ }
3160
3325
  /** Aborts a pending login flow (disconnects the client). */
3161
3326
  async cancelLogin(phone) {
3162
3327
  const flow = this.flows.get(phone);
@@ -3218,11 +3383,20 @@ var TelegramConnectionService = class {
3218
3383
  }
3219
3384
  async finalizeLogin(flow) {
3220
3385
  this.flows.delete(flow.phone);
3386
+ const user = await this.finalizeConnectedClient(flow.client, flow.phone);
3387
+ return { status: "ok", user };
3388
+ }
3389
+ /**
3390
+ * Saves an authorized client's session, upserts the user and creates storage.
3391
+ * `phone` is the number the user typed; QR sign-in passes null and relies on
3392
+ * the number Telegram reports.
3393
+ */
3394
+ async finalizeConnectedClient(client, phone) {
3221
3395
  let stringSession;
3222
3396
  let user;
3223
3397
  try {
3224
- stringSession = flow.client.session.save();
3225
- const me = await flow.client.getMe();
3398
+ stringSession = client.session.save();
3399
+ const me = await client.getMe();
3226
3400
  if (!(me instanceof Api3.User)) {
3227
3401
  throw new Error("Telegram did not return the authenticated user");
3228
3402
  }
@@ -3231,16 +3405,16 @@ var TelegramConnectionService = class {
3231
3405
  username: me.username ?? null,
3232
3406
  firstName: me.firstName ?? null,
3233
3407
  lastName: me.lastName ?? null,
3234
- phone: me.phone ?? flow.phone
3408
+ phone: me.phone ?? phone ?? ""
3235
3409
  });
3236
3410
  } catch (error) {
3237
- await this.disconnectQuietly(flow.client);
3411
+ await this.disconnectQuietly(client);
3238
3412
  throw mapTelegramError(error, "Could not complete the Telegram connection");
3239
3413
  }
3240
3414
  await this.repos.sessions.save(user.id, stringSession);
3241
- await this.ensureStorageForClient(user.id, flow.client);
3242
- this.clientManager.cacheClient(user.id, flow.client);
3243
- return { status: "ok", user };
3415
+ await this.ensureStorageForClient(user.id, client);
3416
+ this.clientManager.cacheClient(user.id, client);
3417
+ return user;
3244
3418
  }
3245
3419
  sweepExpiredFlows() {
3246
3420
  const now = Date.now();