Skip to content
Merged
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
52 changes: 46 additions & 6 deletions src/app/pages/clients/client-details/client-details.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,53 @@
<ion-list *ngIf="serverState | async as state">
<ion-item>
<ion-label>
<h2>Client ID: {{ id }}</h2>
<h2>Connection Status</h2>
<p>Last Seen: {{ client.lastSeen.sec * 1000 | date: 'long' }}</p>
<p>Status: {{ client.connected ? 'Connected' : 'Disconnected' }}</p>
<p>Id: {{ client.id }}</p>

<ng-container *ngIf="state.server.groups">
<ng-container *ngFor="let group of state.server.groups">
<ng-container *ngFor="let client of group.clients">
<p *ngIf="client.id === id">Group: {{ group.name }}</p>
</ng-container>
</ng-container>


</ng-container>
</ion-label>
<ion-button fill="outline" slot="end" (click)="refreshClient()">
<ion-icon name="refresh"></ion-icon>
Refresh
</ion-button>
</ion-item>

<ion-item>
<ion-input label="Name" labelPlacement="floating" type="text" placeholder="Enter client name"
[(ngModel)]="client.config.name" (ngModelChange)="setClientName()"></ion-input>
</ion-item>
<ion-item>
<ion-input label="Latency (in ms)" labelPlacement="floating" type="number" placeholder="Enter latency in ms"
[(ngModel)]="client.config.latency" (ngModelChange)="setClientLatency()"></ion-input>
</ion-item>
<ion-item >
<ion-input type="text" placeholder="Enter client name" [(ngModel)]="client.config.name" (ngModelChange)="setClientName()"></ion-input>
<ion-item lines="none">
<ion-input label="Volume (0-100%)" labelPlacement="floating" type="number" placeholder="Enter volume percentage"
[(ngModel)]="client.config.volume.percent" (ngModelChange)="setClientVolume()"></ion-input>
</ion-item>
<ion-item>
<!-- <ion-label position="floating">Volume</ion-label> -->
<ion-range [(ngModel)]="client.config.volume.percent" (ngModelChange)="setClientVolume()">
<!-- <ion-label slot="start">0%</ion-label>
<ion-label slot="end">100%</ion-label> -->
<ion-icon slot="start" name="volume-low"></ion-icon>
<ion-icon slot="end" name="volume-high"></ion-icon>
</ion-range>
</ion-item>

<!-- group selector -->


<ng-container *ngIf="state.server.groups">
<!-- <ng-container *ngIf="state.server.groups">
<ng-container *ngFor="let group of state.server.groups">
<ng-container *ngFor="let client of group.clients">
<ion-item *ngIf="client.id === id">
Expand All @@ -31,19 +70,20 @@ <h2>{{ client.id }}</h2>
<p>Volume: {{ client.config.volume.percent || 'N/A' }}%</p>
<p>Last Seen: {{ client.lastSeen.sec * 1000 | date }}</p>
<p>Connected: {{ client.connected }}</p>
<p>Latency: {{ client.config.latency }} ms</p>
</ion-label>
</ion-item>
</ng-container>
</ng-container>
</ng-container>
</ng-container> -->
</ion-list>

<ion-accordion-group *ngIf="client">
<ion-accordion value="{{ client.id }}">
<ion-item slot="header">
<ion-label>
<h2>{{ client.id }}</h2>
<p>{{ client.connected}}</p>
<p>Raw json</p>
</ion-label>
</ion-item>
<ion-item slot="content" lines="none" color="dark">
Expand Down
49 changes: 48 additions & 1 deletion src/app/pages/clients/client-details/client-details.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
id?: string;

serverState?: Observable<SnapCastServerStatusResponse>;
client?: Client;
public client?: Client;


constructor(
Expand Down Expand Up @@ -66,5 +66,52 @@
}


setClientLatency() {
if (!this.client || !this.client.id) {
console.error('ClientDetailsPage: No client or client ID available to set latency');
return;
}
this.snapcastService.setClientLatency(this.client.id, this.client.config.latency).subscribe({
next: () => {
console.log(`ClientDetailsPage: Successfully set latency for client ${this.client.id} to ${this.client.config.latency}`);
},
error: (err) => {
console.error(`ClientDetailsPage: Failed to set latency for client ${this.client.id}`, err);
}
});
}

setClientVolume() {
if (!this.client || !this.client.id) {
console.error('ClientDetailsPage: No client or client ID available to set volume');
return;
}
this.snapcastService.setClientVolumePercent(this.client.id, this.client.config.volume.percent).subscribe({
next: () => {
console.log(`ClientDetailsPage: Successfully set volume for client ${this.client.id} to ${this.client.config.volume.percent}`);
},
error: (err) => {
console.error(`ClientDetailsPage: Failed to set volume for client ${this.client.id}`, err);
}
});
}

