Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 101 additions & 4 deletions src/app/pages/main/main.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, HostListener, OnInit } from '@angular/core';
import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { DataStore, User } from '@app/globals';
import { HttpService, I18nService, LogService, SettingService, ViewService } from '@app/services';
import { environment } from '@src/environments/environment';
Expand All @@ -11,13 +11,31 @@
message: string;
}

function createSessionClientId(): string {
if (typeof window.crypto.randomUUID === 'function') {
return window.crypto.randomUUID();
}

const bytes = window.crypto.getRandomValues(new Uint8Array(16));
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
return [
hex.slice(0, 8),
hex.slice(8, 12),
hex.slice(12, 16),
hex.slice(16, 20),
hex.slice(20)
].join('-');
}

@Component({
standalone: false,
selector: 'pages-main',
templateUrl: 'main.component.html',
styleUrls: ['main.component.scss']
})
export class PageMainComponent implements OnInit {
export class PageMainComponent implements OnInit, OnDestroy {

Check warning on line 38 in src/app/pages/main/main.component.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark these members as `readonly`.

See more on https://sonarcloud.io/project/issues?id=jumpserver_luna&issues=AZ_-K7DXRdE24u2TF2Zr&open=AZ_-K7DXRdE24u2TF2Zr&pullRequest=1576
User = User;
store = DataStore;
showIframeHider = false;
Expand All @@ -34,6 +52,11 @@
private popupMessages: SiteMessagePopup[] = [];
private popupDialogRef: NzModalRef | null = null;
private currentPopupMessage: SiteMessagePopup | null = null;
private readonly userSessionClientId = createSessionClientId();
private userSessionHeartbeatInterval = 30 * 1000;
private userSessionHeartbeatTimer: number | null = null;
private userSessionHeartbeatInFlight = false;
private userSessionReleased = false;

constructor(
public viewSrv: ViewService,
Expand All @@ -56,14 +79,89 @@
}

ngOnInit(): void {
this._http.getUserSession().subscribe();
this.renewUserSession();
this._settingSvc.isDirectNavigation$.subscribe(state => {
this.isDirectNavigation = state;
});

this.connectWebsocket();
}

ngOnDestroy(): void {
this.stopUserSessionHeartbeat();
}

private renewUserSession() {
if (this.userSessionHeartbeatInFlight) {
return;
}

this.stopUserSessionHeartbeat();
this.userSessionHeartbeatInFlight = true;
this._http.renewUserSession(this.userSessionClientId).subscribe({
next: data => {
this.userSessionHeartbeatInFlight = false;
if (data?.ok === false) {
this.userSessionReleased = true;
this.stopUserSessionHeartbeat();
return;
}
this.userSessionReleased = false;
const interval = Number(data?.heartbeat_interval);
if (interval > 0) {
this.userSessionHeartbeatInterval = interval * 1000;
}
this.scheduleUserSessionHeartbeat();
},
error: () => {
this.userSessionHeartbeatInFlight = false;
this.stopUserSessionHeartbeat();
}
});
}

private scheduleUserSessionHeartbeat() {
this.stopUserSessionHeartbeat();
if (this.userSessionReleased) {
return;
}
this.userSessionHeartbeatTimer = window.setTimeout(
() => this.renewUserSession(),
this.userSessionHeartbeatInterval
);
}

private stopUserSessionHeartbeat() {
if (this.userSessionHeartbeatTimer !== null) {
window.clearTimeout(this.userSessionHeartbeatTimer);
this.userSessionHeartbeatTimer = null;
}
}

@HostListener('document:visibilitychange')
onVisibilityChange() {
if (document.visibilityState === 'visible') {
this.renewUserSession();
}
}

@HostListener('window:pageshow')
onPageShow() {
this.userSessionReleased = false;
this.renewUserSession();
}

@HostListener('window:pagehide', ['$event'])
onPageHide($event: PageTransitionEvent) {
if ($event.persisted || this.userSessionReleased) {
return;
}

this.userSessionReleased = true;
this.stopUserSessionHeartbeat();
this._http.releaseUserSessionOnPageHide(this.userSessionClientId).catch(() => {});
}

handleLayoutSettingChange(collapsed: boolean) {
this.collapsed = collapsed;
if (collapsed) {
Expand Down Expand Up @@ -164,7 +262,6 @@

@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
this._http.deleteUserSession().subscribe();
if (!environment.production || this.isDirectNavigation) {
return;
}
Expand Down
30 changes: 23 additions & 7 deletions src/app/services/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,28 @@
return Object.assign({}, res[0], res[1]);
}

getUserSession() {
renewUserSession(clientId: string) {
const url = '/api/v1/authentication/user-session/';
return this.get<_User>(url);
return this.post<_User>(url, { client_id: clientId });
}

deleteUserSession() {
releaseUserSessionOnPageHide(clientId: string) {
const url = '/api/v1/authentication/user-session/';
return this.delete<_User>(url);
const csrfToken = getCsrfTokenFromCookie();
const headers: Record<string, string> = {
'Content-Type': 'application/json'
};
if (csrfToken) {
headers['X-CSRFToken'] = csrfToken;
}

return fetch(this.resolveUrl(url), {
method: 'DELETE',
credentials: 'same-origin',
keepalive: true,
headers,
body: JSON.stringify({ client_id: clientId })
});
}

getMyGrantedAssets(keyword) {
Expand Down Expand Up @@ -381,8 +395,7 @@
const secret = encryptPassword(manualAuthInfo.secret);
const connectOption = { ...(connectData.connectOption || {}) };
// 始终以当前表单为准,避免 connectOption 里残留上一次的 input_secret_type
const inputSecretType =
(manualAuthInfo && manualAuthInfo['input_secret_type']) || 'password';
const inputSecretType = (manualAuthInfo && manualAuthInfo['input_secret_type']) || 'password';

Check warning on line 398 in src/app/services/http.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=jumpserver_luna&issues=AZ_-K6-2RdE24u2TF2Zq&open=AZ_-K6-2RdE24u2TF2Zq&pullRequest=1576

const data = {
asset: asset.id,
Expand Down Expand Up @@ -518,7 +531,10 @@
}

getSmartEndpoint({ assetId, sessionId, token }, protocol): Promise<Endpoint> {
const url = new URL(withSitePrefix('/api/v1/terminal/endpoints/smart/'), window.location.origin);
const url = new URL(
withSitePrefix('/api/v1/terminal/endpoints/smart/'),
window.location.origin
);

url.searchParams.append('protocol', protocol);
if (assetId) {
Expand Down