mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-16 22:52:57 +00:00
Compare commits
3 Commits
@openstapp
...
@openstapp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
436e1471a7 | ||
|
|
4c9d330c88 | ||
|
|
580ebee362 |
@@ -1,5 +1,11 @@
|
|||||||
# @openstapps/app
|
# @openstapps/app
|
||||||
|
|
||||||
|
## 3.3.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 4c9d330c: fix user logout when token expires
|
||||||
|
|
||||||
## 3.3.4
|
## 3.3.4
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@openstapps/app",
|
"name": "@openstapps/app",
|
||||||
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
|
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
|
||||||
"version": "3.3.4",
|
"version": "3.3.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
|
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"geojson": "0.5.0",
|
"geojson": "0.5.0",
|
||||||
"ionic-appauth": "0.9.0",
|
"ionic-appauth": "0.9.0",
|
||||||
"jsonpath-plus": "10.0.6",
|
"jsonpath-plus": "10.0.7",
|
||||||
"maplibre-gl": "4.0.2",
|
"maplibre-gl": "4.0.2",
|
||||||
"material-symbols": "0.17.1",
|
"material-symbols": "0.17.1",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ export abstract class AuthService implements IAuthService {
|
|||||||
|
|
||||||
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
|
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
private _loggedInSubject = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
private _initComplete = new BehaviorSubject<boolean>(false);
|
private _initComplete = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
protected tokenHandler: TokenRequestHandler;
|
protected tokenHandler: TokenRequestHandler;
|
||||||
@@ -133,6 +135,13 @@ export abstract class AuthService implements IAuthService {
|
|||||||
return this._authenticatedSubject.asObservable();
|
return this._authenticatedSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to isAuthenticated$, but will also return true if the token is expired
|
||||||
|
*/
|
||||||
|
get isLoggedIn$(): Observable<boolean> {
|
||||||
|
return this._loggedInSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
get initComplete$(): Observable<boolean> {
|
get initComplete$(): Observable<boolean> {
|
||||||
return this._initComplete.asObservable();
|
return this._initComplete.asObservable();
|
||||||
}
|
}
|
||||||
@@ -183,19 +192,20 @@ export abstract class AuthService implements IAuthService {
|
|||||||
protected notifyActionListers(action: IAuthAction) {
|
protected notifyActionListers(action: IAuthAction) {
|
||||||
/* eslint-disable unicorn/no-useless-undefined */
|
/* eslint-disable unicorn/no-useless-undefined */
|
||||||
switch (action.action) {
|
switch (action.action) {
|
||||||
case AuthActions.RefreshFailed:
|
|
||||||
case AuthActions.SignInFailed:
|
case AuthActions.SignInFailed:
|
||||||
case AuthActions.SignOutSuccess:
|
case AuthActions.SignOutSuccess:
|
||||||
case AuthActions.SignOutFailed: {
|
case AuthActions.SignOutFailed: {
|
||||||
this._tokenSubject.next(undefined);
|
this._tokenSubject.next(undefined);
|
||||||
this._userSubject.next(undefined);
|
this._userSubject.next(undefined);
|
||||||
this._authenticatedSubject.next(false);
|
this._authenticatedSubject.next(false);
|
||||||
|
this._loggedInSubject.next(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AuthActions.LoadTokenFromStorageFailed: {
|
case AuthActions.LoadTokenFromStorageFailed: {
|
||||||
this._tokenSubject.next(undefined);
|
this._tokenSubject.next(undefined);
|
||||||
this._userSubject.next(undefined);
|
this._userSubject.next(undefined);
|
||||||
this._authenticatedSubject.next(false);
|
this._authenticatedSubject.next(false);
|
||||||
|
this._loggedInSubject.next(false);
|
||||||
this._initComplete.next(true);
|
this._initComplete.next(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -203,11 +213,13 @@ export abstract class AuthService implements IAuthService {
|
|||||||
case AuthActions.RefreshSuccess: {
|
case AuthActions.RefreshSuccess: {
|
||||||
this._tokenSubject.next(action.tokenResponse);
|
this._tokenSubject.next(action.tokenResponse);
|
||||||
this._authenticatedSubject.next(true);
|
this._authenticatedSubject.next(true);
|
||||||
|
this._loggedInSubject.next(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AuthActions.LoadTokenFromStorageSuccess: {
|
case AuthActions.LoadTokenFromStorageSuccess: {
|
||||||
this._tokenSubject.next(action.tokenResponse);
|
this._tokenSubject.next(action.tokenResponse);
|
||||||
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
|
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
|
||||||
|
this._loggedInSubject.next(true);
|
||||||
this._initComplete.next(true);
|
this._initComplete.next(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -442,7 +454,6 @@ export abstract class AuthService implements IAuthService {
|
|||||||
|
|
||||||
public async refreshToken() {
|
public async refreshToken() {
|
||||||
await this.requestTokenRefresh().catch(error => {
|
await this.requestTokenRefresh().catch(error => {
|
||||||
this.storage.removeItem(TOKEN_RESPONSE_KEY);
|
|
||||||
this.notifyActionListers(AuthActionBuilder.RefreshFailed(error));
|
this.notifyActionListers(AuthActionBuilder.RefreshFailed(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ export class PAIAAuthService {
|
|||||||
|
|
||||||
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
|
private _authenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
private _loggedIn = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
private _initComplete = new BehaviorSubject<boolean>(false);
|
private _initComplete = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
protected tokenHandler: PAIATokenRequestHandler;
|
protected tokenHandler: PAIATokenRequestHandler;
|
||||||
@@ -118,6 +120,13 @@ export class PAIAAuthService {
|
|||||||
return this._authenticatedSubject.asObservable();
|
return this._authenticatedSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to isAuthenticated$, but will also return true if the token is expired
|
||||||
|
*/
|
||||||
|
get isLoggedIn$(): Observable<boolean> {
|
||||||
|
return this._loggedIn.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
get initComplete$(): Observable<boolean> {
|
get initComplete$(): Observable<boolean> {
|
||||||
return this._initComplete.asObservable();
|
return this._initComplete.asObservable();
|
||||||
}
|
}
|
||||||
@@ -170,23 +179,27 @@ export class PAIAAuthService {
|
|||||||
this._tokenSubject.next(undefined);
|
this._tokenSubject.next(undefined);
|
||||||
this._userSubject.next(undefined);
|
this._userSubject.next(undefined);
|
||||||
this._authenticatedSubject.next(false);
|
this._authenticatedSubject.next(false);
|
||||||
|
this._loggedIn.next(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AuthActions.LoadTokenFromStorageFailed: {
|
case AuthActions.LoadTokenFromStorageFailed: {
|
||||||
this._tokenSubject.next(undefined);
|
this._tokenSubject.next(undefined);
|
||||||
this._userSubject.next(undefined);
|
this._userSubject.next(undefined);
|
||||||
this._authenticatedSubject.next(false);
|
this._authenticatedSubject.next(false);
|
||||||
|
this._loggedIn.next(false);
|
||||||
this._initComplete.next(true);
|
this._initComplete.next(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AuthActions.SignInSuccess: {
|
case AuthActions.SignInSuccess: {
|
||||||
this._tokenSubject.next(action.tokenResponse);
|
this._tokenSubject.next(action.tokenResponse);
|
||||||
this._authenticatedSubject.next(true);
|
this._authenticatedSubject.next(true);
|
||||||
|
this._loggedIn.next(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AuthActions.LoadTokenFromStorageSuccess: {
|
case AuthActions.LoadTokenFromStorageSuccess: {
|
||||||
this._tokenSubject.next(action.tokenResponse);
|
this._tokenSubject.next(action.tokenResponse);
|
||||||
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
|
this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0));
|
||||||
|
this._loggedIn.next(true);
|
||||||
this._initComplete.next(true);
|
this._initComplete.next(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ export class IdCardsProvider {
|
|||||||
this.encryptedStorageProvider.get<SCIdCard[]>('id-cards') as Promise<SCIdCard[]>,
|
this.encryptedStorageProvider.get<SCIdCard[]>('id-cards') as Promise<SCIdCard[]>,
|
||||||
).pipe(filter(it => it !== undefined));
|
).pipe(filter(it => it !== undefined));
|
||||||
|
|
||||||
return auth.isAuthenticated$.pipe(
|
return auth.isLoggedIn$.pipe(
|
||||||
mergeMap(isAuthenticated =>
|
mergeMap(isLoggedIn =>
|
||||||
isAuthenticated
|
isLoggedIn
|
||||||
? feature
|
? feature
|
||||||
? storedIdCards.pipe(
|
? storedIdCards.pipe(
|
||||||
concatWith(
|
concatWith(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {SCAuthorizationProviderType} from '@openstapps/core';
|
|||||||
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
|
import {EncryptedStorageProvider} from '../storage/encrypted-storage.provider';
|
||||||
|
|
||||||
class FakeAuth {
|
class FakeAuth {
|
||||||
isAuthenticated$ = new BehaviorSubject(false);
|
isLoggedIn$ = new BehaviorSubject(false);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
getValidToken() {}
|
getValidToken() {}
|
||||||
@@ -42,7 +42,7 @@ describe('IdCards', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should emit network result when logged in', async () => {
|
it('should emit network result when logged in', async () => {
|
||||||
fakeAuth.isAuthenticated$.next(true);
|
fakeAuth.isLoggedIn$.next(true);
|
||||||
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
||||||
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
||||||
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
|
const provider = new IdCardsProvider(authHelper, configProvider, httpClient, encryptedStorageProvider);
|
||||||
@@ -63,7 +63,7 @@ describe('IdCards', () => {
|
|||||||
expect(await firstValueFrom(observable)).toEqual([]);
|
expect(await firstValueFrom(observable)).toEqual([]);
|
||||||
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
httpClient.get = jasmine.createSpy().and.returnValue(of(['abc']));
|
||||||
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
fakeAuth.getValidToken = jasmine.createSpy().and.resolveTo({accessToken: 'fake-token'});
|
||||||
fakeAuth.isAuthenticated$.next(true);
|
fakeAuth.isLoggedIn$.next(true);
|
||||||
// this is counter-intuitive, but because we unsubscribed above the first value
|
// this is counter-intuitive, but because we unsubscribed above the first value
|
||||||
// will now contain the network result.
|
// will now contain the network result.
|
||||||
expect(await firstValueFrom(observable)).toEqual(['abc' as never]);
|
expect(await firstValueFrom(observable)).toEqual(['abc' as never]);
|
||||||
|
|||||||
@@ -6,74 +6,56 @@
|
|||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
* FITNESS FOR A PARTICULAr purpose. see the gnu general public license for
|
||||||
* more details.
|
* more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with
|
* you should have received a copy of the gnu general public license along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. if not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
import {Component, inject, input} from '@angular/core';
|
||||||
import {SCSection} from '../../../../config/profile-page-sections';
|
import {SCSection} from '../../../../config/profile-page-sections';
|
||||||
import {AuthHelperService} from '../../auth/auth-helper.service';
|
import {AuthHelperService} from '../../auth/auth-helper.service';
|
||||||
import {Observable} from 'rxjs';
|
import {mergeMap, of} from 'rxjs';
|
||||||
import {SCAuthorizationProviderType} from '@openstapps/core';
|
|
||||||
import Swiper from 'swiper';
|
import Swiper from 'swiper';
|
||||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
import {toObservable, toSignal} from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'stapps-profile-page-section',
|
selector: 'stapps-profile-page-section',
|
||||||
templateUrl: 'profile-page-section.html',
|
templateUrl: 'profile-page-section.html',
|
||||||
styleUrls: ['profile-page-section.scss'],
|
styleUrls: ['profile-page-section.scss'],
|
||||||
})
|
})
|
||||||
export class ProfilePageSectionComponent implements OnInit {
|
export class ProfilePageSectionComponent {
|
||||||
@Input() item: SCSection;
|
item = input.required<SCSection>();
|
||||||
|
|
||||||
@Input() minSlideWidth = 110;
|
minSlideWidth = input(110);
|
||||||
|
|
||||||
isLoggedIn: boolean;
|
authHelper = inject(AuthHelperService);
|
||||||
|
|
||||||
|
loggedIn = toSignal(
|
||||||
|
toObservable(this.item).pipe(
|
||||||
|
mergeMap(item =>
|
||||||
|
item.authProvider ? this.authHelper.getProvider(item.authProvider).isLoggedIn$ : of(false),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
isEnd = false;
|
isEnd = false;
|
||||||
|
|
||||||
isBeginning = true;
|
isBeginning = true;
|
||||||
|
|
||||||
slidesPerView: number;
|
slidesPerView?: number;
|
||||||
|
|
||||||
slidesFillScreen = false;
|
slidesFillScreen = false;
|
||||||
|
|
||||||
data: {
|
|
||||||
[key in SCAuthorizationProviderType]: {loggedIn$: Observable<boolean>};
|
|
||||||
} = {
|
|
||||||
default: {
|
|
||||||
loggedIn$: this.authHelper.getProvider('default').isAuthenticated$,
|
|
||||||
},
|
|
||||||
paia: {
|
|
||||||
loggedIn$: this.authHelper.getProvider('paia').isAuthenticated$,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
destroy$ = inject(DestroyRef);
|
|
||||||
|
|
||||||
constructor(private authHelper: AuthHelperService) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
if (this.item.authProvider) {
|
|
||||||
this.data[this.item.authProvider].loggedIn$
|
|
||||||
.pipe(takeUntilDestroyed(this.destroy$))
|
|
||||||
.subscribe(loggedIn => {
|
|
||||||
this.isLoggedIn = loggedIn;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activeIndexChange(swiper: Swiper) {
|
activeIndexChange(swiper: Swiper) {
|
||||||
this.isBeginning = swiper.isBeginning;
|
this.isBeginning = swiper.isBeginning;
|
||||||
this.isEnd = swiper.isEnd;
|
this.isEnd = swiper.isEnd;
|
||||||
this.slidesFillScreen = this.slidesPerView >= swiper.slides.length;
|
this.slidesFillScreen = this.slidesPerView! >= swiper.slides.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeSwiper(resizeEvent: ResizeObserverEntry, swiper: Swiper) {
|
resizeSwiper(resizeEvent: ResizeObserverEntry, swiper: Swiper) {
|
||||||
const slidesPerView =
|
const slidesPerView =
|
||||||
Math.floor((resizeEvent.contentRect.width - this.minSlideWidth / 2) / this.minSlideWidth) + 0.5;
|
Math.floor((resizeEvent.contentRect.width - this.minSlideWidth() / 2) / this.minSlideWidth()) + 0.5;
|
||||||
|
|
||||||
if (slidesPerView > 1 && slidesPerView !== this.slidesPerView) {
|
if (slidesPerView > 1 && slidesPerView !== this.slidesPerView) {
|
||||||
this.slidesPerView = slidesPerView;
|
this.slidesPerView = slidesPerView;
|
||||||
@@ -84,16 +66,13 @@ export class ProfilePageSectionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async toggleLogIn() {
|
async toggleLogIn() {
|
||||||
if (!this.item.authProvider) return;
|
const providerType = this.item().authProvider;
|
||||||
await (this.isLoggedIn ? this.signOut(this.item.authProvider) : this.signIn(this.item.authProvider));
|
if (!providerType) return;
|
||||||
}
|
if (this.loggedIn()) {
|
||||||
|
await this.authHelper.getProvider(providerType).signOut();
|
||||||
async signIn(providerType: SCAuthorizationProviderType) {
|
await this.authHelper.endBrowserSession(providerType);
|
||||||
await this.authHelper.getProvider(providerType).signIn();
|
} else {
|
||||||
}
|
await this.authHelper.getProvider(providerType).signIn();
|
||||||
|
}
|
||||||
async signOut(providerType: SCAuthorizationProviderType) {
|
|
||||||
await this.authHelper.getProvider(providerType).signOut();
|
|
||||||
await this.authHelper.endBrowserSession(providerType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,24 +13,24 @@
|
|||||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<stapps-section [title]="'name' | translateSimple: item">
|
<stapps-section [title]="'name' | translateSimple: item()">
|
||||||
@if (item.authProvider) {
|
@if (item().authProvider) {
|
||||||
<ion-button slot="button-end" fill="clear" color="dark" (click)="toggleLogIn()">
|
<ion-button slot="button-end" fill="clear" color="dark" (click)="toggleLogIn()">
|
||||||
@if (isLoggedIn) {
|
@if (loggedIn()) {
|
||||||
<ion-icon slot="end" name="logout"></ion-icon>
|
<ion-icon slot="end" name="logout"></ion-icon>
|
||||||
} @else {
|
} @else {
|
||||||
<ion-icon slot="end" name="login"></ion-icon>
|
<ion-icon slot="end" name="login"></ion-icon>
|
||||||
}
|
}
|
||||||
<ion-label>{{ 'profile.buttons.default.log_' + (isLoggedIn ? 'out' : 'in') | translate }}</ion-label>
|
<ion-label>{{ 'profile.buttons.default.log_' + (loggedIn() ? 'out' : 'in') | translate }}</ion-label>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
}
|
}
|
||||||
|
|
||||||
<simple-swiper>
|
<simple-swiper>
|
||||||
@for (link of item.links; track link) {
|
@for (link of item().links; track link) {
|
||||||
<ion-item
|
<ion-item
|
||||||
lines="none"
|
lines="none"
|
||||||
[routerLink]="link.link"
|
[routerLink]="link.link"
|
||||||
[disabled]="link.needsAuth && !isLoggedIn"
|
[disabled]="link.needsAuth && !loggedIn()"
|
||||||
[detail]="false"
|
[detail]="false"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -837,8 +837,8 @@ importers:
|
|||||||
specifier: 0.9.0
|
specifier: 0.9.0
|
||||||
version: 0.9.0(rxjs@7.8.1)
|
version: 0.9.0(rxjs@7.8.1)
|
||||||
jsonpath-plus:
|
jsonpath-plus:
|
||||||
specifier: 10.0.6
|
specifier: 10.0.7
|
||||||
version: 10.0.6
|
version: 10.0.7
|
||||||
maplibre-gl:
|
maplibre-gl:
|
||||||
specifier: 4.0.2
|
specifier: 4.0.2
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
@@ -15039,8 +15039,8 @@ packages:
|
|||||||
engines: {'0': node >= 0.2.0}
|
engines: {'0': node >= 0.2.0}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/jsonpath-plus@10.0.6:
|
/jsonpath-plus@10.0.7:
|
||||||
resolution: {integrity: sha512-Q0KCash90S0WQnPnE/W0uVXQSww4NkO34COfs+gbq0fk+Kv03FYpZ+uU2I7soLLaS4d/ywsm9PxplZsTMmfBmg==}
|
resolution: {integrity: sha512-GDA8d8fu9+s4QzAzo5LMGiLL/9YjecAX+ytlnqdeXYpU55qME57StDgaHt9R2pA7Dr8U31nwzxNJMJiHkrkRgw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user