From 520e898dff384141849c4bbac44a048419c72fba Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 8 Mar 2025 10:08:03 +0100 Subject: [PATCH] Redirector asgi rewrite --- api/src/api/middleware/request.py | 5 + api/src/api/middleware/websocket.py | 26 +++ api/src/api_graphql/abc/subscription_abc.py | 1 + api/src/api_graphql/graphql/short_url.gql | 4 +- .../mutations/short_url_mutation.py | 13 ++ api/src/redirector.py | 210 +++++++++++------- api/src/startup.py | 4 +- api/src/templates/redirect.html | 10 +- web/.gitignore | 7 +- .../components/home/home.component.spec.ts | 32 ++- web/src/app/components/home/home.component.ts | 9 +- web/src/app/core/token.interceptor.ts | 14 +- .../admin/domains/domains.data.service.ts | 2 +- .../app/modules/admin/domains/domains.page.ts | 4 +- .../admin/groups/groups.data.service.ts | 2 +- .../app/modules/admin/groups/groups.page.ts | 42 ++-- .../short-urls/short-urls.data.service.ts | 2 +- .../admin/short-urls/short-urls.page.ts | 4 +- web/src/app/modules/shared/shared.module.ts | 14 +- 19 files changed, 274 insertions(+), 131 deletions(-) create mode 100644 api/src/api/middleware/websocket.py diff --git a/api/src/api/middleware/request.py b/api/src/api/middleware/request.py index e2b5ee0..a524453 100644 --- a/api/src/api/middleware/request.py +++ b/api/src/api/middleware/request.py @@ -3,6 +3,7 @@ from typing import Optional, Union from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request +from starlette.websockets import WebSocket _request_context: ContextVar[Union[Request, None]] = ContextVar("request", default=None) @@ -19,5 +20,9 @@ class RequestMiddleware(BaseHTTPMiddleware): return response +def set_request(request: Union[Request, WebSocket, None]): + _request_context.set(request) + + def get_request() -> Optional[Request]: return _request_context.get() diff --git a/api/src/api/middleware/websocket.py b/api/src/api/middleware/websocket.py new file mode 100644 index 0000000..e9d66cb --- /dev/null +++ b/api/src/api/middleware/websocket.py @@ -0,0 +1,26 @@ +from ariadne.asgi.handlers import GraphQLTransportWSHandler +from starlette.datastructures import MutableHeaders +from starlette.websockets import WebSocket + +from api.middleware.request import set_request +from core.logger import APILogger + +logger = APILogger("WS") + + +class AuthenticatedGraphQLTransportWSHandler(GraphQLTransportWSHandler): + + def __init__(self, *args, **kwargs): + super().__init__(*args, on_connect=self.on_connect, **kwargs) + + @staticmethod + async def on_connect(ws: WebSocket, message: dict): + if "Authorization" not in message: + return True + + mutable_headers = MutableHeaders() + mutable_headers["Authorization"] = message.get("Authorization", "") + ws._headers = mutable_headers + + set_request(ws) + return True diff --git a/api/src/api_graphql/abc/subscription_abc.py b/api/src/api_graphql/abc/subscription_abc.py index e38f730..073142f 100644 --- a/api/src/api_graphql/abc/subscription_abc.py +++ b/api/src/api_graphql/abc/subscription_abc.py @@ -3,6 +3,7 @@ from asyncio import iscoroutinefunction from ariadne import SubscriptionType +from api.middleware.request import get_request from api_graphql.abc.query_abc import QueryABC from api_graphql.field.subscription_field_builder import SubscriptionFieldBuilder from core.logger import APILogger diff --git a/api/src/api_graphql/graphql/short_url.gql b/api/src/api_graphql/graphql/short_url.gql index a221e97..1ab6144 100644 --- a/api/src/api_graphql/graphql/short_url.gql +++ b/api/src/api_graphql/graphql/short_url.gql @@ -46,7 +46,8 @@ input ShortUrlFuzzy { input ShortUrlFilter { id: IntFilter - name: StringFilter + shortUrl: StringFilter + targetUrl: StringFilter description: StringFilter loadingScreen: BooleanFilter @@ -63,6 +64,7 @@ type ShortUrlMutation { update(input: ShortUrlUpdateInput!): ShortUrl delete(id: ID!): Boolean restore(id: ID!): Boolean + trackVisit(id: ID!, agent: String): Boolean } input ShortUrlCreateInput { diff --git a/api/src/api_graphql/mutations/short_url_mutation.py b/api/src/api_graphql/mutations/short_url_mutation.py index 6705994..b67357d 100644 --- a/api/src/api_graphql/mutations/short_url_mutation.py +++ b/api/src/api_graphql/mutations/short_url_mutation.py @@ -8,6 +8,8 @@ from data.schemas.public.domain_dao import domainDao from data.schemas.public.group_dao import groupDao from data.schemas.public.short_url import ShortUrl from data.schemas.public.short_url_dao import shortUrlDao +from data.schemas.public.short_url_visit import ShortUrlVisit +from data.schemas.public.short_url_visit_dao import shortUrlVisitDao from service.permission.permissions_enum import Permissions logger = APILogger(__name__) @@ -39,6 +41,11 @@ class ShortUrlMutation(MutationABC): self.resolve_restore, require_any_permission=[Permissions.short_urls_delete], ) + self.mutation( + "trackVisit", + self.resolve_track_visit, + require_any_permission=[Permissions.short_urls_update], + ) @staticmethod async def resolve_create(obj: ShortUrlCreateInput, *_): @@ -106,3 +113,9 @@ class ShortUrlMutation(MutationABC): short_url = await shortUrlDao.get_by_id(id) await shortUrlDao.restore(short_url) return True + + @staticmethod + async def resolve_track_visit(*_, id: int, agent: str): + logger.debug(f"track visit: {id} -- {agent}") + await shortUrlVisitDao.create(ShortUrlVisit(0, id, agent)) + return True diff --git a/api/src/redirector.py b/api/src/redirector.py index 153967f..67d9e05 100644 --- a/api/src/redirector.py +++ b/api/src/redirector.py @@ -1,117 +1,188 @@ import asyncio import sys +from typing import Optional -import eventlet -from eventlet import wsgi -from flask import Flask, request, Response, redirect, render_template +import requests +import uvicorn +from starlette.applications import Starlette +from starlette.requests import Request +from starlette.responses import RedirectResponse +from starlette.routing import Route, Mount +from starlette.staticfiles import StaticFiles +from starlette.templating import Jinja2Templates -from core.database.database import Database from core.environment import Environment from core.logger import Logger -from data.schemas.public.short_url import ShortUrl -from data.schemas.public.short_url_dao import shortUrlDao -from data.schemas.public.short_url_visit import ShortUrlVisit -from data.schemas.public.short_url_visit_dao import shortUrlVisitDao logger = Logger(__name__) +templates = Jinja2Templates(directory="templates") -class Redirector(Flask): - - def __init__(self, *args, **kwargs): - Flask.__init__(self, *args, **kwargs) +async def index(request: Request): + return templates.TemplateResponse("404.html", {"request": request}, status_code=404) -app = Redirector(__name__) - - -@app.route("/") -def index(): - return render_template("404.html"), 404 - - -@app.route("/") -async def _handle_request(path: str): - short_url = await _find_short_url_by_url(path) +async def handle_request(request: Request): + path = request.path_params["path"] + short_url = _find_short_url_by_path(path) if short_url is None: - return render_template("404.html"), 404 + return templates.TemplateResponse( + "404.html", {"request": request}, status_code=404 + ) domains = Environment.get("DOMAINS", list[str], []) - domain = await short_url.domain + domain = short_url["domain"] logger.debug( - f"Domain: {domain.name if domain is not None else None}, request.host: {request.host}" + f"Domain: {domain["name"] if domain is not None else None}, request.host: {request.headers['host']}" ) - host = request.host + host = request.headers["host"] if ":" in host: host = host.split(":")[0] domain_strict_mode = Environment.get("DOMAIN_STRICT_MODE", bool, False) if domain is not None and ( - domain.name not in domains - or (domain_strict_mode and not host.endswith(domain.name)) + domain["name"] not in domains + or (domain_strict_mode and not host.endswith(domain["name"])) ): - return render_template("404.html"), 404 + return templates.TemplateResponse( + "404.html", {"request": request}, status_code=404 + ) user_agent = request.headers.get("User-Agent", "").lower() if "wheregoes" in user_agent or "someothertool" in user_agent: - return await _handle_short_url(path, short_url) + return await _handle_short_url(request, short_url) - if short_url.loading_screen: - await _track_visit(short_url) + if short_url["loadingScreen"]: + await _track_visit(request, short_url) - return render_template( + return templates.TemplateResponse( "redirect.html", - key=short_url.short_url, - target_url=_get_redirect_url(short_url.target_url), + { + "request": request, + "key": short_url["shortUrl"], + "target_url": _get_redirect_url(short_url["targetUrl"]), + }, ) - return await _handle_short_url(path, short_url) + return await _handle_short_url(request, short_url) -async def _handle_short_url(path: str, short_url: ShortUrl): - if path.startswith("api/"): - path = path.replace("api/", "") +def _find_short_url_by_path(path: str) -> Optional[dict]: + api_url = Environment.get("API_URL", str) + if api_url is None: + raise Exception("API_URL is not set") - await _track_visit(short_url) + api_key = Environment.get("API_KEY", str) + if api_key is None: + raise Exception("API_KEY is not set") - return _do_redirect(short_url.target_url) + request = requests.post( + f"{api_url}/graphql", + json={ + "query": f""" + query getShortUrlByPath($path: String!) {{ + shortUrls(filter: {{ shortUrl: {{ equal: $path }}, deleted: {{ equal: false }} }}) {{ + nodes {{ + id + shortUrl + targetUrl + description + group {{ + id + name + }} + domain {{ + id + name + }} + loadingScreen + deleted + }} + }} + }} + """, + "variables": {"path": path}, + }, + headers={"Authorization": f"API-Key {api_key}"}, + ) + data = request.json()["data"]["shortUrls"]["nodes"] + if len(data) == 0: + return None + + return data[0] -async def _track_visit(short_url: ShortUrl): +async def _handle_short_url(request: Request, short_url: dict): + await _track_visit(request, short_url) + + return RedirectResponse(_get_redirect_url(short_url["targetUrl"])) + + +async def _track_visit(r: Request, short_url: dict): + api_url = Environment.get("API_URL", str) + if api_url is None: + raise Exception("API_URL is not set") + + api_key = Environment.get("API_KEY", str) + if api_key is None: + raise Exception("API_KEY is not set") + try: - await shortUrlVisitDao.create( - ShortUrlVisit(0, short_url.id, request.headers.get("User-Agent")) + request = requests.post( + f"{api_url}/graphql", + json={ + "query": f""" + mutation trackShortUrlVisit($id: ID!, $agent: String) {{ + shortUrl {{ + trackVisit(id: $id, agent: $agent) + }} + }} + """, + "variables": { + "id": short_url["id"], + "agent": r.headers.get("User-Agent"), + }, + }, + headers={"Authorization": f"API-Key {api_key}"}, ) + if request.status_code != 200: + logger.warning( + f"Failed to track visit for short url {short_url["shortUrl"]}" + ) + + data = request.json() + if "errors" in data: + raise Exception(data["errors"]) + else: + logger.debug(f"Tracked visit for short url {short_url["shortUrl"]}") except Exception as e: - logger.error(f"Failed to update short url {short_url.short_url} with error", e) - - -async def _find_short_url_by_url(url: str) -> ShortUrl: - return await shortUrlDao.find_single_by({ShortUrl.short_url: url}) + logger.error( + f"Failed to update short url {short_url["shortUrl"]} with error", e + ) def _get_redirect_url(url: str) -> str: - # todo: multiple protocols like ts3:// if not url.startswith("http://") and not url.startswith("https://"): url = f"http://{url}" return url -def _do_redirect(url: str) -> Response: - return redirect(_get_redirect_url(url)) - - async def configure(): Logger.set_level(Environment.get("LOG_LEVEL", str, "info")) Environment.set_environment(Environment.get("ENVIRONMENT", str, "production")) logger.info(f"Environment: {Environment.get_environment()}") - app.debug = Environment.get_environment() == "development" - await Database.startup_db() +routes = [ + Route("/", endpoint=index), + Mount("/static", StaticFiles(directory="static"), name="static"), + Route("/{path:path}", endpoint=handle_request), +] + +app = Starlette(routes=routes, on_startup=[configure]) def main(): @@ -120,26 +191,13 @@ def main(): asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) - loop = asyncio.new_event_loop() - loop.run_until_complete(configure()) - loop.close() - - port = Environment.get("PORT", int, 5001) - logger.info(f"Start API on port: {port}") - if Environment.get_environment() == "development": - logger.info(f"Playground: http://localhost:{port}/") - wsgi.server(eventlet.listen(("0.0.0.0", port)), app, log_output=False) + uvicorn.run( + app, + host="0.0.0.0", + port=Environment.get("PORT", int, 5001), + log_config=None, + ) if __name__ == "__main__": main() - -# (( -# ( ) -# ; / , -# / \/ -# / | -# / ~/ -# / ) ) ~ edraft -# ___// | / -# --' \_~-, diff --git a/api/src/startup.py b/api/src/startup.py index 9829dc6..5bb417e 100644 --- a/api/src/startup.py +++ b/api/src/startup.py @@ -12,6 +12,7 @@ from api.auth.keycloak_client import Keycloak from api.broadcast import broadcast from api.middleware.logging import LoggingMiddleware from api.middleware.request import RequestMiddleware +from api.middleware.websocket import AuthenticatedGraphQLTransportWSHandler from api.route import Route from api_graphql.service.schema import schema from core.database.database import Database @@ -118,7 +119,8 @@ class Startup: WebSocketRoute( "/graphql", endpoint=GraphQL( - schema, websocket_handler=GraphQLTransportWSHandler() + schema, + websocket_handler=AuthenticatedGraphQLTransportWSHandler(), ), ), ], diff --git a/api/src/templates/redirect.html b/api/src/templates/redirect.html index ef8d573..607c54f 100644 --- a/api/src/templates/redirect.html +++ b/api/src/templates/redirect.html @@ -8,17 +8,17 @@
-
-
+
-
+
-

Redirecting...

+

Redirecting...

You will be redirected in 5 seconds.

diff --git a/web/.gitignore b/web/.gitignore index 56324c9..a4142eb 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1 +1,6 @@ -config.*.json \ No newline at end of file +config.*.json + +dist/ +.angular/ +node_modules/ +coverage/ \ No newline at end of file diff --git a/web/src/app/components/home/home.component.spec.ts b/web/src/app/components/home/home.component.spec.ts index bc6a361..5a36317 100644 --- a/web/src/app/components/home/home.component.spec.ts +++ b/web/src/app/components/home/home.component.spec.ts @@ -1,14 +1,38 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { HomeComponent } from "./home.component"; +import { HomeComponent } from './home.component'; +import { SharedModule } from 'src/app/modules/shared/shared.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { AuthService } from 'src/app/service/auth.service'; +import { KeycloakService } from 'keycloak-angular'; +import { ErrorHandlingService } from 'src/app/service/error-handling.service'; +import { ToastService } from 'src/app/service/toast.service'; +import { ConfirmationService, MessageService } from 'primeng/api'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; -describe("HomeComponent", () => { +describe('HomeComponent', () => { let component: HomeComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [HomeComponent], + imports: [SharedModule, TranslateModule.forRoot()], + providers: [ + AuthService, + KeycloakService, + ErrorHandlingService, + ToastService, + MessageService, + ConfirmationService, + { + provide: ActivatedRoute, + useValue: { + snapshot: { params: of({}) }, + }, + }, + ], }).compileComponents(); fixture = TestBed.createComponent(HomeComponent); @@ -16,7 +40,7 @@ describe("HomeComponent", () => { fixture.detectChanges(); }); - it("should create", () => { + it('should create', () => { expect(component).toBeTruthy(); }); }); diff --git a/web/src/app/components/home/home.component.ts b/web/src/app/components/home/home.component.ts index 9c05a54..444635d 100644 --- a/web/src/app/components/home/home.component.ts +++ b/web/src/app/components/home/home.component.ts @@ -1,8 +1,15 @@ import { Component } from '@angular/core'; +import { KeycloakService } from 'keycloak-angular'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrl: './home.component.scss', }) -export class HomeComponent {} +export class HomeComponent { + constructor(private keycloak: KeycloakService) { + if (!this.keycloak.isLoggedIn()) { + this.keycloak.login().then(() => {}); + } + } +} diff --git a/web/src/app/core/token.interceptor.ts b/web/src/app/core/token.interceptor.ts index f927bc4..13c0ead 100644 --- a/web/src/app/core/token.interceptor.ts +++ b/web/src/app/core/token.interceptor.ts @@ -1,7 +1,7 @@ -import { HttpInterceptorFn } from "@angular/common/http"; -import { KeycloakService } from "keycloak-angular"; -import { inject } from "@angular/core"; -import { from, switchMap } from "rxjs"; +import { HttpInterceptorFn } from '@angular/common/http'; +import { KeycloakService } from 'keycloak-angular'; +import { inject } from '@angular/core'; +import { from, switchMap } from 'rxjs'; export const tokenInterceptor: HttpInterceptorFn = (req, next) => { const keycloak = inject(KeycloakService); @@ -15,14 +15,14 @@ export const tokenInterceptor: HttpInterceptorFn = (req, next) => { } return from(keycloak.getToken()).pipe( - switchMap((token) => { + switchMap(token => { const modifiedReq = token ? req.clone({ - headers: req.headers.set("Authorization", `Bearer ${token}`), + headers: req.headers.set('Authorization', `Bearer ${token}`), }) : req; return next(modifiedReq); - }), + }) ); }; diff --git a/web/src/app/modules/admin/domains/domains.data.service.ts b/web/src/app/modules/admin/domains/domains.data.service.ts index 469958a..d3a6272 100644 --- a/web/src/app/modules/admin/domains/domains.data.service.ts +++ b/web/src/app/modules/admin/domains/domains.data.service.ts @@ -113,7 +113,7 @@ export class DomainsDataService return this.apollo .subscribe<{ domainChange: void }>({ query: gql` - subscription onRoleChange { + subscription onDomainChange { domainChange } `, diff --git a/web/src/app/modules/admin/domains/domains.page.ts b/web/src/app/modules/admin/domains/domains.page.ts index 14ba9e3..337b4e3 100644 --- a/web/src/app/modules/admin/domains/domains.page.ts +++ b/web/src/app/modules/admin/domains/domains.page.ts @@ -30,8 +30,8 @@ export class DomainsPage extends PageBase< }); } - load(): void { - this.loading = true; + load(silent?: boolean): void { + if (!silent) this.loading = true; this.dataService .load(this.filter, this.sort, this.skip, this.take) .subscribe(result => { diff --git a/web/src/app/modules/admin/groups/groups.data.service.ts b/web/src/app/modules/admin/groups/groups.data.service.ts index 589bf9b..93be7ec 100644 --- a/web/src/app/modules/admin/groups/groups.data.service.ts +++ b/web/src/app/modules/admin/groups/groups.data.service.ts @@ -121,7 +121,7 @@ export class GroupsDataService return this.apollo .subscribe<{ groupChange: void }>({ query: gql` - subscription onRoleChange { + subscription onGroupChange { groupChange } `, diff --git a/web/src/app/modules/admin/groups/groups.page.ts b/web/src/app/modules/admin/groups/groups.page.ts index f6a835e..e1447e7 100644 --- a/web/src/app/modules/admin/groups/groups.page.ts +++ b/web/src/app/modules/admin/groups/groups.page.ts @@ -1,16 +1,16 @@ -import { Component } from "@angular/core"; -import { PageBase } from "src/app/core/base/page-base"; -import { ToastService } from "src/app/service/toast.service"; -import { ConfirmationDialogService } from "src/app/service/confirmation-dialog.service"; -import { PermissionsEnum } from "src/app/model/auth/permissionsEnum"; -import { Group } from "src/app/model/entities/group"; -import { GroupsDataService } from "src/app/modules/admin/groups/groups.data.service"; -import { GroupsColumns } from "src/app/modules/admin/groups/groups.columns"; +import { Component } from '@angular/core'; +import { PageBase } from 'src/app/core/base/page-base'; +import { ToastService } from 'src/app/service/toast.service'; +import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service'; +import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum'; +import { Group } from 'src/app/model/entities/group'; +import { GroupsDataService } from 'src/app/modules/admin/groups/groups.data.service'; +import { GroupsColumns } from 'src/app/modules/admin/groups/groups.columns'; @Component({ - selector: "app-groups", - templateUrl: "./groups.page.html", - styleUrl: "./groups.page.scss", + selector: 'app-groups', + templateUrl: './groups.page.html', + styleUrl: './groups.page.scss', }) export class GroupsPage extends PageBase< Group, @@ -19,7 +19,7 @@ export class GroupsPage extends PageBase< > { constructor( private toast: ToastService, - private confirmation: ConfirmationDialogService, + private confirmation: ConfirmationDialogService ) { super(true, { read: [PermissionsEnum.groups], @@ -30,11 +30,11 @@ export class GroupsPage extends PageBase< }); } - load(): void { - this.loading = true; + load(silent?: boolean): void { + if (!silent) this.loading = true; this.dataService .load(this.filter, this.sort, this.skip, this.take) - .subscribe((result) => { + .subscribe(result => { this.result = result; this.loading = false; }); @@ -42,12 +42,12 @@ export class GroupsPage extends PageBase< delete(group: Group): void { this.confirmation.confirmDialog({ - header: "dialog.delete.header", - message: "dialog.delete.message", + header: 'dialog.delete.header', + message: 'dialog.delete.message', accept: () => { this.loading = true; this.dataService.delete(group).subscribe(() => { - this.toast.success("action.deleted"); + this.toast.success('action.deleted'); this.load(); }); }, @@ -57,12 +57,12 @@ export class GroupsPage extends PageBase< restore(group: Group): void { this.confirmation.confirmDialog({ - header: "dialog.restore.header", - message: "dialog.restore.message", + header: 'dialog.restore.header', + message: 'dialog.restore.message', accept: () => { this.loading = true; this.dataService.restore(group).subscribe(() => { - this.toast.success("action.restored"); + this.toast.success('action.restored'); this.load(); }); }, diff --git a/web/src/app/modules/admin/short-urls/short-urls.data.service.ts b/web/src/app/modules/admin/short-urls/short-urls.data.service.ts index 25f2e35..7dca99f 100644 --- a/web/src/app/modules/admin/short-urls/short-urls.data.service.ts +++ b/web/src/app/modules/admin/short-urls/short-urls.data.service.ts @@ -139,7 +139,7 @@ export class ShortUrlsDataService return this.apollo .subscribe<{ shortUrlChange: void }>({ query: gql` - subscription onRoleChange { + subscription onShortUrlChange { shortUrlChange } `, diff --git a/web/src/app/modules/admin/short-urls/short-urls.page.ts b/web/src/app/modules/admin/short-urls/short-urls.page.ts index ae54319..a591713 100644 --- a/web/src/app/modules/admin/short-urls/short-urls.page.ts +++ b/web/src/app/modules/admin/short-urls/short-urls.page.ts @@ -95,8 +95,8 @@ export class ShortUrlsPage }; } - load(): void { - this.loading = true; + load(silent?: boolean): void { + if (!silent) this.loading = true; this.dataService .load(this.filter, this.sort, this.skip, this.take) .subscribe(result => { diff --git a/web/src/app/modules/shared/shared.module.ts b/web/src/app/modules/shared/shared.module.ts index 02be5de..ff7c0fe 100644 --- a/web/src/app/modules/shared/shared.module.ts +++ b/web/src/app/modules/shared/shared.module.ts @@ -70,6 +70,7 @@ import { ConfigService } from 'src/app/service/config.service'; import { Logger } from 'src/app/service/logger.service'; import { Router } from '@angular/router'; import { SliderModule } from 'primeng/slider'; +import { KeycloakService } from 'keycloak-angular'; const sharedModules = [ StepsModule, @@ -148,11 +149,11 @@ function debounce(func: (...args: unknown[]) => void, wait: number) { exports: [...sharedModules, ...sharedComponents], providers: [ provideHttpClient(withInterceptors([tokenInterceptor])), - provideApollo(() => { const logger = new Logger('graphql'); const settings = inject(ConfigService); + const keycloak = inject(KeycloakService); const httpLink = inject(HttpLink); const router = inject(Router); @@ -168,9 +169,11 @@ function debounce(func: (...args: unknown[]) => void, wait: number) { retryAttempts: Infinity, shouldRetry: () => true, keepAlive: 10000, - connectionParams: () => ({ - authToken: localStorage.getItem('token'), - }), + connectionParams: async () => { + return { + Authorization: `Bearer ${await keycloak.getToken()}`, + }; + }, on: { connected: () => { logger.info('WebSocket connected'); @@ -187,10 +190,7 @@ function debounce(func: (...args: unknown[]) => void, wait: number) { }) ); - // Using the ability to split links, you can send data to each link - // depending on what kind of operation is being sent const link = split( - // Split based on operation type ({ query }) => { const definition = getMainDefinition(query); return (