From ea3c159ffa95d72e687022b50a9075b2cb3c8dde Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 2 Dec 2022 07:39:51 -0800 Subject: [PATCH] core: publish beta --- plugins/core/package-lock.json | 4 +- plugins/core/package.json | 2 +- plugins/core/src/converters.ts | 83 ++++++++++++++++++++- plugins/core/src/media-core.ts | 20 ++++- plugins/core/ui/src/common/DevicePicker.vue | 2 +- plugins/core/ui/src/interfaces/Notifier.vue | 53 +++---------- 6 files changed, 114 insertions(+), 50 deletions(-) diff --git a/plugins/core/package-lock.json b/plugins/core/package-lock.json index af66045fd..436b77be1 100644 --- a/plugins/core/package-lock.json +++ b/plugins/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/core", - "version": "0.1.61", + "version": "0.1.62", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/core", - "version": "0.1.61", + "version": "0.1.62", "license": "Apache-2.0", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/core/package.json b/plugins/core/package.json index 90e377f5f..f3a61735c 100644 --- a/plugins/core/package.json +++ b/plugins/core/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/core", - "version": "0.1.61", + "version": "0.1.62", "description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.", "author": "Scrypted", "license": "Apache-2.0", diff --git a/plugins/core/src/converters.ts b/plugins/core/src/converters.ts index b8b20eb3f..d5f15528d 100644 --- a/plugins/core/src/converters.ts +++ b/plugins/core/src/converters.ts @@ -1,4 +1,4 @@ -import { BufferConverter, HttpRequest, HttpRequestHandler, HttpResponse, HttpResponseOptions, ScryptedDeviceBase, ScryptedMimeTypes } from "@scrypted/sdk"; +import { BufferConverter, BufferConvertorOptions, HttpRequest, HttpRequestHandler, HttpResponse, HttpResponseOptions, MediaObject, RequestMediaObject, ScryptedDeviceBase, ScryptedMimeTypes } from "@scrypted/sdk"; import sdk from "@scrypted/sdk"; import mime from "mime/lite"; import path from 'path'; @@ -65,6 +65,87 @@ export class BufferHost extends ScryptedDeviceBase implements HttpRequestHandler } } +export class RequestMediaObjectHost extends ScryptedDeviceBase implements HttpRequestHandler, BufferConverter { + secureHosted = new Map() + insecureHosted = new Map() + + constructor() { + super('rmo-host'); + this.fromMimeType = ScryptedMimeTypes.RequestMediaObject; + this.toMimeType = ScryptedMimeTypes.MediaObject; + // this.toMimeType = secure ? ScryptedMimeTypes.LocalUrl : ScryptedMimeTypes.InsecureLocalUrl; + } + + async onRequest(request: HttpRequest, response: HttpResponse) { + const normalizedRequest = Object.assign({}, request); + normalizedRequest.url = normalizedRequest.url.replace(normalizedRequest.rootPath, ''); + const pathOnly = normalizedRequest.url.split('?')[0]; + const file = this.secureHosted.get(pathOnly) || this.insecureHosted.get(pathOnly);; + + if (!file) { + response.send('Not Found', { + code: 404, + }); + return; + } + + + let options: HttpResponseOptions = { + headers: { + 'Content-Type': file.fromMimeType, + } + }; + + const q = new URLSearchParams(request.url.split('?')[1]); + if (q.has('attachment')) { + options.headers['Content-Disposition'] = 'attachment'; + } + + try { + const mo = await file.request(); + const data = await sdk.mediaManager.convertMediaObjectToBuffer(mo, mo.mimeType); + + response.send(data); + } + catch (e) { + this.secureHosted.delete(pathOnly); + this.insecureHosted.delete(pathOnly); + throw e; + } + } + + async convert(request: RequestMediaObject, fromMimeType: string, toMimeType: string): Promise { + let hosted: typeof this.secureHosted; + if (toMimeType === ScryptedMimeTypes.Url || toMimeType === ScryptedMimeTypes.LocalUrl) { + hosted = this.secureHosted; + toMimeType = ScryptedMimeTypes.LocalUrl; + } + else if (toMimeType === ScryptedMimeTypes.InsecureLocalUrl) { + hosted = this.insecureHosted; + } + else { + return request(); + } + + const uuid = uuidv4(); + + const endpoint = await (toMimeType === ScryptedMimeTypes.LocalUrl ? endpointManager.getPublicLocalEndpoint(this.nativeId) : endpointManager.getInsecurePublicLocalEndpoint(this.nativeId)); + const data = await request(); + fromMimeType = data.mimeType; + const extension = mime.getExtension(fromMimeType); + + const filename = uuid + (extension ? `.${extension}` : ''); + + const pathOnly = `/${filename}`; + hosted.set(pathOnly, { request, fromMimeType, toMimeType }); + // free this resource after an hour. + setTimeout(() => hosted.delete(pathOnly), 1 * 60 * 60 * 1000); + + const url = Buffer.from(`${endpoint}${filename}`); + return sdk.mediaManager.createMediaObject(url, toMimeType); + } +} + export class FileHost extends ScryptedDeviceBase implements HttpRequestHandler, BufferConverter { hosted = new Map() diff --git a/plugins/core/src/media-core.ts b/plugins/core/src/media-core.ts index a914ef6bc..c9c116045 100644 --- a/plugins/core/src/media-core.ts +++ b/plugins/core/src/media-core.ts @@ -1,12 +1,13 @@ import path from 'path'; -import { ScryptedDeviceBase, DeviceProvider, ScryptedInterface, ScryptedDeviceType, BufferConverter, MediaObject, VideoCamera, Camera, ScryptedMimeTypes, RequestMediaStreamOptions, HttpRequestHandler, HttpRequest, HttpResponse } from '@scrypted/sdk'; +import { ScryptedDeviceBase, DeviceProvider, ScryptedInterface, ScryptedDeviceType, BufferConverter, MediaObject, VideoCamera, Camera, ScryptedMimeTypes, RequestMediaStreamOptions, HttpRequestHandler, HttpRequest, HttpResponse, RequestMediaObject } from '@scrypted/sdk'; import sdk from '@scrypted/sdk'; const { systemManager, deviceManager, mediaManager, endpointManager } = sdk; -import { BufferHost, FileHost } from './converters'; +import { RequestMediaObjectHost, FileHost, BufferHost } from './converters'; export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, BufferConverter, HttpRequestHandler { httpHost: BufferHost; httpsHost: BufferHost; + rmoHost: RequestMediaObjectHost; fileHost: FileHost; filesHost: FileHost; @@ -33,6 +34,12 @@ export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, Buf interfaces: [ScryptedInterface.BufferConverter, ScryptedInterface.HttpRequestHandler], type: ScryptedDeviceType.API, }, + { + name: 'RequestMediaObject Host', + nativeId: 'rmo-host', + interfaces: [ScryptedInterface.BufferConverter, ScryptedInterface.HttpRequestHandler], + type: ScryptedDeviceType.API, + }, { name: 'HTTP File Host', nativeId: 'file', @@ -50,6 +57,7 @@ export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, Buf }) this.httpHost = new BufferHost(false); this.httpsHost = new BufferHost(true); + this.rmoHost = new RequestMediaObjectHost(); this.fileHost = new FileHost(false); this.filesHost = new FileHost(true); })(); @@ -105,10 +113,12 @@ export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, Buf if (path === ScryptedInterface.Camera) { if (toMimeType === ScryptedMimeTypes.LocalUrl) return this.getLocalSnapshot(id, path, url.search); - return await systemManager.getDeviceById(id).takePicture() as any; + const rmo: RequestMediaObject = async () => systemManager.getDeviceById(id).takePicture(); + return mediaManager.createMediaObject(rmo, ScryptedMimeTypes.RequestMediaObject); } if (path === ScryptedInterface.VideoCamera) { - return await systemManager.getDeviceById(id).getVideoStream() as any; + const rmo: RequestMediaObject = async () => systemManager.getDeviceById(id).getVideoStream(); + return mediaManager.createMediaObject(rmo, ScryptedMimeTypes.RequestMediaObject); } else { throw new Error('Unrecognized Scrypted Media interface.') @@ -120,6 +130,8 @@ export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, Buf return this.httpHost; if (nativeId === 'https') return this.httpsHost; + if (nativeId === 'rmo-host') + return this.rmoHost; if (nativeId === 'file') return this.fileHost; if (nativeId === 'files') diff --git a/plugins/core/ui/src/common/DevicePicker.vue b/plugins/core/ui/src/common/DevicePicker.vue index 8e82a945f..feb41243a 100644 --- a/plugins/core/ui/src/common/DevicePicker.vue +++ b/plugins/core/ui/src/common/DevicePicker.vue @@ -1,5 +1,5 @@