refreshClient() {
if (!this.id) {
console.error('ClientDetailsPage: No ID available to refresh client');
return;
}
this.snapcastService.getClientStatus(this.id).subscribe({
next: () => {
console.log(`ClientDetailsPage: Successfully refreshed client ${this.id}`);
this.snapcastService.refreshState(); // Refresh the server state to get the latest data
},
error: (err) => {
console.error(`ClientDetailsPage: Failed to refresh client ${this.id}`, err);

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.

Copilot Autofix

AI 6 months ago

To address the issue, the id value should be sanitized or validated before being used in the log message. A simple and effective approach is to explicitly cast the id to a string and escape any potentially harmful characters. Alternatively, the %s format specifier can be used in the log message to ensure that the id is treated as a string.

The fix involves:

  1. Updating the log message on line 110 to use a %s format specifier and passing the id as a separate argument.
  2. Ensuring that the id is properly sanitized or validated before use.

Suggested changeset 1
src/app/pages/clients/client-details/client-details.page.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/pages/clients/client-details/client-details.page.ts b/src/app/pages/clients/client-details/client-details.page.ts
--- a/src/app/pages/clients/client-details/client-details.page.ts
+++ b/src/app/pages/clients/client-details/client-details.page.ts
@@ -109,3 +109,3 @@
       error: (err) => {
-        console.error(`ClientDetailsPage: Failed to refresh client ${this.id}`, err);
+        console.error('ClientDetailsPage: Failed to refresh client %s', this.id, err);
       }
EOF
@@ -109,3 +109,3 @@
error: (err) => {
console.error(`ClientDetailsPage: Failed to refresh client ${this.id}`, err);
console.error('ClientDetailsPage: Failed to refresh client %s', this.id, err);
}
Copilot is powered by AI and may make mistakes. Always verify output.
}
});
}



}
55 changes: 51 additions & 4 deletions src/app/services/snapcast.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,27 @@
console.warn(`SnapcastService: Client disconnected but not found in current state: ${disconnectParams.id}`);
}
break;
case 'Client.OnNameChanged':
const nameParams = notification.params as { id: string; name: string };
const clientName = server.groups.flatMap(g => g.clients).find(c => c.id === nameParams.id);
if (clientName) {
clientName.config.name = nameParams.name;
console.log(`SnapcastService: Client ${clientName.id} name changed to ${nameParams.name}`);
} else {
console.warn(`SnapcastService: Client name change for unknown client ID: ${nameParams.id}`);
}
break;

case 'Client.OnLatencyChanged':
const latencyParams = notification.params as { id: string; latency: number };
const clientLatency = server.groups.flatMap(g => g.clients).find(c => c.id === latencyParams.id);
if (clientLatency) {
clientLatency.config.latency = latencyParams.latency;
console.log(`SnapcastService: Client ${clientLatency.id} latency changed to ${latencyParams.latency}`);
} else {
console.warn(`SnapcastService: Client latency change for unknown client ID: ${latencyParams.id}`);
}
break;


case 'Group.OnMute':
Expand Down Expand Up @@ -299,6 +320,8 @@

// CLIENT ACTIONS



public setClientVolumePercent(clientId: string, percent: number): Observable<void> {
if (percent < 0 || percent > 100) return throwError(() => new Error('Volume percentage must be between 0 and 100.'));

Expand Down Expand Up @@ -333,7 +356,31 @@
);
}


setClientLatency(clientId: string, latency: number): Observable<void> {
if (latency < 0) return throwError(() => new Error('Latency must be a non-negative number.'));
return this.rpc('Client.SetLatency', { id: clientId, latency }).pipe(
map((): void => void 0),
catchError(err => {
console.error(`SnapcastService: Failed to set latency for client ${clientId}`, err);
return throwError(() => err);
})
);
}

// Function added just for completeness, not used in the app yet.
getClientStatus(id: string): Observable<Client | undefined> {
return this.rpc('Client.GetStatus', { id }).pipe(
map(response => response.result as Client | undefined),
catchError(err => {
console.error(`SnapcastService: Failed to get status for client ${id}`, err);

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.

Copilot Autofix

AI 6 months ago

To fix the issue, the untrusted id parameter should be passed as a separate argument to the format string using the %s specifier. This ensures that the id is treated as a string and prevents any unintended format specifiers from being interpreted. The fix involves modifying the console.error statement on line 375 of src/app/services/snapcast.service.ts.

Suggested changeset 1
src/app/services/snapcast.service.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/app/services/snapcast.service.ts b/src/app/services/snapcast.service.ts
--- a/src/app/services/snapcast.service.ts
+++ b/src/app/services/snapcast.service.ts
@@ -374,3 +374,3 @@
       catchError(err => {
-        console.error(`SnapcastService: Failed to get status for client ${id}`, err);
+        console.error('SnapcastService: Failed to get status for client %s', id, err);
         return throwError(() => err);
EOF
@@ -374,3 +374,3 @@
catchError(err => {
console.error(`SnapcastService: Failed to get status for client ${id}`, err);
console.error('SnapcastService: Failed to get status for client %s', id, err);
return throwError(() => err);
Copilot is powered by AI and may make mistakes. Always verify output.
return throwError(() => err);
})
);
}




setGroupName(groupId: string, name: string): Observable<void> {
return this.rpc('Group.SetName', { id: groupId, name }).pipe(
map((): void => void 0),
Expand Down Expand Up @@ -362,7 +409,7 @@

}





Expand All @@ -373,8 +420,8 @@
}

public mockServerState(): void {
const url = "assets/mock/json/server-state.json"
this.http.get<SnapCastServerStatusResponse>(url).subscribe({
const url = "assets/mock/json/server-state.json"
this.http.get<SnapCastServerStatusResponse>(url).subscribe({
next: (data) => {
console.log('Mock server state loaded:', data);
this.stateSubject$.next(data);
Expand Down