@meshmakers/octo-services 3.4.1160 → 3.4.1190

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.
@@ -4676,18 +4676,49 @@ class TusUploadService {
4676
4676
  httpClient = inject(HttpClient);
4677
4677
  configurationService = inject(CONFIGURATION_SERVICE);
4678
4678
  authorizeService = inject(AuthorizeService);
4679
+ /**
4680
+ * Delays between retries of a restore-job start that answered 403 (AB#5227). The bot service's
4681
+ * tenant gate authorizes a parent administrator against an EXISTING child tenant, and its
4682
+ * hierarchy answer is cached for up to 60 seconds — so a restore right after (re-)creating the
4683
+ * tenant can be refused although the tenant is there. The ladder spans ~75s, one TTL plus slack.
4684
+ * A genuine permission denial still fails, after the ladder, with the server's own reason.
4685
+ * Overridable so specs don't wait.
4686
+ */
4687
+ restoreForbiddenRetryDelaysMs = [5_000, 10_000, 15_000, 20_000, 25_000];
4679
4688
  async startUpload(options) {
4680
4689
  const botServicesUrl = this.configurationService.config?.botServices;
4681
4690
  if (!botServicesUrl) {
4682
4691
  throw new Error('Bot services URL not configured');
4683
4692
  }
4684
4693
  const tusFileId = await this.performTusUpload(botServicesUrl, options);
4685
- const jobResponse = await this.startRestoreJob(botServicesUrl, tusFileId, options);
4694
+ const jobResponse = await this.startRestoreJobWithForbiddenRetry(botServicesUrl, tusFileId, options);
4686
4695
  if (!jobResponse?.jobId) {
4687
4696
  throw new Error('Failed to start restore job');
4688
4697
  }
4689
4698
  return { jobId: jobResponse.jobId };
4690
4699
  }
4700
+ /**
4701
+ * Starts the restore job, retrying a 403 across the tenant-hierarchy cache TTL (see
4702
+ * {@link restoreForbiddenRetryDelaysMs}). Only the job start is retried — the uploaded artifact
4703
+ * is already staged, so no bytes are re-transferred. Every other error propagates immediately.
4704
+ */
4705
+ async startRestoreJobWithForbiddenRetry(botServicesUrl, tusFileId, options) {
4706
+ for (let attempt = 0;; attempt++) {
4707
+ try {
4708
+ return await this.startRestoreJob(botServicesUrl, tusFileId, options);
4709
+ }
4710
+ catch (error) {
4711
+ const isForbidden = error instanceof HttpErrorResponse && error.status === 403;
4712
+ if (!isForbidden || attempt >= this.restoreForbiddenRetryDelaysMs.length) {
4713
+ throw error;
4714
+ }
4715
+ const delayMs = this.restoreForbiddenRetryDelaysMs[attempt];
4716
+ options.onStatus?.(`Waiting for tenant authorization to propagate (retry ${attempt + 1} of ` +
4717
+ `${this.restoreForbiddenRetryDelaysMs.length} in ${Math.round(delayMs / 1000)}s)...`);
4718
+ await new Promise(resolve => setTimeout(resolve, delayMs));
4719
+ }
4720
+ }
4721
+ }
4691
4722
  performTusUpload(botServicesUrl, options) {
4692
4723
  return new Promise((resolve, reject) => {
4693
4724
  const metadata = {