@lacrous/omnicloud 0.2.7 → 0.2.8

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.cjs CHANGED
@@ -3158,10 +3158,13 @@ var TelegramStorageProvider = class {
3158
3158
  };
3159
3159
 
3160
3160
  // ../telegram/src/connection.ts
3161
+ var import_node_crypto6 = require("crypto");
3161
3162
  var import_telegram3 = require("telegram");
3162
3163
  var import_Password = require("telegram/Password.js");
3163
3164
  var TELEGRAM_PROVIDER = "telegram";
3164
3165
  var LOGIN_FLOW_TTL_MS = 5 * 60 * 1e3;
3166
+ var QR_FLOW_TTL_MS = 5 * 60 * 1e3;
3167
+ var QR_APPROVAL_POLL_MS = 2e3;
3165
3168
  var TelegramConnectionService = class {
3166
3169
  constructor(repos, clientManager, credentials) {
3167
3170
  this.repos = repos;
@@ -3172,6 +3175,7 @@ var TelegramConnectionService = class {
3172
3175
  clientManager;
3173
3176
  credentials;
3174
3177
  flows = /* @__PURE__ */ new Map();
3178
+ qrFlows = /* @__PURE__ */ new Map();
3175
3179
  /** Sends the Telegram confirmation code for the phone number. */
3176
3180
  async startLogin(phone) {
3177
3181
  this.sweepExpiredFlows();
@@ -3220,6 +3224,167 @@ var TelegramConnectionService = class {
3220
3224
  }
3221
3225
  return this.finalizeLogin(flow);
3222
3226
  }
3227
+ /**
3228
+ * Starts a QR sign-in. Returns a flow id and the first login token. The phone
3229
+ * approves the token in the Telegram app; `pollQrLogin` reports the result.
3230
+ */
3231
+ async startQrLogin() {
3232
+ this.sweepExpiredQrFlows();
3233
+ const client = newTelegramClient(this.credentials);
3234
+ try {
3235
+ await client.connect();
3236
+ const flow = {
3237
+ client,
3238
+ createdAt: Date.now(),
3239
+ approved: false,
3240
+ needsPassword: false,
3241
+ failure: null
3242
+ };
3243
+ const flowId = (0, import_node_crypto6.randomUUID)();
3244
+ const token = await this.exportQrToken(client);
3245
+ this.qrFlows.set(flowId, flow);
3246
+ this.watchQrApproval(flowId, flow);
3247
+ return { flowId, token };
3248
+ } catch (error) {
3249
+ await this.disconnectQuietly(client);
3250
+ throw mapTelegramError(error, "Could not start Telegram QR sign-in");
3251
+ }
3252
+ }
3253
+ /**
3254
+ * Reports where a QR sign-in stands. While waiting, returns a fresh token
3255
+ * (Telegram rotates them every few seconds). On approval, completes the login.
3256
+ */
3257
+ async pollQrLogin(flowId) {
3258
+ const flow = this.requireQrFlow(flowId);
3259
+ if (flow.failure) {
3260
+ this.qrFlows.delete(flowId);
3261
+ await this.disconnectQuietly(flow.client);
3262
+ throw new TelegramConnectionError(flow.failure);
3263
+ }
3264
+ if (flow.needsPassword) return { status: "password_required" };
3265
+ if (!flow.approved) {
3266
+ const token = await this.exportQrToken(flow.client).catch((error) => {
3267
+ this.qrFlows.delete(flowId);
3268
+ void this.disconnectQuietly(flow.client);
3269
+ throw mapTelegramError(error, "Could not refresh the Telegram QR code");
3270
+ });
3271
+ return { status: "waiting", token };
3272
+ }
3273
+ return this.completeQrLogin(flowId, flow);
3274
+ }
3275
+ /** Completes a QR sign-in for an account with two-factor authentication. */
3276
+ async submitQrPassword(flowId, password) {
3277
+ const flow = this.requireQrFlow(flowId);
3278
+ try {
3279
+ const passwordInfo = await flow.client.invoke(new import_telegram3.Api.account.GetPassword());
3280
+ const check = await (0, import_Password.computeCheck)(passwordInfo, password);
3281
+ await flow.client.invoke(new import_telegram3.Api.auth.CheckPassword({ password: check }));
3282
+ } catch (error) {
3283
+ throw mapTelegramError(error, "Telegram sign-in failed");
3284
+ }
3285
+ return this.completeQrLogin(flowId, flow);
3286
+ }
3287
+ async exportQrToken(client) {
3288
+ const result = await client.invoke(
3289
+ new import_telegram3.Api.auth.ExportLoginToken({
3290
+ apiId: this.credentials.apiId,
3291
+ apiHash: this.credentials.apiHash,
3292
+ exceptIds: []
3293
+ })
3294
+ );
3295
+ if (!(result instanceof import_telegram3.Api.auth.LoginToken)) {
3296
+ throw new TelegramConnectionError("Telegram did not issue a QR login token");
3297
+ }
3298
+ const tokenBase64 = Buffer.from(result.token).toString("base64url");
3299
+ return {
3300
+ url: `tg://login?token=${tokenBase64}`,
3301
+ expiresAt: result.expires * 1e3
3302
+ };
3303
+ }
3304
+ /** Waits for the phone to approve the token, then marks the flow approved. */
3305
+ watchQrApproval(flowId, flow) {
3306
+ const timer = setInterval(() => {
3307
+ if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {
3308
+ clearInterval(timer);
3309
+ this.qrFlows.delete(flowId);
3310
+ void this.disconnectQuietly(flow.client);
3311
+ return;
3312
+ }
3313
+ if (flow.approved || flow.needsPassword || flow.failure) {
3314
+ clearInterval(timer);
3315
+ return;
3316
+ }
3317
+ this.probeQrApproval(flow).then(
3318
+ (state) => {
3319
+ if (state === "approved") {
3320
+ flow.approved = true;
3321
+ clearInterval(timer);
3322
+ } else if (state === "password") {
3323
+ flow.needsPassword = true;
3324
+ clearInterval(timer);
3325
+ }
3326
+ },
3327
+ (error) => {
3328
+ flow.failure = mapTelegramError(error, "Telegram QR sign-in failed").message;
3329
+ clearInterval(timer);
3330
+ }
3331
+ );
3332
+ }, QR_APPROVAL_POLL_MS);
3333
+ timer.unref();
3334
+ }
3335
+ /**
3336
+ * Asks Telegram whether the token was approved. Approval is observed by
3337
+ * `ExportLoginToken` returning `LoginTokenSuccess` (or a DC migration).
3338
+ */
3339
+ async probeQrApproval(flow) {
3340
+ try {
3341
+ const result = await flow.client.invoke(
3342
+ new import_telegram3.Api.auth.ExportLoginToken({
3343
+ apiId: this.credentials.apiId,
3344
+ apiHash: this.credentials.apiHash,
3345
+ exceptIds: []
3346
+ })
3347
+ );
3348
+ if (result instanceof import_telegram3.Api.auth.LoginTokenSuccess) return "approved";
3349
+ if (result instanceof import_telegram3.Api.auth.LoginTokenMigrateTo) {
3350
+ await flow.client._switchDC(result.dcId);
3351
+ const migrated = await flow.client.invoke(
3352
+ new import_telegram3.Api.auth.ImportLoginToken({ token: result.token })
3353
+ );
3354
+ if (migrated instanceof import_telegram3.Api.auth.LoginTokenSuccess) return "approved";
3355
+ }
3356
+ return "pending";
3357
+ } catch (error) {
3358
+ if (isPasswordRequiredError(error)) return "password";
3359
+ throw error;
3360
+ }
3361
+ }
3362
+ async completeQrLogin(flowId, flow) {
3363
+ this.qrFlows.delete(flowId);
3364
+ const user = await this.finalizeConnectedClient(flow.client, null);
3365
+ return { status: "approved", user };
3366
+ }
3367
+ requireQrFlow(flowId) {
3368
+ const flow = this.qrFlows.get(flowId);
3369
+ if (!flow) {
3370
+ throw new NotFoundError("This Telegram QR sign-in has ended. Start again.");
3371
+ }
3372
+ if (Date.now() - flow.createdAt > QR_FLOW_TTL_MS) {
3373
+ this.qrFlows.delete(flowId);
3374
+ void this.disconnectQuietly(flow.client);
3375
+ throw new ValidationError("The QR sign-in expired. Start again.");
3376
+ }
3377
+ return flow;
3378
+ }
3379
+ sweepExpiredQrFlows() {
3380
+ const now = Date.now();
3381
+ for (const [flowId, flow] of this.qrFlows) {
3382
+ if (now - flow.createdAt > QR_FLOW_TTL_MS) {
3383
+ this.qrFlows.delete(flowId);
3384
+ void this.disconnectQuietly(flow.client);
3385
+ }
3386
+ }
3387
+ }
3223
3388
  /** Aborts a pending login flow (disconnects the client). */
3224
3389
  async cancelLogin(phone) {
3225
3390
  const flow = this.flows.get(phone);
@@ -3281,11 +3446,20 @@ var TelegramConnectionService = class {
3281
3446
  }
3282
3447
  async finalizeLogin(flow) {
3283
3448
  this.flows.delete(flow.phone);
3449
+ const user = await this.finalizeConnectedClient(flow.client, flow.phone);
3450
+ return { status: "ok", user };
3451
+ }
3452
+ /**
3453
+ * Saves an authorized client's session, upserts the user and creates storage.
3454
+ * `phone` is the number the user typed; QR sign-in passes null and relies on
3455
+ * the number Telegram reports.
3456
+ */
3457
+ async finalizeConnectedClient(client, phone) {
3284
3458
  let stringSession;
3285
3459
  let user;
3286
3460
  try {
3287
- stringSession = flow.client.session.save();
3288
- const me = await flow.client.getMe();
3461
+ stringSession = client.session.save();
3462
+ const me = await client.getMe();
3289
3463
  if (!(me instanceof import_telegram3.Api.User)) {
3290
3464
  throw new Error("Telegram did not return the authenticated user");
3291
3465
  }
@@ -3294,16 +3468,16 @@ var TelegramConnectionService = class {
3294
3468
  username: me.username ?? null,
3295
3469
  firstName: me.firstName ?? null,
3296
3470
  lastName: me.lastName ?? null,
3297
- phone: me.phone ?? flow.phone
3471
+ phone: me.phone ?? phone ?? ""
3298
3472
  });
3299
3473
  } catch (error) {
3300
- await this.disconnectQuietly(flow.client);
3474
+ await this.disconnectQuietly(client);
3301
3475
  throw mapTelegramError(error, "Could not complete the Telegram connection");
3302
3476
  }
3303
3477
  await this.repos.sessions.save(user.id, stringSession);
3304
- await this.ensureStorageForClient(user.id, flow.client);
3305
- this.clientManager.cacheClient(user.id, flow.client);
3306
- return { status: "ok", user };
3478
+ await this.ensureStorageForClient(user.id, client);
3479
+ this.clientManager.cacheClient(user.id, client);
3480
+ return user;
3307
3481
  }
3308
3482
  sweepExpiredFlows() {
3309
3483
  const now = Date.now();