@tiba-spark/client-shared-lib 26.3.0-206 → 26.3.0-219

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.
@@ -123416,7 +123416,12 @@ let FacilityState = class FacilityState {
123416
123416
  }
123417
123417
  onZoneChangedAction(ctx, { zone, parkId }) {
123418
123418
  const selected = ctx.getState().selected;
123419
- const zoneIndex = selected.zones?.findIndex(item => item.id === zone.id && item.parkID === parkId);
123419
+ // SPRK3-14984: ZoneChangedAction is a SignalR push, so it arrives regardless of
123420
+ // whether a park is selected or its zones are loaded yet.
123421
+ if (!selected?.zones) {
123422
+ return;
123423
+ }
123424
+ const zoneIndex = selected.zones.findIndex(item => item.id === zone.id && item.parkID === parkId);
123420
123425
  zone.occupancy = zone.monthliesOccupancy + zone.transientsOccupancy;
123421
123426
  zone.vacancy = zone.capacity - zone.occupancy;
123422
123427
  switch (zone.rulesMode) {
@@ -123441,7 +123446,13 @@ let FacilityState = class FacilityState {
123441
123446
  selected.zones = clone(selected.zones);
123442
123447
  const facilityDictionary = ctx.getState().dictionary;
123443
123448
  const guid = ctx.getState().guid;
123444
- facilityDictionary[guid][selected.id].zones = selected.zones;
123449
+ // SPRK3-14984 (Sentry JAVASCRIPT-ANGULAR-35): dictionary defaults to {} and is
123450
+ // always truthy, but dictionary[guid] is undefined until the tenant is hydrated
123451
+ // and the park may not be under it. The selected/zones slices are still correct.
123452
+ const storedFacility = facilityDictionary[guid]?.[selected.id];
123453
+ if (storedFacility) {
123454
+ storedFacility.zones = selected.zones;
123455
+ }
123445
123456
  ctx.patchState({
123446
123457
  dictionary: facilityDictionary,
123447
123458
  selected,
@@ -123492,9 +123503,17 @@ let FacilityState = class FacilityState {
123492
123503
  servicesRunningStatusDictionary[guid] = servicesRunningStatus;
123493
123504
  ctx.patchState({ servicesRunningStatusDictionary });
123494
123505
  // Updating ParkConnectionStatus for parks
123495
- if (facilityDictionary) {
123506
+ // SPRK3-14836 (Sentry JAVASCRIPT-ANGULAR-1R): the old `if (facilityDictionary)`
123507
+ // guarded the outer object, which defaults to {} and always passes, while
123508
+ // Object.values(facilityDictionary[guid]) threw for an unhydrated tenant. The
123509
+ // connectionStatusDictionary slice needs its own per-guid bucket for the same reason.
123510
+ const parksForGuid = facilityDictionary?.[guid];
123511
+ if (parksForGuid) {
123496
123512
  const connectionStatusDictionary = cloneDeep(ctx.getState().connectionStatusDictionary);
123497
- const facilities = Object.values(facilityDictionary[guid]);
123513
+ if (!connectionStatusDictionary[guid]) {
123514
+ connectionStatusDictionary[guid] = {};
123515
+ }
123516
+ const facilities = Object.values(parksForGuid);
123498
123517
  for (const facility of facilities) {
123499
123518
  connectionStatusDictionary[guid][facility.id] = this.globalErrorService.getParkConnectionStatus(guid, facility, servicesRunningStatus, facilityDictionary);
123500
123519
  }
@@ -123518,6 +123537,9 @@ let FacilityState = class FacilityState {
123518
123537
  }
123519
123538
  // Updating ParkConnectionStatus for parks
123520
123539
  const connectionStatusDictionary = cloneDeep(ctx.getState().connectionStatusDictionary);
123540
+ if (!connectionStatusDictionary[guid]) {
123541
+ connectionStatusDictionary[guid] = {};
123542
+ }
123521
123543
  const servicesRunningStatusDictionary = ctx.getState().servicesRunningStatusDictionary;
123522
123544
  const facilities = Object.values(facilityDictionary[guid]);
123523
123545
  for (const facility of facilities) {
@@ -123623,7 +123645,11 @@ let FacilityState = class FacilityState {
123623
123645
  }
123624
123646
  }
123625
123647
  onFacilityRemoteCommandExecutedAction(ctx, { facilityRemoteCommandEvent }) {
123626
- const facility = ctx.getState().dictionary[facilityRemoteCommandEvent.guid][facilityRemoteCommandEvent.parkId];
123648
+ // The command-executed push can name a tenant/park this client has not loaded.
123649
+ const facility = ctx.getState().dictionary[facilityRemoteCommandEvent.guid]?.[facilityRemoteCommandEvent.parkId];
123650
+ if (!facility) {
123651
+ return;
123652
+ }
123627
123653
  const operationTypeKey = FacilityRemoteCommandsLocalizationKey[FacilityRemoteCommandTypes$2[facilityRemoteCommandEvent.type]];
123628
123654
  const config = {
123629
123655
  messages: [{ value: facility.name, class: NotificationClass.body }],
@@ -123654,19 +123680,36 @@ let FacilityState = class FacilityState {
123654
123680
  const facilityDictionary = ctx.getState().dictionary;
123655
123681
  const nowMoment = moment();
123656
123682
  if (facilitiesMap) {
123657
- facilitiesMap[guid].forEach(f => {
123658
- f.cloudId = f.cloudId = facilityDictionary[guid][f.id].cloudId;
123659
- f.featurePermissions = facilityDictionary[guid][f.id].featurePermissions;
123660
- f.permissions = facilityDictionary[guid][f.id].permissions;
123661
- facilityDictionary[guid][f.id] = this.updateFacilityConnectivityFlags(facilityDictionary[guid][f.id], f);
123662
- return facilityDictionary[guid][f.id];
123683
+ // Same class as the SPRK3-15108 guard in the else-branch below: neither the
123684
+ // incoming map nor the stored dictionary is guaranteed to hold this guid, and a
123685
+ // super-park map can name a child park the dictionary has not loaded.
123686
+ const storedParks = facilityDictionary[guid];
123687
+ facilitiesMap[guid]?.forEach(f => {
123688
+ const storedFacility = storedParks?.[f.id];
123689
+ if (!storedFacility) {
123690
+ return;
123691
+ }
123692
+ f.cloudId = storedFacility.cloudId;
123693
+ f.featurePermissions = storedFacility.featurePermissions;
123694
+ f.permissions = storedFacility.permissions;
123695
+ storedParks[f.id] = this.updateFacilityConnectivityFlags(storedFacility, f);
123663
123696
  });
123664
123697
  }
123665
123698
  else {
123666
- facility.cloudId = facilityDictionary[guid][facility.id].cloudId;
123667
- facility.featurePermissions = facilityDictionary[guid][facility.id].featurePermissions;
123668
- facility.permissions = facilityDictionary[guid][facility.id].permissions;
123669
- facilityDictionary[guid][parkId] = this.updateFacilityConnectivityFlags(facilityDictionary[guid][parkId], facility);
123699
+ // SPRK3-15108: getFacility can resolve with an empty payload, and the dictionary may hold
123700
+ // no entry for this guid/facility yet. Either way there is nothing to merge, and the three
123701
+ // reads below used to throw (Sentry JAVASCRIPT-ANGULAR-3F) — the `if (facility)` guard
123702
+ // further down was added for the same class of crash but sits after the dereference.
123703
+ const storedFacility = facility ? facilityDictionary[guid]?.[facility.id] : null;
123704
+ if (!storedFacility) {
123705
+ return;
123706
+ }
123707
+ facility.cloudId = storedFacility.cloudId;
123708
+ facility.featurePermissions = storedFacility.featurePermissions;
123709
+ facility.permissions = storedFacility.permissions;
123710
+ // The guard above validated [facility.id]; parkId can differ for a super-park
123711
+ // child, in which case [parkId] holds nothing to merge from.
123712
+ facilityDictionary[guid][parkId] = this.updateFacilityConnectivityFlags(facilityDictionary[guid][parkId] ?? storedFacility, facility);
123670
123713
  }
123671
123714
  // Guard: a super-park facilities map may not contain the requested parkId
123672
123715
  // (or the response may be empty). Skip the time-diff calc rather than
@@ -123675,7 +123718,7 @@ let FacilityState = class FacilityState {
123675
123718
  clientServerDiffDictionary[guid] = this.getClientServerTimeDiff(facility, nowMoment);
123676
123719
  this.patchDiffDictinaryToStore(ctx, clientServerDiffDictionary);
123677
123720
  }
123678
- this.patchFacilitiesToStore(ctx, facilityDictionary, facilityDictionary[guid][parkId], appModule);
123721
+ this.patchFacilitiesToStore(ctx, facilityDictionary, facilityDictionary[guid]?.[parkId], appModule);
123679
123722
  }
123680
123723
  updateFacilityConnectivityFlags(storedFacility, incomingFacility) {
123681
123724
  const cloudDateTimeUtc = this.getCloudDateTimeUtc();
@@ -123688,7 +123731,7 @@ let FacilityState = class FacilityState {
123688
123731
  ctx.patchState({
123689
123732
  dictionary,
123690
123733
  });
123691
- if (appModule === AppModule$2.Cc) {
123734
+ if (appModule === AppModule$2.Cc && facility) {
123692
123735
  const allowedActions = convertFeaturePermissionsToDictionary(facility.featurePermissions);
123693
123736
  ctx.patchState({
123694
123737
  selected: facility,
@@ -123716,10 +123759,12 @@ let FacilityState = class FacilityState {
123716
123759
  }
123717
123760
  updateServiceRunningStatusFlags(currentServicesRunningStatus, newServicesRunningStatus) {
123718
123761
  const cloudDateTimeUtc = this.getCloudDateTimeUtc();
123719
- newServicesRunningStatus.smartParkDisconnectedTimestamp = newServicesRunningStatus.isSmartParkConnected ? null : currentServicesRunningStatus.smartParkDisconnectedTimestamp ?? cloudDateTimeUtc;
123720
- newServicesRunningStatus.smartParkCoreDisconnectedTimestamp = newServicesRunningStatus.isSmartParkCoreConnected ? null : currentServicesRunningStatus.smartParkCoreDisconnectedTimestamp ?? cloudDateTimeUtc;
123721
- newServicesRunningStatus.sparkCloudDisconnectedTimestamp = newServicesRunningStatus.isSparkCloudConnected ? null : currentServicesRunningStatus.sparkCloudDisconnectedTimestamp ?? cloudDateTimeUtc;
123722
- newServicesRunningStatus.sparkCoreDisconnectedTimestamp = newServicesRunningStatus.isSparkCoreConnected ? null : currentServicesRunningStatus.sparkCoreDisconnectedTimestamp ?? cloudDateTimeUtc;
123762
+ // The caller passes servicesRunningStatusDictionary[guid], which is absent on the
123763
+ // first status push for a tenant -- fall back to "disconnected as of now".
123764
+ newServicesRunningStatus.smartParkDisconnectedTimestamp = newServicesRunningStatus.isSmartParkConnected ? null : currentServicesRunningStatus?.smartParkDisconnectedTimestamp ?? cloudDateTimeUtc;
123765
+ newServicesRunningStatus.smartParkCoreDisconnectedTimestamp = newServicesRunningStatus.isSmartParkCoreConnected ? null : currentServicesRunningStatus?.smartParkCoreDisconnectedTimestamp ?? cloudDateTimeUtc;
123766
+ newServicesRunningStatus.sparkCloudDisconnectedTimestamp = newServicesRunningStatus.isSparkCloudConnected ? null : currentServicesRunningStatus?.sparkCloudDisconnectedTimestamp ?? cloudDateTimeUtc;
123767
+ newServicesRunningStatus.sparkCoreDisconnectedTimestamp = newServicesRunningStatus.isSparkCoreConnected ? null : currentServicesRunningStatus?.sparkCoreDisconnectedTimestamp ?? cloudDateTimeUtc;
123723
123768
  return newServicesRunningStatus;
123724
123769
  }
123725
123770
  assignFacility(storedFacility, incomingFacility) {
@@ -124699,7 +124744,14 @@ let ParkManagerState = class ParkManagerState {
124699
124744
  const clonedDto = DeviceStatusDto$1.fromJS(action.deviceStatus);
124700
124745
  clonedDto.type = devicesById[deviceId].type;
124701
124746
  devicesById[deviceId].deviceStatus = clonedDto;
124702
- devicesbyCommId[commID].deviceStatus = clonedDto;
124747
+ // SPRK3-14987 (Sentry JAVASCRIPT-ANGULAR-3A): the guard above tests byId, but
124748
+ // this writes byCommId -- a different map, keyed by a different, optional field.
124749
+ // byId is the primary index and both maps are rebuilt together in patchDevices,
124750
+ // so a missing secondary entry must not cost byId its fresh status.
124751
+ const deviceByCommId = devicesbyCommId[commID];
124752
+ if (deviceByCommId) {
124753
+ deviceByCommId.deviceStatus = clonedDto;
124754
+ }
124703
124755
  ctx.patchState({
124704
124756
  byId: cloneDeep(devicesById),
124705
124757
  byCommId: cloneDeep(devicesbyCommId),
@@ -128370,6 +128422,11 @@ class CompanyService {
128370
128422
  includeSubCompanies: includeSubCompanies,
128371
128423
  });
128372
128424
  const accountsFetchMethod = this.getAccountsFetchMethod(permissionType);
128425
+ if (!accountsFetchMethod) {
128426
+ return of(null).pipe(tap(_ => {
128427
+ this.loadingCompanySubject$.next(false);
128428
+ }));
128429
+ }
128373
128430
  const getCompanies$ = accountsFetchMethod(guid, parkId, searchParamsDto.maxResults, searchParamsDto.searchTerm, searchParamsDto.orderBy.column, searchParamsDto.orderBy.orderDirection, companiesFilterDto);
128374
128431
  return getCompanies$.pipe(tap(_ => {
128375
128432
  this.loadingCompanySubject$.next(false);
@@ -128389,6 +128446,11 @@ class CompanyService {
128389
128446
  subCompanyIds: subCompanyIds,
128390
128447
  });
128391
128448
  const subAccountsFetchMethod = this.getSubAccountsFetchMethod(permissionType);
128449
+ if (!subAccountsFetchMethod) {
128450
+ return of(null).pipe(tap(_ => {
128451
+ this.loadingSubCompanySubject$.next(false);
128452
+ }));
128453
+ }
128392
128454
  getSubCompanies$ = subAccountsFetchMethod(guid, parkId, null, null, null, OrderDirection$2.None, subCompaniesWithPermissions);
128393
128455
  return getSubCompanies$.pipe(tap(_ => {
128394
128456
  this.loadingSubCompanySubject$.next(false);
@@ -128403,6 +128465,11 @@ class CompanyService {
128403
128465
  this.loadingSubCompanySubject$.next(true);
128404
128466
  let getSubCompanies$;
128405
128467
  const subAccountsFetchMethod = this.getSubAccountsFetchMethod(permissionType);
128468
+ if (!subAccountsFetchMethod) {
128469
+ return of(null).pipe(tap(_ => {
128470
+ this.loadingSubCompanySubject$.next(false);
128471
+ }));
128472
+ }
128406
128473
  const subCompaniesFilterDto = new SubCompaniesFilterDto({
128407
128474
  parkId,
128408
128475
  companyIds,
@@ -128464,11 +128531,16 @@ class CompanyService {
128464
128531
  this.messageBarService.clearMessagebar('credentialWarningMessage');
128465
128532
  }
128466
128533
  }
128534
+ // Both mappings below are partial lookups over PermissionType, so a miss is a
128535
+ // reachable state rather than a programming error: the Reports Center binds its
128536
+ // company dropdowns to the selected category's permission type, and the "All
128537
+ // reports" category legitimately has none. Return undefined on a miss and let
128538
+ // the callers degrade — see SPRK3-15029.
128467
128539
  getAccountsFetchMethod(permissionType) {
128468
- return (this.accountsFetchMethodsMapping[permissionType]).bind(this.accountRpcServiceProxy);
128540
+ return this.accountsFetchMethodsMapping[permissionType]?.bind(this.accountRpcServiceProxy);
128469
128541
  }
128470
128542
  getSubAccountsFetchMethod(permissionType) {
128471
- return (this.subAccountsFetchMethodsMapping[permissionType]).bind(this.accountRpcServiceProxy);
128543
+ return this.subAccountsFetchMethodsMapping[permissionType]?.bind(this.accountRpcServiceProxy);
128472
128544
  }
128473
128545
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CompanyService, deps: [{ token: i1$2.Store }, { token: MessageBarService }, { token: i1$5.TranslateService }, { token: SmartparkService }, { token: AccountRpcServiceProxy }], target: i0.ɵɵFactoryTarget.Injectable }); }
128474
128546
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CompanyService }); }
@@ -134289,7 +134361,9 @@ class FacilityService {
134289
134361
  };
134290
134362
  this.hasPermissionTypesByPark = (permissionTypes, guid, parkId) => {
134291
134363
  const facilityDictionary = this.store.selectSnapshot(FacilityState.facilityDictionary);
134292
- const featurePermissions = facilityDictionary[guid][parkId].featurePermissions;
134364
+ // SPRK3-14812: the dictionary is not hydrated for every guid/park at every point
134365
+ // in the change-detection cycle. An unknown park grants nothing (fail closed).
134366
+ const featurePermissions = facilityDictionary?.[guid]?.[parkId]?.featurePermissions;
134293
134367
  const sparkVersion = this.store.selectSnapshot(({ smartpark: { versions } }) => versions[guid]);
134294
134368
  return permissionTypes.reduce((acc, permissionType) => {
134295
134369
  const permissionSupportVersion = PermissionTypeSupportedVersionDictionary[permissionType];
@@ -134297,7 +134371,10 @@ class FacilityService {
134297
134371
  console.warn(`Permission ${permissionType} version support is missing in PermissionTypeSupportedVersionDictionary file`);
134298
134372
  }
134299
134373
  const isVersionSupported = isVersionGreaterThanEqual(sparkVersion, permissionSupportVersion);
134300
- acc[permissionType] = isVersionSupported && featurePermissions.includes(permissionType);
134374
+ // isVersionGreaterThanEqual returns undefined (not false) for an unknown version
134375
+ // despite its declared boolean return, which leaked undefined into a dictionary
134376
+ // typed { [key: string]: boolean }. Coerce so the declared type holds.
134377
+ acc[permissionType] = !!(isVersionSupported && featurePermissions?.includes(permissionType));
134301
134378
  return acc;
134302
134379
  }, {});
134303
134380
  };
@@ -134306,8 +134383,11 @@ class FacilityService {
134306
134383
  let isVersionSupported = this.isVersionSupported(permissionType, guid);
134307
134384
  if (isVersionSupported) {
134308
134385
  const facilityDictionary = this.store.selectSnapshot(FacilityState.facilityDictionary);
134309
- const featurePermissions = facilityDictionary[guid][parkId].featurePermissions;
134310
- hasPermission = featurePermissions?.includes(permissionType);
134386
+ // SPRK3-14812 (Sentry JAVASCRIPT-ANGULAR-K): the optional chain sat one level too
134387
+ // deep -- [guid] and [parkId] were both indexed unguarded. Fail closed, which is
134388
+ // what the `hasPermission = false` initialiser above already promises callers.
134389
+ const featurePermissions = facilityDictionary?.[guid]?.[parkId]?.featurePermissions;
134390
+ hasPermission = !!featurePermissions?.includes(permissionType);
134311
134391
  }
134312
134392
  return hasPermission;
134313
134393
  };