steelseries-sonar-sdk 0.1.1

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.
Files changed (118) hide show
  1. package/.gitattributes +8 -0
  2. package/.github/workflows/tests.yml +18 -0
  3. package/.vscode/extensions.json +8 -0
  4. package/.vscode/settings.json +33 -0
  5. package/LICENSE +21 -0
  6. package/README.md +121 -0
  7. package/_.todo +4 -0
  8. package/biome.json +62 -0
  9. package/http/sonar.http +104 -0
  10. package/package.json +31 -0
  11. package/src/consts/fetch-options-put.ts +3 -0
  12. package/src/enums.ts +88 -0
  13. package/src/exceptions.ts +45 -0
  14. package/src/functions/audio/get-audio-data-classic.ts +31 -0
  15. package/src/functions/audio/get-audio-data-stream.ts +37 -0
  16. package/src/functions/audio/get-audio-mode.ts +11 -0
  17. package/src/functions/audio/set-audio-mode.ts +12 -0
  18. package/src/functions/audio/set-channel-mute-classic.ts +35 -0
  19. package/src/functions/audio/set-channel-mute-streamer.ts +41 -0
  20. package/src/functions/audio/set-channel-volume-classic.ts +37 -0
  21. package/src/functions/audio/set-channel-volume-streamer.ts +43 -0
  22. package/src/functions/chatmix/get-chat-mix-state.ts +20 -0
  23. package/src/functions/chatmix/set-chat-mix-balance.ts +22 -0
  24. package/src/functions/converters/convert-channel-to-api.ts +19 -0
  25. package/src/functions/converters/convert-chat-mix-balance-to-api.ts +6 -0
  26. package/src/functions/converters/convert-chat-mix-balance-to-user.ts +7 -0
  27. package/src/functions/converters/convert-profile-channel-to-api.ts +19 -0
  28. package/src/functions/converters/convert-profile-channel-to-user.ts +19 -0
  29. package/src/functions/converters/convert-volume-to-api.ts +5 -0
  30. package/src/functions/converters/convert-volume-to-user.ts +6 -0
  31. package/src/functions/devices/get-audio-devices.ts +40 -0
  32. package/src/functions/devices/set-audio-device.ts +19 -0
  33. package/src/functions/endpoint/get-app-endpoint.ts +73 -0
  34. package/src/functions/endpoint/get-sonar-endpoint-cached.ts +28 -0
  35. package/src/functions/endpoint/get-sonar-endpoint.ts +88 -0
  36. package/src/functions/profile/get-channel-profiles.ts +26 -0
  37. package/src/functions/profile/get-selected-profiles.ts +17 -0
  38. package/src/functions/profile/set-selected-profile.ts +17 -0
  39. package/src/index.ts +41 -0
  40. package/src/sonar/models/audio-settings/chatmix-data.ts +6 -0
  41. package/src/sonar/models/audio-settings/enums/audio-mode.ts +4 -0
  42. package/src/sonar/models/audio-settings/enums/chat-mix-state.ts +6 -0
  43. package/src/sonar/models/audio-settings/enums/device-data-flow.ts +5 -0
  44. package/src/sonar/models/audio-settings/enums/device-default-roles.ts +6 -0
  45. package/src/sonar/models/audio-settings/enums/device-role.ts +9 -0
  46. package/src/sonar/models/audio-settings/enums/device-state.ts +8 -0
  47. package/src/sonar/models/audio-settings/enums/sonar-channel.ts +8 -0
  48. package/src/sonar/models/audio-settings/enums/streaming-path.ts +4 -0
  49. package/src/sonar/models/audio-settings/volume-info-classic.ts +4 -0
  50. package/src/sonar/models/audio-settings/volume-info-streamer.ts +10 -0
  51. package/src/sonar/models/audio-settings/volume-settings-classic.ts +13 -0
  52. package/src/sonar/models/audio-settings/volume-settings-streamer.ts +13 -0
  53. package/src/sonar/models/config/config.ts +9 -0
  54. package/src/sonar/models/config/enums/VirtualAudioDevice.ts +7 -0
  55. package/src/sonar/models/devices/audio-device.ts +17 -0
  56. package/src/sonar/models/devices/changed-device.ts +7 -0
  57. package/src/sonar/requests/audio-devices/change-audio-device.ts +37 -0
  58. package/src/sonar/requests/audio-devices/request-audio-devices.ts +42 -0
  59. package/src/sonar/requests/chatmix/change-chat-mix-balance.ts +25 -0
  60. package/src/sonar/requests/chatmix/request-chat-mix-state.ts +24 -0
  61. package/src/sonar/requests/mode/change-audio-mode.ts +24 -0
  62. package/src/sonar/requests/mode/request-audio-mode.ts +24 -0
  63. package/src/sonar/requests/profiles/change-selected-config.ts +25 -0
  64. package/src/sonar/requests/profiles/request-configs.ts +24 -0
  65. package/src/sonar/requests/profiles/request-selected-configs.ts +22 -0
  66. package/src/sonar/requests/volume-settings/change-volume-level-classic.ts +27 -0
  67. package/src/sonar/requests/volume-settings/change-volume-level-streamer.ts +29 -0
  68. package/src/sonar/requests/volume-settings/change-volume-mute-classic.ts +27 -0
  69. package/src/sonar/requests/volume-settings/change-volume-mute-streamer.ts +29 -0
  70. package/src/sonar/requests/volume-settings/request-volume-settings-classic.ts +22 -0
  71. package/src/sonar/requests/volume-settings/request-volume-settings-streamer.ts +22 -0
  72. package/src/types/audio-device.ts +7 -0
  73. package/src/types/changed-device.ts +7 -0
  74. package/src/types/channel-volume-classic.ts +4 -0
  75. package/src/types/channel-volume-streamer-path.ts +4 -0
  76. package/src/types/channel-volume-streamer.ts +6 -0
  77. package/src/types/channel-volumes-classic.ts +8 -0
  78. package/src/types/channel-volumes-streamer.ts +8 -0
  79. package/src/types/chat-mix-data.ts +7 -0
  80. package/src/types/profile-option.ts +9 -0
  81. package/tests/e2e/audio/get-audio-data-classic.real.test.ts +11 -0
  82. package/tests/e2e/audio/get-audio-data-stream.real.test.ts +11 -0
  83. package/tests/e2e/audio/get-audio-mode.real.test.ts +12 -0
  84. package/tests/e2e/audio/set-audio-mode.real.test.ts +20 -0
  85. package/tests/e2e/audio/set-channel-mute-classic.real.test.ts +18 -0
  86. package/tests/e2e/audio/set-channel-mute-streamer.real.test.ts +18 -0
  87. package/tests/e2e/audio/set-channel-volume-classic.real.test.ts +17 -0
  88. package/tests/e2e/audio/set-channel-volume-streamer.real.test.ts +22 -0
  89. package/tests/e2e/chatmix/get-chat-mix-state.real.test.ts +25 -0
  90. package/tests/e2e/chatmix/set-chat-mix-balance.real.test.ts +25 -0
  91. package/tests/e2e/devices/get-audio-devices.real.test.ts +29 -0
  92. package/tests/e2e/devices/set-audio-device.real.test.ts +32 -0
  93. package/tests/e2e/enpoint/get-app-endpoint.real.test.ts +19 -0
  94. package/tests/e2e/enpoint/get-sonar-endpoint-cached.real.test.ts +32 -0
  95. package/tests/e2e/enpoint/get-sonar-endpoint.real.test.ts +21 -0
  96. package/tests/e2e/profiles/get-channel-profiles.real.test.ts +21 -0
  97. package/tests/e2e/profiles/get-selected-profiles.real.test.ts +11 -0
  98. package/tests/e2e/profiles/set-selected-profile.real.test.ts +17 -0
  99. package/tests/helpers/get-endpoint-e2e.ts +8 -0
  100. package/tests/unit/audio/get-audio-data-classic.test.ts +57 -0
  101. package/tests/unit/audio/get-audio-data-stream.test.ts +65 -0
  102. package/tests/unit/audio/get-audio-mode.test.ts +58 -0
  103. package/tests/unit/audio/set-audio-mode.test.ts +66 -0
  104. package/tests/unit/audio/set-channel-mute-classic.test.ts +58 -0
  105. package/tests/unit/audio/set-channel-mute-streamer.test.ts +64 -0
  106. package/tests/unit/audio/set-channel-volume-classic.test.ts +58 -0
  107. package/tests/unit/audio/set-channel-volume-streamer.test.ts +64 -0
  108. package/tests/unit/chatmix/get-chat-mix-state.test.ts +70 -0
  109. package/tests/unit/chatmix/set-chat-mix-balance.test.ts +58 -0
  110. package/tests/unit/devices/get-audio-devices.test.ts +59 -0
  111. package/tests/unit/devices/set-audio-devices.test.ts +62 -0
  112. package/tests/unit/endpoint/get-app-endpoint.test.ts +72 -0
  113. package/tests/unit/endpoint/get-sonar-endpoint-cached.test.ts +93 -0
  114. package/tests/unit/endpoint/get-sonar-endpoint.test.ts +140 -0
  115. package/tests/unit/profiles/get-channel-profiles.test.ts +91 -0
  116. package/tests/unit/profiles/get-selected-profiles.test.ts +64 -0
  117. package/tests/unit/profiles/set-selected-profile.test.ts +55 -0
  118. package/tsconfig.json +36 -0
