@oussema_mili/test-pkg-123 1.1.35 → 1.1.36
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.
- package/docker-actions/apps.js +1 -1
- package/docker-actions/registry.js +248 -0
- package/package.json +1 -1
- package/store/registryStore.js +164 -12
- package/websocket-server.js +3 -1
package/docker-actions/apps.js
CHANGED
|
@@ -24,6 +24,10 @@ async function handleRegistryAction(ws, action, payload) {
|
|
|
24
24
|
return await handleSetActiveRegistry(ws, payload);
|
|
25
25
|
case "fetchRegistryImages":
|
|
26
26
|
return await handleFetchRegistryImages(ws, payload);
|
|
27
|
+
case "validateRegistry":
|
|
28
|
+
return await handleValidateRegistry(ws, payload);
|
|
29
|
+
case "updateRegistryCredentials":
|
|
30
|
+
return await handleUpdateRegistryCredentials(ws, payload);
|
|
27
31
|
default:
|
|
28
32
|
throw new Error(`Unknown registry action: ${action}`);
|
|
29
33
|
}
|
|
@@ -148,6 +152,8 @@ async function handleConnectRegistry(ws, payload) {
|
|
|
148
152
|
type,
|
|
149
153
|
connected: true,
|
|
150
154
|
connectedAt: new Date().toISOString(),
|
|
155
|
+
status: "valid", // Mark as valid since we just authenticated
|
|
156
|
+
lastValidated: new Date().toISOString(),
|
|
151
157
|
// Store metadata at top level for API exposure
|
|
152
158
|
...(type === "ecr" && {
|
|
153
159
|
username: extractedUsername,
|
|
@@ -604,6 +610,248 @@ async function handleSetActiveRegistry(ws, payload) {
|
|
|
604
610
|
}
|
|
605
611
|
}
|
|
606
612
|
|
|
613
|
+
async function handleValidateRegistry(ws, payload) {
|
|
614
|
+
try {
|
|
615
|
+
const { id, requestId } = payload;
|
|
616
|
+
|
|
617
|
+
if (!id) {
|
|
618
|
+
throw new Error("Registry ID is required");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Get registry with credentials
|
|
622
|
+
const registry = await registryStore.getRegistryWithCredentials(id);
|
|
623
|
+
if (!registry) {
|
|
624
|
+
throw new Error("Registry not found");
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
let isValid = false;
|
|
628
|
+
let errorMessage = "";
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
const { credentials, type } = registry;
|
|
632
|
+
|
|
633
|
+
switch (type) {
|
|
634
|
+
case "docker-hub":
|
|
635
|
+
isValid = await testDockerHubCredentials(
|
|
636
|
+
credentials.username,
|
|
637
|
+
credentials.password
|
|
638
|
+
);
|
|
639
|
+
break;
|
|
640
|
+
|
|
641
|
+
case "ecr":
|
|
642
|
+
const ecrResult = await testECRCredentials(
|
|
643
|
+
credentials.accessKeyId,
|
|
644
|
+
credentials.secretAccessKey,
|
|
645
|
+
registry.region,
|
|
646
|
+
credentials.sessionToken
|
|
647
|
+
);
|
|
648
|
+
isValid = ecrResult.success;
|
|
649
|
+
break;
|
|
650
|
+
|
|
651
|
+
case "gcr":
|
|
652
|
+
isValid = await testGCRCredentials(
|
|
653
|
+
credentials.serviceAccountJson,
|
|
654
|
+
registry.url
|
|
655
|
+
);
|
|
656
|
+
break;
|
|
657
|
+
|
|
658
|
+
case "acr":
|
|
659
|
+
isValid = await testACRCredentials(
|
|
660
|
+
credentials.username,
|
|
661
|
+
credentials.password,
|
|
662
|
+
registry.url
|
|
663
|
+
);
|
|
664
|
+
break;
|
|
665
|
+
|
|
666
|
+
case "custom":
|
|
667
|
+
isValid = await testCustomRegistryCredentials(
|
|
668
|
+
credentials.username,
|
|
669
|
+
credentials.password,
|
|
670
|
+
registry.url
|
|
671
|
+
);
|
|
672
|
+
break;
|
|
673
|
+
|
|
674
|
+
default:
|
|
675
|
+
throw new Error(`Unsupported registry type: ${type}`);
|
|
676
|
+
}
|
|
677
|
+
} catch (validationError) {
|
|
678
|
+
isValid = false;
|
|
679
|
+
errorMessage = validationError.message;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Update registry status in store
|
|
683
|
+
const status = isValid ? "valid" : "invalid";
|
|
684
|
+
await registryStore.updateRegistryStatus(id, status, new Date().toISOString());
|
|
685
|
+
|
|
686
|
+
// Get updated registry for response
|
|
687
|
+
const updatedRegistry = await registryStore.getRegistryWithCredentials(id);
|
|
688
|
+
const { credentials, ...safeRegistry } = updatedRegistry;
|
|
689
|
+
|
|
690
|
+
ws.send(
|
|
691
|
+
JSON.stringify({
|
|
692
|
+
type: "registryValidated",
|
|
693
|
+
registry: {
|
|
694
|
+
...safeRegistry,
|
|
695
|
+
status,
|
|
696
|
+
lastValidated: new Date().toISOString(),
|
|
697
|
+
},
|
|
698
|
+
isValid,
|
|
699
|
+
errorMessage: isValid ? null : errorMessage,
|
|
700
|
+
requestId,
|
|
701
|
+
})
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
if (isValid) {
|
|
705
|
+
console.log(chalk.green(`✅ Registry "${registry.name}" credentials are valid`));
|
|
706
|
+
} else {
|
|
707
|
+
console.log(chalk.yellow(`⚠️ Registry "${registry.name}" credentials are invalid: ${errorMessage}`));
|
|
708
|
+
}
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.error("Error validating registry:", error);
|
|
711
|
+
ws.send(
|
|
712
|
+
JSON.stringify({
|
|
713
|
+
type: "error",
|
|
714
|
+
error: "Failed to validate registry: " + error.message,
|
|
715
|
+
requestId: payload.requestId,
|
|
716
|
+
})
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async function handleUpdateRegistryCredentials(ws, payload) {
|
|
722
|
+
try {
|
|
723
|
+
const {
|
|
724
|
+
id,
|
|
725
|
+
password,
|
|
726
|
+
accessKeyId,
|
|
727
|
+
secretAccessKey,
|
|
728
|
+
sessionToken,
|
|
729
|
+
serviceAccountJson,
|
|
730
|
+
requestId,
|
|
731
|
+
} = payload;
|
|
732
|
+
|
|
733
|
+
if (!id) {
|
|
734
|
+
throw new Error("Registry ID is required");
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Get existing registry
|
|
738
|
+
const registry = await registryStore.getRegistryWithCredentials(id);
|
|
739
|
+
if (!registry) {
|
|
740
|
+
throw new Error("Registry not found");
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Build new credentials based on registry type
|
|
744
|
+
let newCredentials = {};
|
|
745
|
+
let authenticationValid = false;
|
|
746
|
+
let errorMessage = "";
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
switch (registry.type) {
|
|
750
|
+
case "docker-hub":
|
|
751
|
+
case "acr":
|
|
752
|
+
case "custom":
|
|
753
|
+
if (!password) {
|
|
754
|
+
throw new Error("Password/token is required");
|
|
755
|
+
}
|
|
756
|
+
newCredentials = {
|
|
757
|
+
...registry.credentials,
|
|
758
|
+
password,
|
|
759
|
+
};
|
|
760
|
+
// Validate the new credentials
|
|
761
|
+
if (registry.type === "docker-hub") {
|
|
762
|
+
authenticationValid = await testDockerHubCredentials(
|
|
763
|
+
registry.credentials.username,
|
|
764
|
+
password
|
|
765
|
+
);
|
|
766
|
+
} else if (registry.type === "acr") {
|
|
767
|
+
authenticationValid = await testACRCredentials(
|
|
768
|
+
registry.credentials.username,
|
|
769
|
+
password,
|
|
770
|
+
registry.url
|
|
771
|
+
);
|
|
772
|
+
} else {
|
|
773
|
+
authenticationValid = await testCustomRegistryCredentials(
|
|
774
|
+
registry.credentials.username,
|
|
775
|
+
password,
|
|
776
|
+
registry.url
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
break;
|
|
780
|
+
|
|
781
|
+
case "ecr":
|
|
782
|
+
if (!accessKeyId || !secretAccessKey) {
|
|
783
|
+
throw new Error("AWS Access Key ID and Secret Access Key are required");
|
|
784
|
+
}
|
|
785
|
+
newCredentials = {
|
|
786
|
+
accessKeyId,
|
|
787
|
+
secretAccessKey,
|
|
788
|
+
sessionToken,
|
|
789
|
+
};
|
|
790
|
+
const ecrResult = await testECRCredentials(
|
|
791
|
+
accessKeyId,
|
|
792
|
+
secretAccessKey,
|
|
793
|
+
registry.region,
|
|
794
|
+
sessionToken
|
|
795
|
+
);
|
|
796
|
+
authenticationValid = ecrResult.success;
|
|
797
|
+
break;
|
|
798
|
+
|
|
799
|
+
case "gcr":
|
|
800
|
+
if (!serviceAccountJson) {
|
|
801
|
+
throw new Error("Service Account JSON is required");
|
|
802
|
+
}
|
|
803
|
+
newCredentials = {
|
|
804
|
+
serviceAccountJson,
|
|
805
|
+
};
|
|
806
|
+
authenticationValid = await testGCRCredentials(
|
|
807
|
+
serviceAccountJson,
|
|
808
|
+
registry.url
|
|
809
|
+
);
|
|
810
|
+
break;
|
|
811
|
+
|
|
812
|
+
default:
|
|
813
|
+
throw new Error(`Unsupported registry type: ${registry.type}`);
|
|
814
|
+
}
|
|
815
|
+
} catch (authError) {
|
|
816
|
+
authenticationValid = false;
|
|
817
|
+
errorMessage = authError.message;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (!authenticationValid) {
|
|
821
|
+
throw new Error(errorMessage || "New credentials are invalid");
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Update credentials in store
|
|
825
|
+
const updatedRegistry = await registryStore.updateCredentials(id, newCredentials);
|
|
826
|
+
|
|
827
|
+
console.log(chalk.green(`✅ Successfully updated credentials for registry: ${registry.name}`));
|
|
828
|
+
|
|
829
|
+
// Return safe registry data
|
|
830
|
+
const { credentials, ...safeRegistry } = updatedRegistry;
|
|
831
|
+
|
|
832
|
+
ws.send(
|
|
833
|
+
JSON.stringify({
|
|
834
|
+
type: "registryCredentialsUpdated",
|
|
835
|
+
registry: {
|
|
836
|
+
...safeRegistry,
|
|
837
|
+
status: "valid",
|
|
838
|
+
lastValidated: new Date().toISOString(),
|
|
839
|
+
},
|
|
840
|
+
requestId,
|
|
841
|
+
})
|
|
842
|
+
);
|
|
843
|
+
} catch (error) {
|
|
844
|
+
console.error("Error updating registry credentials:", error);
|
|
845
|
+
ws.send(
|
|
846
|
+
JSON.stringify({
|
|
847
|
+
type: "error",
|
|
848
|
+
error: "Failed to update registry credentials: " + error.message,
|
|
849
|
+
requestId: payload.requestId,
|
|
850
|
+
})
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
607
855
|
async function handleFetchRegistryImages(ws, payload) {
|
|
608
856
|
try {
|
|
609
857
|
const { registryId, requestId } = payload;
|
package/package.json
CHANGED
package/store/registryStore.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import dotenv from "dotenv";
|
|
6
|
+
import { encrypt, decrypt } from "../utils/encryption.js";
|
|
6
7
|
dotenv.config();
|
|
7
8
|
|
|
8
9
|
const fsPromises = fs.promises;
|
|
@@ -14,10 +15,93 @@ const REGISTRY_FILE = path.join(
|
|
|
14
15
|
os.homedir(),
|
|
15
16
|
AGENT_ROOT_DIR,
|
|
16
17
|
REGISTRIES_DIR,
|
|
17
|
-
REGISTRIES_FILE
|
|
18
|
+
REGISTRIES_FILE,
|
|
18
19
|
);
|
|
19
20
|
|
|
20
|
-
const SCHEMA_VERSION =
|
|
21
|
+
const SCHEMA_VERSION = 2; // Bumped for encryption support
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Encrypt sensitive credentials based on registry type
|
|
25
|
+
*/
|
|
26
|
+
function encryptCredentials(credentials, type) {
|
|
27
|
+
if (!credentials) return credentials;
|
|
28
|
+
|
|
29
|
+
const encrypted = { ...credentials };
|
|
30
|
+
|
|
31
|
+
switch (type) {
|
|
32
|
+
case "docker-hub":
|
|
33
|
+
case "acr":
|
|
34
|
+
case "custom":
|
|
35
|
+
if (credentials.password) {
|
|
36
|
+
encrypted.password = encrypt(credentials.password);
|
|
37
|
+
encrypted._encrypted = true;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
case "ecr":
|
|
41
|
+
if (credentials.secretAccessKey) {
|
|
42
|
+
encrypted.secretAccessKey = encrypt(credentials.secretAccessKey);
|
|
43
|
+
encrypted._encrypted = true;
|
|
44
|
+
}
|
|
45
|
+
if (credentials.sessionToken) {
|
|
46
|
+
encrypted.sessionToken = encrypt(credentials.sessionToken);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case "gcr":
|
|
50
|
+
if (credentials.serviceAccountJson) {
|
|
51
|
+
encrypted.serviceAccountJson = encrypt(credentials.serviceAccountJson);
|
|
52
|
+
encrypted._encrypted = true;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return encrypted;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Decrypt sensitive credentials based on registry type
|
|
62
|
+
*/
|
|
63
|
+
function decryptCredentials(credentials, type) {
|
|
64
|
+
if (!credentials || !credentials._encrypted) return credentials;
|
|
65
|
+
|
|
66
|
+
const decrypted = { ...credentials };
|
|
67
|
+
delete decrypted._encrypted;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
switch (type) {
|
|
71
|
+
case "docker-hub":
|
|
72
|
+
case "acr":
|
|
73
|
+
case "custom":
|
|
74
|
+
if (credentials.password) {
|
|
75
|
+
decrypted.password = decrypt(credentials.password);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case "ecr":
|
|
79
|
+
if (credentials.secretAccessKey) {
|
|
80
|
+
decrypted.secretAccessKey = decrypt(credentials.secretAccessKey);
|
|
81
|
+
}
|
|
82
|
+
if (credentials.sessionToken) {
|
|
83
|
+
decrypted.sessionToken = decrypt(credentials.sessionToken);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case "gcr":
|
|
87
|
+
if (credentials.serviceAccountJson) {
|
|
88
|
+
decrypted.serviceAccountJson = decrypt(
|
|
89
|
+
credentials.serviceAccountJson,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(
|
|
96
|
+
chalk.red("❌ Failed to decrypt credentials:", error.message),
|
|
97
|
+
);
|
|
98
|
+
throw new Error(
|
|
99
|
+
"Failed to decrypt credentials. The encryption key may have changed.",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return decrypted;
|
|
104
|
+
}
|
|
21
105
|
|
|
22
106
|
/**
|
|
23
107
|
* Registry Store for managing registry credentials
|
|
@@ -44,7 +128,7 @@ class RegistryStore {
|
|
|
44
128
|
this.initialized = true;
|
|
45
129
|
} catch (error) {
|
|
46
130
|
console.error(
|
|
47
|
-
chalk.red("❌ Failed to initialize registry store:", error.message)
|
|
131
|
+
chalk.red("❌ Failed to initialize registry store:", error.message),
|
|
48
132
|
);
|
|
49
133
|
throw error;
|
|
50
134
|
}
|
|
@@ -72,13 +156,16 @@ class RegistryStore {
|
|
|
72
156
|
const data = await fsPromises.readFile(REGISTRY_FILE, "utf8");
|
|
73
157
|
const parsed = JSON.parse(data);
|
|
74
158
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
159
|
+
// Handle schema migration
|
|
160
|
+
if (parsed.version < SCHEMA_VERSION) {
|
|
161
|
+
console.log(
|
|
162
|
+
chalk.yellow(
|
|
163
|
+
`⚠️ Migrating registry file from version ${parsed.version} to ${SCHEMA_VERSION}`,
|
|
164
|
+
),
|
|
78
165
|
);
|
|
79
166
|
}
|
|
80
167
|
|
|
81
|
-
// Load registries
|
|
168
|
+
// Load registries and decrypt credentials
|
|
82
169
|
let hasActiveRegistry = false;
|
|
83
170
|
|
|
84
171
|
for (const registry of parsed.registries || []) {
|
|
@@ -92,12 +179,20 @@ class RegistryStore {
|
|
|
92
179
|
hasActiveRegistry = true;
|
|
93
180
|
}
|
|
94
181
|
|
|
95
|
-
//
|
|
182
|
+
// Decrypt credentials if they were encrypted
|
|
183
|
+
if (registry.credentials?._encrypted) {
|
|
184
|
+
registry.credentials = decryptCredentials(
|
|
185
|
+
registry.credentials,
|
|
186
|
+
registry.type,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Store registry with decrypted credentials in memory
|
|
96
191
|
this.registries.set(registry.id, registry);
|
|
97
192
|
} catch (error) {
|
|
98
193
|
console.error(
|
|
99
194
|
chalk.red(`❌ Failed to load registry ${registry.id}:`),
|
|
100
|
-
error.message
|
|
195
|
+
error.message,
|
|
101
196
|
);
|
|
102
197
|
// Skip this registry but continue with others
|
|
103
198
|
}
|
|
@@ -123,15 +218,19 @@ class RegistryStore {
|
|
|
123
218
|
}
|
|
124
219
|
|
|
125
220
|
/**
|
|
126
|
-
* Save registries to file with
|
|
221
|
+
* Save registries to file with encrypted credentials
|
|
127
222
|
*/
|
|
128
223
|
async saveRegistries() {
|
|
129
224
|
try {
|
|
130
225
|
const registriesToSave = [];
|
|
131
226
|
|
|
132
227
|
for (const [id, registry] of this.registries.entries()) {
|
|
133
|
-
//
|
|
134
|
-
|
|
228
|
+
// Create a copy and encrypt credentials before saving
|
|
229
|
+
const registryToSave = {
|
|
230
|
+
...registry,
|
|
231
|
+
credentials: encryptCredentials(registry.credentials, registry.type),
|
|
232
|
+
};
|
|
233
|
+
registriesToSave.push(registryToSave);
|
|
135
234
|
}
|
|
136
235
|
|
|
137
236
|
const data = {
|
|
@@ -212,6 +311,10 @@ class RegistryStore {
|
|
|
212
311
|
// Ensure active field is included (default to false if not set)
|
|
213
312
|
metadata.active = registry.active || false;
|
|
214
313
|
|
|
314
|
+
// Include status and lastValidated fields
|
|
315
|
+
metadata.status = registry.status || "unknown";
|
|
316
|
+
metadata.lastValidated = registry.lastValidated || null;
|
|
317
|
+
|
|
215
318
|
result.push(metadata);
|
|
216
319
|
}
|
|
217
320
|
|
|
@@ -321,6 +424,55 @@ class RegistryStore {
|
|
|
321
424
|
const activeRegistry = await this.getActiveRegistry();
|
|
322
425
|
return activeRegistry ? activeRegistry.id : null;
|
|
323
426
|
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Update credentials for an existing registry
|
|
430
|
+
*/
|
|
431
|
+
async updateCredentials(id, newCredentials) {
|
|
432
|
+
await this.initialize();
|
|
433
|
+
|
|
434
|
+
const registry = this.registries.get(id);
|
|
435
|
+
if (!registry) {
|
|
436
|
+
throw new Error("Registry not found");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Update credentials
|
|
440
|
+
registry.credentials = {
|
|
441
|
+
...registry.credentials,
|
|
442
|
+
...newCredentials,
|
|
443
|
+
};
|
|
444
|
+
registry.updatedAt = new Date().toISOString();
|
|
445
|
+
registry.lastValidated = new Date().toISOString();
|
|
446
|
+
registry.status = "valid";
|
|
447
|
+
|
|
448
|
+
this.registries.set(id, registry);
|
|
449
|
+
await this.saveRegistries();
|
|
450
|
+
|
|
451
|
+
return registry;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Update registry status
|
|
456
|
+
*/
|
|
457
|
+
async updateRegistryStatus(id, status, lastValidated = null) {
|
|
458
|
+
await this.initialize();
|
|
459
|
+
|
|
460
|
+
const registry = this.registries.get(id);
|
|
461
|
+
if (!registry) {
|
|
462
|
+
throw new Error("Registry not found");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
registry.status = status;
|
|
466
|
+
if (lastValidated) {
|
|
467
|
+
registry.lastValidated = lastValidated;
|
|
468
|
+
}
|
|
469
|
+
registry.updatedAt = new Date().toISOString();
|
|
470
|
+
|
|
471
|
+
this.registries.set(id, registry);
|
|
472
|
+
await this.saveRegistries();
|
|
473
|
+
|
|
474
|
+
return registry;
|
|
475
|
+
}
|
|
324
476
|
}
|
|
325
477
|
|
|
326
478
|
// Singleton instance
|
package/websocket-server.js
CHANGED
|
@@ -334,7 +334,9 @@ async function routeMessage(ws, clientId, data) {
|
|
|
334
334
|
action === "disconnectRegistry" ||
|
|
335
335
|
action === "renameRegistry" ||
|
|
336
336
|
action === "fetchRegistryImages" ||
|
|
337
|
-
action === "setActiveRegistry"
|
|
337
|
+
action === "setActiveRegistry" ||
|
|
338
|
+
action === "validateRegistry" ||
|
|
339
|
+
action === "updateRegistryCredentials"
|
|
338
340
|
) {
|
|
339
341
|
return await registryHandlers.handleRegistryAction(ws, action, payload);
|
|
340
342
|
}
|