1+ import { QueryTarget , Sample , Subscriber } from '@eclipse-zenoh/zenoh-ts'
2+
13import zenoh from '@/libs/zenoh'
24import {
35 ExtensionData ,
@@ -9,26 +11,14 @@ import {
911 UploadProgressEvent ,
1012} from '@/types/kraken'
1113import back_axios from '@/utils/api'
12- import { QueryTarget , Sample , Subscriber } from '@eclipse-zenoh/zenoh-ts '
14+ import { createDeferred } from '@/utils/deferred '
1315
1416const KRAKEN_BASE_URL = '/kraken'
1517const KRAKEN_API_V2_URL = `${ KRAKEN_BASE_URL } /v2.0`
1618const KRAKEN_BASE_ZENOH = 'kraken'
19+ const INSTALL_PROGRESS_TOPIC = `${ KRAKEN_BASE_ZENOH } /extension/install/progress`
1720const ZENOH_QUERY_STANDARD_TIMEOUT = 10000
1821
19- /**
20- * List details of all installed extensions.
21- * @returns {Promise<InstalledExtensionData[]> }
22- */
23- export async function fetchInstalledExtensions ( ) : Promise < InstalledExtensionData [ ] > {
24- const response = await back_axios ( {
25- method : 'get' ,
26- url : `${ KRAKEN_API_V2_URL } /extension/` ,
27- timeout : 10000 ,
28- } )
29-
30- return response . data as InstalledExtensionData [ ]
31- }
3222
3323/**
3424 * List all manifest sources from kraken, uses API v2
@@ -190,78 +180,161 @@ export async function setManifestSourceOrder(identifier: string, order: number):
190180 } )
191181}
192182
183+ function buildInstallQueryKey ( identifier : string , tag : string | undefined , stable : boolean ) : string {
184+ let key = `${ KRAKEN_BASE_ZENOH } /extension/install?identifier=${ encodeURIComponent ( identifier ) } `
185+ if ( tag ) key += `;tag=${ encodeURIComponent ( tag ) } `
186+ if ( ! stable ) key += ';stable=false'
187+ return key
188+ }
189+
190+ type InstallSample =
191+ | { kind : 'error' ; message : string }
192+ | { kind : 'complete' }
193+ | { kind : 'progress' ; raw : string }
194+ | null
195+
196+ function parseInstallSample ( raw : string , identifier : string ) : InstallSample {
197+ let data : { identifier ?: string ; status ?: string ; error ?: string }
198+ try {
199+ data = JSON . parse ( raw )
200+ } catch {
201+ return null
202+ }
203+ if ( data . identifier !== identifier ) return null
204+ if ( data . error ) return { kind : 'error' , message : data . error }
205+ if ( data . status === 'complete' ) return { kind : 'complete' }
206+ return { kind : 'progress' , raw }
207+ }
208+
193209/**
194- * Install an extension to the latest version available
195- * @param {InstalledExtensionData } extension The extension to be installed
210+ * Install an extension to the latest version available.
211+ * The backend publishes the pull progress on `INSTALL_PROGRESS_TOPIC`.
212+ *
213+ * @param {string } identifier The identifier of the extension
196214 * @param {function } progressHandler The progress handler for the download
215+ * @param {string } tag The tag of the extension
216+ * @param {boolean } stable If true, will install the latest stable version, default is true
217+ * @param {number } timeout The timeout for the install
197218 */
198219export async function installExtension (
199- extension : InstalledExtensionData ,
200- progressHandler : ( event : any ) => void ,
220+ identifier : string ,
221+ progressHandler ?: ( fragment : string ) => void ,
222+ tag ?: string ,
223+ stable = true ,
224+ timeout = 600000 ,
201225) : Promise < void > {
202- await back_axios ( {
203- url : `${ KRAKEN_API_V2_URL } /extension/install` ,
204- method : 'POST' ,
205- data : {
206- identifier : extension . identifier ,
207- name : extension . name ,
208- docker : extension . docker ,
209- tag : extension . tag ,
210- enabled : true ,
211- permissions : extension ?. permissions ?? '' ,
212- user_permissions : extension ?. user_permissions ?? '' ,
213- } ,
214- timeout : 600000 ,
215- onDownloadProgress : progressHandler ,
216- } )
226+ const deferred = createDeferred < void > ( )
227+ let subscriber : Subscriber | null = null
228+ let timer : ReturnType < typeof setTimeout > | null = null
229+
230+ async function cleanup ( ) : Promise < void > {
231+ if ( timer !== null ) {
232+ clearTimeout ( timer )
233+ timer = null
234+ }
235+ try {
236+ await subscriber ?. undeclare ( )
237+ } catch {
238+ // The subscriber may already be gone. Ignore cleanup errors.
239+ }
240+ subscriber = null
241+ }
242+
243+ async function handleSample ( sample : Sample ) : Promise < void > {
244+ const result = parseInstallSample ( sample . payload ( ) . to_string ( ) , identifier )
245+ if ( result === null ) return
246+ switch ( result . kind ) {
247+ case 'error' :
248+ cleanup ( ) . finally ( ( ) => deferred . reject ( new Error ( result . message ) ) )
249+ break
250+ case 'complete' :
251+ cleanup ( ) . finally ( ( ) => deferred . resolve ( ) )
252+ break
253+ case 'progress' :
254+ progressHandler ?.( result . raw )
255+ break
256+ default :
257+ break
258+ }
259+ }
260+
261+ // Subscribe before triggering the install.
262+ subscriber = await zenoh . subscriber ( INSTALL_PROGRESS_TOPIC , handleSample )
263+ if ( ! subscriber ) {
264+ throw new Error ( 'Failed to subscribe to install progress topic' )
265+ }
266+ timer = setTimeout (
267+ ( ) => cleanup ( ) . finally ( ( ) => deferred . reject ( new Error ( `Install timed out after ${ timeout } ms` ) ) ) ,
268+ timeout ,
269+ )
270+
271+ try {
272+ const reply = await zenoh . query (
273+ buildInstallQueryKey ( identifier , tag , stable ) ,
274+ QueryTarget . BestMatching ,
275+ timeout ,
276+ )
277+ if ( ! reply || reply . error ) {
278+ throw new Error ( reply ?. error ?? 'Install query failed' )
279+ }
280+ } catch ( error ) {
281+ await cleanup ( )
282+ throw error
283+ }
284+
285+ return deferred . promise
217286}
218287
219288/**
220- * Enable an extension by its identifier and tag, uses API v2
289+ * Enable an extension by its identifier and tag, uses zenoh
221290 * @param {string } identifier The identifier of the extension
222291 * @param {string } tag The tag of the extension
223292 */
224293export async function enableExtension ( identifier : string , tag : string ) : Promise < void > {
225- await back_axios ( {
226- method : 'POST' ,
227- url : ` ${ KRAKEN_API_V2_URL } /extension/ ${ identifier } / ${ tag } /enable` ,
228- timeout : 10000 ,
229- } )
294+ await zenoh . query (
295+ ` ${ KRAKEN_BASE_ZENOH } /extension/enable?identifier= ${ encodeURIComponent ( identifier ) } ;tag= ${ encodeURIComponent ( tag ) } ` ,
296+ QueryTarget . BestMatching ,
297+ ZENOH_QUERY_STANDARD_TIMEOUT ,
298+ )
230299}
231300
232301/**
233- * Disable an extension by its identifier, uses API v2
302+ * Disable an extension by its identifier, uses zenoh
234303 * @param {string } identifier The identifier of the extension
235304 */
236305export async function disableExtension ( identifier : string ) : Promise < void > {
237- await back_axios ( {
238- method : 'POST' ,
239- url : ` ${ KRAKEN_API_V2_URL } /extension/ ${ identifier } /disable` ,
240- timeout : 10000 ,
241- } )
306+ await zenoh . query (
307+ ` ${ KRAKEN_BASE_ZENOH } /extension/disable?identifier= ${ encodeURIComponent ( identifier ) } ` ,
308+ QueryTarget . BestMatching ,
309+ ZENOH_QUERY_STANDARD_TIMEOUT ,
310+ )
242311}
243312
244313/**
245- * Uninstall an extension by its identifier, uses API v2
314+ * Uninstall an extension by its identifier, uses zenoh
246315 * @param {string } identifier The identifier of the extension
247316 */
248- export async function uninstallExtension ( identifier : string ) : Promise < void > {
249- await back_axios ( {
250- method : 'DELETE' ,
251- url : `${ KRAKEN_API_V2_URL } /extension/${ identifier } ` ,
252- } )
317+ export async function uninstallExtension ( identifier : string , tag ?: string ) : Promise < void > {
318+ let queryKey = `${ KRAKEN_BASE_ZENOH } /extension/uninstall?identifier=${ encodeURIComponent ( identifier ) } `
319+ if ( tag ) queryKey += `;tag=${ encodeURIComponent ( tag ) } `
320+
321+ await zenoh . query (
322+ queryKey ,
323+ QueryTarget . BestMatching ,
324+ ZENOH_QUERY_STANDARD_TIMEOUT ,
325+ )
253326}
254327
255328/**
256- * Restart an extension by its identifier, uses API v2
329+ * Restart an extension by its identifier, uses zenoh
257330 * @param {string } identifier The identifier of the extension
258331 */
259332export async function restartExtension ( identifier : string ) : Promise < void > {
260- await back_axios ( {
261- method : 'POST' ,
262- url : ` ${ KRAKEN_API_V2_URL } /extension/ ${ identifier } /restart` ,
263- timeout : 10000 ,
264- } )
333+ await zenoh . query (
334+ ` ${ KRAKEN_BASE_ZENOH } /extension/restart?identifier= ${ encodeURIComponent ( identifier ) } ` ,
335+ QueryTarget . BestMatching ,
336+ ZENOH_QUERY_STANDARD_TIMEOUT ,
337+ )
265338}
266339
267340/**
@@ -284,16 +357,15 @@ export async function updateExtensionToVersion(
284357}
285358
286359/**
287- * List all installed extensions from kraken, uses API v2
360+ * List details of all installed extensions.
361+ * @returns {Promise<InstalledExtensionData[]> | null }
288362 */
289- export async function getInstalledExtensions ( ) : Promise < InstalledExtensionData [ ] > {
290- const response = await back_axios ( {
291- method : 'GET' ,
292- url : `${ KRAKEN_API_V2_URL } /extension/` ,
293- timeout : 30000 ,
294- } )
295-
296- return response . data as InstalledExtensionData [ ]
363+ export async function fetchInstalledExtensions ( ) : Promise < InstalledExtensionData [ ] | null > {
364+ return zenoh . query (
365+ `${ KRAKEN_BASE_ZENOH } /extension/fetch` ,
366+ QueryTarget . BestMatching ,
367+ 30000 ,
368+ )
297369}
298370
299371/**
@@ -350,11 +422,11 @@ export async function uploadExtensionTarFile(
350422 * @returns {Promise<void> }
351423 */
352424export async function keepTemporaryExtensionAlive ( tempTag : string ) : Promise < void > {
353- await back_axios ( {
354- method : 'POST' ,
355- url : ` ${ KRAKEN_API_V2_URL } /extension/upload/keep-alive?temp_tag= ${ tempTag } ` ,
356- timeout : 10000 ,
357- } )
425+ await zenoh . query (
426+ ` ${ KRAKEN_BASE_ZENOH } /extension/upload/keep-alive?temp_tag= ${ encodeURIComponent ( tempTag ) } ` ,
427+ QueryTarget . BestMatching ,
428+ ZENOH_QUERY_STANDARD_TIMEOUT ,
429+ )
358430}
359431
360432/**
@@ -422,9 +494,7 @@ export default {
422494 disabledManifestSource,
423495 setManifestSourcesOrders,
424496 setManifestSourceOrder,
425- updateExtensionToVersion,
426497 installExtension,
427- getInstalledExtensions,
428498 enableExtension,
429499 disableExtension,
430500 uninstallExtension,
0 commit comments