package/.gitattributes ADDED
@@ -0,0 +1,8 @@
1
+ *.ts text eol=lf
2
+ *.json text eol=lf
3
+ *.yml text eol=lf
4
+ *.txt text eol=lf
5
+ *.md text eol=lf
6
+ *.http text eol=lf
7
+ .gitattributes text eol=lf
8
+ bun.lock text eol=lf
@@ -0,0 +1,18 @@
1
+ name: Tests
2
+ on: [push, pull_request]
3
+
4
+ jobs:
5
+ Test:
6
+ runs-on: ubuntu-latest
7
+ steps:
8
+ - uses: actions/checkout@v4
9
+ - uses: oven-sh/setup-bun@v2
10
+ - name: Install Bun
11
+ run: bun install
12
+ - name: Run Tests
13
+ run: bun test --coverage --coverage-reporter=lcov ./tests/unit
14
+ - name: Upload coverage to Codecov
15
+ uses: codecov/codecov-action@v3
16
+ with:
17
+ file: ./coverage/lcov.info
18
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,8 @@
1
+ {
2
+ "recommendations": [
3
+ "biomejs.biome",
4
+ "streetsidesoftware.code-spell-checker",
5
+ "humao.rest-client",
6
+ "gruntfuggly.todo-tree"
7
+ ]
8
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "editor.tabSize": 4,
3
+ "editor.insertSpaces": false,
4
+ "editor.detectIndentation": false,
5
+ "files.eol": "\n",
6
+ "files.encoding": "utf8",
7
+ "files.trimTrailingWhitespace": true,
8
+ "files.insertFinalNewline": true,
9
+ "editor.formatOnSave": true,
10
+ "editor.defaultFormatter": "biomejs.biome",
11
+ "editor.codeActionsOnSave": {
12
+ "source.fixAll.biome": "explicit",
13
+ "source.organizeImports.biome": "explicit"
14
+ },
15
+ "prettier.enable": false,
16
+ "typescript.preferences.importModuleSpecifier": "non-relative",
17
+ "javascript.preferences.importModuleSpecifier": "non-relative",
18
+ "files.associations": {
19
+ "*.json": "jsonc"
20
+ },
21
+ "search.exclude": {
22
+ "**/.git": true,
23
+ "**/node_modules": true,
24
+ "**/build": true
25
+ },
26
+ "files.exclude": {
27
+ "**/.git": true,
28
+ "**/.DS_Store": true,
29
+ "**/node_modules": true,
30
+ "**/build": true
31
+ },
32
+ "cSpell.words": ["CHATMIX", "Redirections", "steelseries", "Torrisi"]
33
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harley Bob Marley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # Unofficial - SteelSeries Sonar SDK [![Tests](https://github.com/harley-codes/steelseries-sonar-sdk-js/actions/workflows/tests.yml/badge.svg)](https://github.com/harley-codes/steelseries-sonar-sdk-js/actions/workflows/tests.yml) [![Codecov](https://codecov.io/github/harley-codes/steelseries-sonar-sdk-js/graph/badge.svg?token=TBF8WTZ19B)](https://codecov.io/github/harley-codes/steelseries-sonar-sdk-js)
2
+
3
+ An SDK for interacting with the SteelSeries Sonar software.
4
+
5
+ ## How It Works
6
+ When Sonar is installed via the SteelSeries GG software - it exposes an API on localhost. The port used is unique every time Sonar is started up. This means the lifecycle of the port can change while you computer is running; depending on logout, app-crash, etc. An entry point in the GG software exposes a second endpoint in the users app data, this endpoint does not change and can be used to get the active endpoint for Sonar.
7
+
8
+ The SDK provides methods for getting the active Sonar endpoint, along with various wrapper functions to simplify the request/response payloads, along with QOL preferences.
9
+
10
+ ## Credit
11
+ Building this SDK would not have been as simple without the work of [wex/sonar-rev](https://github.com/wex/sonar-rev). Their work on reverse engineering how Sonar works was paramount.
12
+
13
+ ## Known Limitation
14
+ Changes made with this SDK are not immediately visible in the SteelSeries GG software. Both the SDK and GG software communicate directly with the Sonar Service API, but the GG UI does not do any polling to auto-refresh when changes are made externally. To see the latest state in GG, you must close and reopen the GG window.
15
+
16
+ _I don't think this will matter to anybody - but feel obliged to mention it._
17
+
18
+ ## SDK Usage
19
+
20
+ #### **Initialize**
21
+ Get the GG endpoint, then get the Sonar endpoint. Getting the Sonar endpoint request can optionally be cached to assist with certain types of application environments. When using the cached option, if an attempt to get the Sonar endpoint fails, the cache timeout will be reset.
22
+
23
+ ```typescript
24
+ const appEndpoint = await getAppEndpoint()
25
+ const sonarEndpoint = await getSonarEndpoint(appEndpoint)
26
+ const sonarEndpoint = await getSonarEndpointCached(appEndpoint, 60 = default)
27
+ ```
28
+
29
+ #### **Audio Mode**
30
+ Sonar can be running in either classic or streamer mode. Depending on this factor, you will need to use different methods when setting or retrieving audio.
31
+ ```typescript
32
+ const mode = await getAudioMode(sonarEndpoint)
33
+ const mode = await setAudioMode(sonarEndpoint, AudioMode.Classic)
34
+ ```
35
+
36
+ #### **Audio Data - Classic**
37
+ Get the audio data for Sonar when in classic mode.
38
+ ```typescript
39
+ // GET
40
+ const audioData = await getAudioDataClassic(sonarEndpoint)
41
+ const auxData = audioData[AudioChannel.Aux]
42
+ // SET
43
+ const auxData = await setChannelVolumeClassic(sonarEndpoint, 75, AudioChannel.Aux)
44
+ const auxData = await setChannelMuteClassic(sonarEndpoint, false, AudioChannel.Aux)
45
+ ```
46
+
47
+ #### **Audio Data - Streamer**
48
+ Get the audio data for Sonar when in streamer mode. Different to classic, the volume data is nested deeper into a division of 'Streaming' and 'Monitoring'.
49
+ ```typescript
50
+ // GET
51
+ const audioData = await getAudioDataStream(sonarEndpoint)
52
+ const auxMonitoringData = audioData
53
+ [AudioChannel.Aux]
54
+ [StreamerPath.Monitoring]
55
+ // SET
56
+ const auxData = await setChannelVolumeStreamer(
57
+ sonarEndpoint, 75,
58
+ AudioChannel.Aux,
59
+ StreamerPath.Monitoring)
60
+ const auxData = await setChannelMuteStreamer(
61
+ sonarEndpoint, false,
62
+ AudioChannel.Aux,
63
+ StreamerPath.Monitoring)
64
+ ```
65
+
66
+ #### **CHATMIX**
67
+ You can change the balance between game, and chat output volume. This requires that CHATMIX is available. If the CHATMIX is being controlled via a Sonar DAC for example, it is not available to be changed via software and will throw an exception.
68
+ ```typescript
69
+ // GET
70
+ const chatmixData = await getChatMixState(sonarEndpoint)
71
+ // SET
72
+ if (chatmixData.state == ChatMixState.Enabled){
73
+ const chatmixData = await setChatMixState(sonarEndpoint, 75)
74
+ }
75
+ ```
76
+
77
+ #### **Audio Devices**
78
+ You can get all available audio hardware, and set specific channels to use that device. Additionally you can also target all output channels when passing the target channel parameter.
79
+ ```typescript
80
+ // GET
81
+ const allDevices = getAudioDevices(sonarEndpoint)
82
+ const outputDevices = getAudioDevices(sonarEndpoint, DeviceFlow.Output)
83
+ // SET
84
+ const updatedChannel = setAudioDevice(
85
+ sonarEndpoint,
86
+ DeviceChannel.Output,
87
+ outputDevices[0].id)
88
+ ```
89
+
90
+ #### **EQ/Config Profiles**
91
+ Configuration make up all the settings when changing options in a specified channel. This can be useful to switch out EQ balances. When setting a profile, you only need an ID as each profile is unique to Channel.
92
+ ```typescript
93
+ // GET
94
+ const selectedProfiles = getSelectedProfiles(sonarEndpoint)
95
+ const selectedAuxProfile = currentProfiles.find(x => x.Channel === ProfileChannel.Aux)
96
+ const favoriteAuxProfiles = getChannelProfiles(sonarEndpoint, ProfileChannel.Aux, true)
97
+ // SET
98
+ const selectedAuxProfile = setSelectedProfile(sonarEndpoint, favoriteAuxProfiles[0].id)
99
+ ```
100
+
101
+ #### **Exception Handling**
102
+ Exceptions are broken up into initialization and requests.
103
+
104
+ `SonarInitializationException` provides a reason to easily determine if it failed to get the config, or if there is an issue with Sonar.
105
+ ```typescript
106
+ catch(error){
107
+ if(error instanceof SonarInitializationException){
108
+ console.error({
109
+ error.message,
110
+ error.reason,
111
+ error.innerException
112
+ })
113
+ }
114
+ if(error instanceof SonarRequestException){
115
+ console.error({
116
+ error.message,
117
+ error.innerException
118
+ })
119
+ }
120
+ }
121
+ ```
package/_.todo ADDED
@@ -0,0 +1,4 @@
1
+ TODO: Use the sonar.http file to build better exception classes.
2
+ TODO: Seperate ./test from package - give its own config
3
+ TODO: Better error handling when not in classic or streamer mode
4
+ TODO: Improve set stream audio response type
package/biome.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true,
7
+ "defaultBranch": "main"
8
+ },
9
+ "files": {
10
+ "ignoreUnknown": false
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "tab",
15
+ "lineEnding": "lf",
16
+ "lineWidth": 120,
17
+ "bracketSameLine": false
18
+ },
19
+ "linter": {
20
+ "enabled": true,
21
+ "rules": {
22
+ "recommended": true,
23
+ "suspicious": {
24
+ "useIsArray": "error"
25
+ },
26
+ "style": {
27
+ "useConsistentCurlyBraces": "error",
28
+ "useNodejsImportProtocol": "error"
29
+ },
30
+ "correctness": {
31
+ "noUnusedImports": "error",
32
+ "noPrivateImports": "error"
33
+ }
34
+ }
35
+ },
36
+ "assist": {
37
+ "enabled": true,
38
+ "actions": {
39
+ "source": {
40
+ "organizeImports": "on",
41
+ "useSortedProperties": "on",
42
+ "useSortedAttributes": "on"
43
+ }
44
+ }
45
+ },
46
+ "javascript": {
47
+ "formatter": {
48
+ "quoteStyle": "single",
49
+ "semicolons": "asNeeded",
50
+ "trailingCommas": "none",
51
+ "arrowParentheses": "always",
52
+ "bracketSameLine": false,
53
+ "bracketSpacing": true,
54
+ "expand": "auto"
55
+ }
56
+ },
57
+ "json": {
58
+ "formatter": {
59
+ "trailingCommas": "none"
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,104 @@
1
+ ### VS Code HTTP request to /chatMix
2
+ @SONAR_ADDRESS=http://127.0.0.1:52335
3
+
4
+ ############
5
+ # CHANNELS #
6
+ ############
7
+
8
+ ### -> GET -> Channel Mode
9
+ GET {{SONAR_ADDRESS}}/mode
10
+
11
+ ### -> SET -> Channel Mode - Classic
12
+ PUT {{SONAR_ADDRESS}}/mode/classic
13
+
14
+ ### -> SET -> Channel Mode - Streamer
15
+ PUT {{SONAR_ADDRESS}}/mode/stream
16
+
17
+ ### -> GET -> Volume Settings - Classic
18
+ GET {{SONAR_ADDRESS}}/volumeSettings/classic
19
+
20
+ ### -> GET -> Volume Settings - Streamer
21
+ GET {{SONAR_ADDRESS}}/volumeSettings/streamer
22
+
23
+ ### -> SET -> Volume Level - Classic - Master - 50%
24
+ PUT {{SONAR_ADDRESS}}/volumeSettings/classic/Master/volume/0.5
25
+
26
+ ### -> SET -> Volume Level - Classic - Mute - TRUE
27
+ PUT {{SONAR_ADDRESS}}/volumeSettings/classic/Master/mute/true
28
+
29
+ ### -> SET -> Volume Level - Classic - Mute - FALSE
30
+ PUT {{SONAR_ADDRESS}}/volumeSettings/classic/Master/mute/false
31
+
32
+ ### -> SET -> Volume Level - Streamer - Monitoring - Master - 50%
33
+ PUT {{SONAR_ADDRESS}}/volumeSettings/streamer/monitoring/master/volume/0.5
34
+
35
+ ### -> SET -> Volume Level - Streamer - Streaming - Master - 50%
36
+ PUT {{SONAR_ADDRESS}}/volumeSettings/streamer/streaming/master/volume/0.5
37
+
38
+ ### -> SET -> Mute - Master - true
39
+ PUT {{SONAR_ADDRESS}}/volumeSettings/classic/master/mute/false
40
+
41
+ ### -> SET -> Mute - Streamer - Streaming - true
42
+ PUT {{SONAR_ADDRESS}}/volumeSettings/streamer/streaming/master/isMuted/false
43
+
44
+
45
+ ###########
46
+ # CHATMIX #
47
+ ###########
48
+
49
+ ### -> GET -> ChatMix
50
+ GET {{SONAR_ADDRESS}}/chatMix
51
+ # {
52
+ # "state": "enabled", "finiteWheel", "differentDeviceSelected", "noDeviceSelected"
53
+ # }
54
+
55
+ ### -> SET -> ChatMix - 70% Game, 30% Chat
56
+ PUT {{SONAR_ADDRESS}}/chatMix?balance=0.3
57
+
58
+ #############
59
+ # Equalizer #
60
+ #############
61
+
62
+ ### -> GET -> EQ Presets - Active
63
+ GET {{SONAR_ADDRESS}}/configs/selected
64
+
65
+ ### -> GET -> EQ Presets
66
+ GET {{SONAR_ADDRESS}}/configs
67
+
68
+ ### -> GET -> EQ Presets - GAME
69
+ GET {{SONAR_ADDRESS}}/configs?vad=game
70
+
71
+ ### -> SET -> EQ Preset - AUX - Music: Bright
72
+ PUT {{SONAR_ADDRESS}}/configs/c75b5225-041c-430a-abda-dd27bbbe9485/select
73
+
74
+ ############
75
+ # Hardware #
76
+ ############
77
+
78
+ ### -> GET -> Audio Devices
79
+ GET {{SONAR_ADDRESS}}/audioDevices
80
+
81
+ ### -> GET -> Audio Devices | Assignable
82
+ GET {{SONAR_ADDRESS}}/audioDevices?removeSteelSeriesVAD=true
83
+
84
+ ### -> GET -> Device - Active
85
+ GET {{SONAR_ADDRESS}}/classicRedirections
86
+
87
+ ### -> SET -> Device - Game - Speakers
88
+ PUT {{SONAR_ADDRESS}}/ClassicRedirections/game/deviceId/{0.0.0.00000000}.{0e5b2eb2-8d10-4e5f-b757-59a3e15def98}
89
+
90
+ ### -> SET -> Device - Game - Headset
91
+ PUT {{SONAR_ADDRESS}}/ClassicRedirections/game/deviceId/{0.0.0.00000000}.{a555a65a-c167-480e-8825-220ccbe42b82}
92
+
93
+ ### -> SET -> Device - Audio Out - Speakers
94
+ PUT {{SONAR_ADDRESS}}/ClassicRedirections/render/deviceId/{0.0.0.00000000}.{0e5b2eb2-8d10-4e5f-b757-59a3e15def98}
95
+
96
+ ### -> SET -> Device - Audio Out - Headset
97
+ PUT {{SONAR_ADDRESS}}/ClassicRedirections/render/deviceId/{0.0.0.00000000}.{a555a65a-c167-480e-8825-220ccbe42b82}
98
+
99
+ ###########
100
+ # Devices #
101
+ ###########
102
+
103
+ ### -> GET -> Audio Devices
104
+ GET {{SONAR_ADDRESS}}/volumeSettings/devices/volumes
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "steelseries-sonar-sdk",
3
+ "version": "0.1.1",
4
+ "description": "An SDK for interacting with the SteelSeries Sonar software.",
5
+ "author": "Harley Torrisi <mail@harleycodes.com>",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/harley-codes/steelseries-sonar-sdk-js"
11
+ },
12
+ "module": "./src/index.ts",
13
+ "devDependencies": {
14
+ "@biomejs/biome": "2.3.11",
15
+ "@types/bun": "latest",
16
+ "globals": "^17.0.0"
17
+ },
18
+ "peerDependencies": {
19
+ "typescript": "^5"
20
+ },
21
+ "scripts": {
22
+ "lint": "biome lint",
23
+ "lint-fix": "biome check --write",
24
+ "test-e2e": "bun test ./tests/e2e/**/*.test.ts",
25
+ "test-e2e-endpoint": "bun test ./tests/e2e/**/*.test.ts -t getSonarEndpoint",
26
+ "test-unit": "bun test ./tests/unit/**/*.test.ts",
27
+ "test-all": "bun test-unit && bun test-e2e",
28
+ "package": "bun build ./src/index.ts --outdir=./dist --target node",
29
+ "package-minify": "bun build ./src/index.ts --outdir=./dist --minify --target node"
30
+ }
31
+ }
@@ -0,0 +1,3 @@
1
+ export const FETCH_OPTIONS_PUT = {
2
+ method: 'PUT'
3
+ } as const
package/src/enums.ts ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Sonar audio mixing channels.
3
+ * @property {@link AudioChannel.Master} - Master volume channel.
4
+ * @property {@link AudioChannel.Game} - Game audio channel.
5
+ * @property {@link AudioChannel.Chat} - Chat audio channel.
6
+ * @property {@link AudioChannel.Media} - Media audio channel.
7
+ * @property {@link AudioChannel.Aux} - Auxiliary audio channel.
8
+ * @property {@link AudioChannel.Mic} - Microphone audio channel.
9
+ */
10
+ export enum AudioChannel {
11
+ Master = 'master',
12
+ Game = 'game',
13
+ Chat = 'chat',
14
+ Media = 'media',
15
+ Aux = 'aux',
16
+ Mic = 'mic'
17
+ }
18
+
19
+ // TODO: Sort this channel duplication nonsense out.
20
+ export enum ProfileChannel {
21
+ Game = 'game',
22
+ Chat = 'chat',
23
+ Media = 'media',
24
+ Aux = 'aux',
25
+ Mic = 'mic'
26
+ }
27
+
28
+ /**
29
+ * Sonar device channels.
30
+ * @property {@link DeviceChannel.Output} - All output channels.
31
+ * @property {@link DeviceChannel.Game} - Game output channel.
32
+ * @property {@link DeviceChannel.Chat} - Chat output channel.
33
+ * @property {@link DeviceChannel.Media} - Media output channel.
34
+ * @property {@link DeviceChannel.Aux} - Auxiliary output channel.
35
+ * @property {@link DeviceChannel.Mic} - Microphone input channel.
36
+ */
37
+ export enum DeviceChannel {
38
+ Output = 'render',
39
+ Game = 'game',
40
+ Chat = 'chat',
41
+ Media = 'media',
42
+ Aux = 'aux',
43
+ Mic = 'mic'
44
+ }
45
+
46
+ /**
47
+ * Sonar Streamer Mode path.
48
+ * @property {@link StreamerPath.Streaming} - What the audience will hear.
49
+ * @property {@link StreamerPath.Monitoring} - What the owner will hear.
50
+ */
51
+ export enum StreamerPath {
52
+ Streaming = 'streaming',
53
+ Monitoring = 'monitoring'
54
+ }
55
+
56
+ /**
57
+ * Sonar mixing state.
58
+ * @property {@link AudioMode.Classic} - Basic mixing.
59
+ * @property {@link AudioMode.Streamer} - Advanced mixing - has stream/monitor path each channel.
60
+ */
61
+ export enum AudioMode {
62
+ Classic = 'classic',
63
+ Streamer = 'stream'
64
+ }
65
+
66
+ /**
67
+ * The state of CHATMIX availability.
68
+ * @property {@link ChatMixState.Enabled} - CHATMIX is enabled and available.
69
+ * @property {@link ChatMixState.FiniteWheel} - CHATMIX is available but the user cannot interact with it via software.
70
+ * @property {@link ChatMixState.DifferentDeviceSelected} - CHATMIX is unavailable because a different output device is selected.
71
+ * @property {@link ChatMixState.NoDeviceSelected} - CHATMIX is unavailable because no output device is selected.
72
+ */
73
+ export enum ChatMixState {
74
+ Enabled = 'enabled',
75
+ FiniteWheel = 'finiteWheel',
76
+ DifferentDeviceSelected = 'differentDeviceSelected',
77
+ NoDeviceSelected = 'noDeviceSelected'
78
+ }
79
+
80
+ /**
81
+ * Type of audio device.
82
+ * @property {@link DeviceFlow.Output} - Device used to render audio (e.g. speakers, headphones).
83
+ * @property {@link DeviceFlow.Input} - Device used to capture audio (e.g. microphones).
84
+ */
85
+ export enum DeviceFlow {
86
+ Output = 'output',
87
+ Input = 'input'
88
+ }
@@ -0,0 +1,45 @@
1
+ type RequestErrorProps = {
2
+ message?: string
3
+ innerException?: Error
4
+ }
5
+
6
+ export enum InitializeErrorReason {
7
+ BadConfig = 'Bad Config',
8
+ OSUnsupported = 'OS Unsupported',
9
+ NotEnabled = 'Not Enabled',
10
+ NotRunning = 'Not Running',
11
+ NotReady = 'Not Ready',
12
+ NotResponding = 'Not Responding',
13
+ NotAvailable = 'Not Available'
14
+ }
15
+
16
+ type InitializeErrorProps = {
17
+ message?: string
18
+ innerException?: Error
19
+ reason: InitializeErrorReason
20
+ }
21
+
22
+ /** Base exception for all handled Sonar related exception. */
23
+ export abstract class SonarException extends Error {
24
+ constructor(message: string, cause?: Error) {
25
+ super(message, { cause })
26
+ super.name = new.target.name
27
+ }
28
+ }
29
+
30
+ /** Thrown when the server responds in an unexpected way. */
31
+ export class SonarRequestException extends SonarException {
32
+ constructor(error?: RequestErrorProps) {
33
+ super(error?.message ?? 'There was an issue communicating with the Sonar service.', error?.innerException)
34
+ }
35
+ }
36
+
37
+ /** Thrown when the server cannot be determined. */
38
+ export class SonarInitializationException extends SonarException {
39
+ public reason: InitializeErrorReason
40
+ constructor(error: InitializeErrorProps) {
41
+ const message = `${error?.message ?? `There was an issue finding the Sonar service: ${error.reason}.`}${error?.innerException ? ' See inner exception.' : ''}`
42
+ super(message, error?.innerException)
43
+ this.reason = error.reason
44
+ }
45
+ }
@@ -0,0 +1,31 @@
1
+ import { AudioChannel } from '@/enums'
2
+ import { convertVolumeToUser } from '@/functions/converters/convert-volume-to-user'
3
+ import type { VolumeInfoClassic } from '@/sonar/models/audio-settings/volume-info-classic'
4
+ import { requestVolumeSettingsClassic } from '@/sonar/requests/volume-settings/request-volume-settings-classic'
5
+ import type { ChannelVolumeClassic } from '@/types/channel-volume-classic'
6
+ import type { ChannelVolumesClassic } from '@/types/channel-volumes-classic'
7
+
8
+ /**
9
+ * Gets audio data for all channels.
10
+ * @param sonarEndpoint Sonar endpoint URL
11
+ * @returns volume in the range of 0 to 100,
12
+ */
13
+ export async function getAudioDataClassic(sonarEndpoint: string): Promise<ChannelVolumesClassic> {
14
+ const data = await requestVolumeSettingsClassic(sonarEndpoint)
15
+ const volumeData: ChannelVolumesClassic = {
16
+ [AudioChannel.Master]: createResponseVolumeData(data.masters.classic),
17
+ [AudioChannel.Game]: data.devices.game && createResponseVolumeData(data.devices.game.classic),
18
+ [AudioChannel.Chat]: data.devices.chatRender && createResponseVolumeData(data.devices.chatRender.classic),
19
+ [AudioChannel.Media]: data.devices.media && createResponseVolumeData(data.devices.media.classic),
20
+ [AudioChannel.Aux]: data.devices.aux && createResponseVolumeData(data.devices.aux.classic),
21
+ [AudioChannel.Mic]: data.devices.chatCapture && createResponseVolumeData(data.devices.chatCapture.classic)
22
+ }
23
+ return volumeData
24
+ }
25
+
26
+ function createResponseVolumeData(volumeData: VolumeInfoClassic): ChannelVolumeClassic {
27
+ return {
28
+ volume: convertVolumeToUser(volumeData.volume),
29
+ isMuted: volumeData.muted
30
+ }
31
+ }
@@ -0,0 +1,37 @@
1
+ import { AudioChannel, StreamerPath } from '@/enums'
2
+ import { convertVolumeToUser } from '@/functions/converters/convert-volume-to-user'
3
+ import type { VolumeInfoStreamer } from '@/sonar/models/audio-settings/volume-info-streamer'
4
+ import { requestVolumeSettingsStreamer } from '@/sonar/requests/volume-settings/request-volume-settings-streamer'
5
+ import type { ChannelVolumeStreamer } from '@/types/channel-volume-streamer'
6
+ import type { ChannelVolumesStreamer } from '@/types/channel-volumes-streamer'
7
+
8
+ /**
9
+ * Gets audio data for all channels.
10
+ * @param sonarEndpoint Sonar endpoint URL
11
+ * @returns volume in the range of 0 to 100,
12
+ */
13
+ export async function getAudioDataStream(sonarEndpoint: string): Promise<ChannelVolumesStreamer> {
14
+ const data = await requestVolumeSettingsStreamer(sonarEndpoint)
15
+ const volumeData: ChannelVolumesStreamer = {
16
+ [AudioChannel.Master]: createResponseVolumeData(data.masters.stream),
17
+ [AudioChannel.Game]: data.devices.game && createResponseVolumeData(data.devices.game.stream),
18
+ [AudioChannel.Chat]: data.devices.chatRender && createResponseVolumeData(data.devices.chatRender.stream),
19
+ [AudioChannel.Media]: data.devices.media && createResponseVolumeData(data.devices.media.stream),
20
+ [AudioChannel.Aux]: data.devices.aux && createResponseVolumeData(data.devices.aux.stream),
21
+ [AudioChannel.Mic]: data.devices.chatCapture && createResponseVolumeData(data.devices.chatCapture.stream)
22
+ }
23
+ return volumeData
24
+ }
25
+
26
+ function createResponseVolumeData(volumeData: VolumeInfoStreamer): ChannelVolumeStreamer {
27
+ return {
28
+ [StreamerPath.Streaming]: {
29
+ volume: convertVolumeToUser(volumeData.streaming.volume),
30
+ isMuted: volumeData.streaming.muted
31
+ },
32
+ [StreamerPath.Monitoring]: {
33
+ volume: convertVolumeToUser(volumeData.monitoring.volume),
34
+ isMuted: volumeData.monitoring.muted
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,11 @@
1
+ import type { AudioMode } from '@/enums'
2
+ import { requestAudioMode } from '@/sonar/requests/mode/request-audio-mode'
3
+
4
+ /**
5
+ * Gets audio data for all channels.
6
+ * @param sonarEndpoint Sonar endpoint URL
7
+ * @returns classic or streamer mode
8
+ */
9
+ export async function getAudioMode(sonarEndpoint: string): Promise<AudioMode> {
10
+ return await requestAudioMode(sonarEndpoint)
11
+ }
@@ -0,0 +1,12 @@
1
+ import type { AudioMode } from '@/enums'
2
+ import { changeAudioMode } from '@/sonar/requests/mode/change-audio-mode'
3
+
4
+ /**
5
+ * Gets audio data for all channels.
6
+ * @param sonarEndpoint Sonar endpoint URL
7
+ * @param audioMode Target audio mode
8
+ * @returns classic or streamer mode
9
+ */
10
+ export async function setAudioMode(sonarEndpoint: string, audioMode: AudioMode): Promise<AudioMode> {
11
+ return await changeAudioMode(sonarEndpoint, audioMode)
12
+ }