diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..ecc94feb2 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,132 @@ +name: Node CI and Release + +on: + push: + branches: + - '**' + tags: + - '*' + pull_request: + branches: + - '**' + +jobs: + lint: + runs-on: ubuntu-20.04 + env: + IS_CI_ENV: true + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Run linting checks + run: | + cd types && npm ci --loglevel=error && npm run lint && cd .. + cd browse && npm ci --loglevel=error && npm run lint && cd .. + cd api && npm ci --loglevel=error && npm run lint && cd .. + cd integration-tests && npm ci --loglevel=error && npm run lint && cd .. + + test: + runs-on: ubuntu-20.04 + strategy: + matrix: + batch: [ci-batch-1, ci-batch-2] + services: + docker: + image: docker:19.03.12 + options: --privileged + ports: + - 2375:2375 + env: + DOCKER_TLS_CERTDIR: "" + + env: + IS_CI_ENV: true + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Install Docker Compose + run: | + sudo curl -L "https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose --version # Verify installation + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Set up Docker environment + run: | + docker-compose build + docker-compose up --force-recreate &> dockerstart.log & + node ./api/waitForApi.js || { docker ps; docker-compose logs; exit 1; } + + - name: Apply migration undo and redo to test undo migration + run: | + docker-compose exec server /app/api/node_modules/.bin/sequelize db:migrate:undo --config /app/api/config/app_test_default.js --migrations-path /app/api/migrations + docker-compose exec server /app/api/node_modules/.bin/sequelize db:migrate --config /app/api/config/app_test_default.js --migrations-path /app/api/migrations + + - name: Run Cypress tests + env: + BATCH: ${{ matrix.batch }} + run: | + cd integration-tests + npm ci --loglevel=error + npm run $BATCH + + build: + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + env: + IS_CI_ENV: true + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install global npm packages + run: npm install -g --loglevel=error npm json apidoc@0.50.0 + + - name: Generate API docs + run: | + cd api + npm update --loglevel=error apidoc-plugin-ts + npm run apidoc + + - name: Prepare for deployment + run: | + version=${GITHUB_REF#refs/tags/v} + mkdir /tmp/nfpm + curl -sL https://github.com/goreleaser/nfpm/releases/download/v0.9.5/nfpm_0.9.5_Linux_x86_64.tar.gz | tar -C /tmp/nfpm -xzf - + export PATH=/tmp/nfpm:$PATH + _release/build.sh ${version} + + - name: Upload release assets + uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*.deb" + allowUpdates: true diff --git a/.travis.yml b/.travis_old.yml similarity index 100% rename from .travis.yml rename to .travis_old.yml diff --git a/_release/build.sh b/_release/build.sh index 459149bef..b3552bdd7 100755 --- a/_release/build.sh +++ b/_release/build.sh @@ -32,15 +32,15 @@ cd ${build_dir} echo "Building Cacophony Browse-Next" cd browse-next npm version --no-git-tag-version ${version} -rm -rf node_modules -npm install +echo "Installing dependencies" +npm ci npm run build rm -rf node_modules echo "Installing shared type definitions" cd ../types -rm -rf node_modules -npm install +echo "Installing dependencies" +npm ci echo "Compiling TypeScript..." ./node_modules/.bin/tsc npm run generate-schemas @@ -49,9 +49,7 @@ cd .. echo "Installing dependencies for build..." cd api - -rm -rf node_modules -npm install +npm ci echo "Compiling TypeScript..." ./node_modules/.bin/tsc @@ -59,10 +57,8 @@ echo "Compiling TypeScript..." echo "Creating API docs..." npm update apidoc-plugin-ts npm run apidoc - echo "Removing external dependencies..." rm -rf node_modules - echo "Removing TypeScript files..." find -name '*.ts' -print0 | xargs -0 rm @@ -72,9 +68,11 @@ find -name '*.ts' -print0 | xargs -0 rm echo "Building Cacophony Browse" cd ../browse npm version --no-git-tag-version ${version} -rm -rf node_modules -npm install +echo "Installing dependencies" +npm ci +echo "Running build with webpack" npm run release +echo "Cleaning up node_modules" rm -rf node_modules cd ../types diff --git a/api/api/V1/Admin.ts b/api/api/V1/Admin.ts index 82dd2db1f..9d3df01dc 100755 --- a/api/api/V1/Admin.ts +++ b/api/api/V1/Admin.ts @@ -33,6 +33,7 @@ import { SuperUsers } from "@/Globals.js"; interface ApiUpdateGlobalPermissionRequestBody { permission: UserGlobalPermission; // Permission to apply for user } + export default function (app: Application, baseUrl: string) { const apiUrl = `${baseUrl}/admin`; { diff --git a/api/api/V1/Device.ts b/api/api/V1/Device.ts index 075df5384..f6a4c595a 100755 --- a/api/api/V1/Device.ts +++ b/api/api/V1/Device.ts @@ -88,6 +88,7 @@ import type { ApiStationResponse } from "@typedefs/api/station.js"; import { mapStation } from "@api/V1/Station.js"; import { mapTrack } from "@api/V1/Recording.js"; import { createEntityJWT } from "@api/auth.js"; +import logger from "@log"; const models = await modelsInit(); @@ -387,6 +388,7 @@ export default function (app: Application, baseUrl: string) { }, }); if (hasRecording) { + logger.info("Setting device %s with recordings inactive", deviceId); await response.locals.device.update({ active: false, }); @@ -460,6 +462,21 @@ export default function (app: Application, baseUrl: string) { } ); + app.get( + `${apiUrl}/latest-software-versions`, + extractJwtAuthorizedUser, + async function (request: Request, response: Response) { + const result = await ( + await fetch( + "https://raw.githubusercontent.com/TheCacophonyProject/salt-version-info/main/salt-version-info.json" + ) + ).json(); + return successResponse(response, "Got latest software versions.", { + versions: result, + }); + } + ); + /** * @api {get} /api/v1/devices/:deviceId Get a single device by its unique id * @apiName GetDeviceById @@ -508,8 +525,8 @@ export default function (app: Application, baseUrl: string) { query("view-mode").optional().equals("user"), deprecatedField(query("where")), // Sidekick anyOf( - query("onlyActive").optional().isBoolean().toBoolean(), - query("only-active").optional().isBoolean().toBoolean() + query("onlyActive").default(false).isBoolean().toBoolean(), + query("only-active").default(false).isBoolean().toBoolean() ), ]), fetchAuthorizedRequiredDeviceById(param("id")), @@ -532,8 +549,8 @@ export default function (app: Application, baseUrl: string) { query("view-mode").optional().equals("user"), deprecatedField(query("where")), // Sidekick anyOf( - query("onlyActive").optional().isBoolean().toBoolean(), - query("only-active").optional().isBoolean().toBoolean() + query("onlyActive").default(false).isBoolean().toBoolean(), + query("only-active").default(false).isBoolean().toBoolean() ), ]), fetchAuthorizedRequiredDeviceById(param("id")), @@ -1289,6 +1306,7 @@ export default function (app: Application, baseUrl: string) { validateFields([ idOf(param("id")), body("maskRegions").custom(jsonSchemaOf(MaskRegionsSchema)), + booleanOf(query("only-active"), false), ]), fetchAuthorizedRequiredDeviceById(param("id")), async (request: Request, response: Response, next: NextFunction) => { @@ -1397,6 +1415,7 @@ export default function (app: Application, baseUrl: string) { validateFields([ idOf(param("id")), query("at-time").default(new Date().toISOString()).isISO8601().toDate(), + booleanOf(query("only-active"), false), ]), fetchAuthorizedRequiredDeviceById(param("id")), async (request: Request, response: Response, next: NextFunction) => { @@ -1510,6 +1529,7 @@ export default function (app: Application, baseUrl: string) { validateFields([ idOf(param("id")), body("settings").custom(jsonSchemaOf(ApiDeviceHistorySettingsSchema)), + booleanOf(query("only-active"), false), ]), fetchAuthorizedRequiredDeviceById(param("id")), async (request: Request, response: Response, next: NextFunction) => { @@ -1563,6 +1583,7 @@ export default function (app: Application, baseUrl: string) { body("setStationAtTime").custom( jsonSchemaOf(ApiDeviceLocationFixupSchema) ), + booleanOf(query("only-active"), false), ]), fetchAdminAuthorizedRequiredDeviceById(param("id")), parseJSONField(body("setStationAtTime")), @@ -1835,7 +1856,7 @@ export default function (app: Application, baseUrl: string) { validateFields([ nameOrIdOf(param("groupIdOrName")), nameOf(param("deviceName")), - query("only-active").optional().isBoolean().toBoolean(), + booleanOf(query("only-active"), false), query("view-mode").optional().equals("user"), ]), fetchAuthorizedRequiredDeviceInGroup( @@ -1907,7 +1928,7 @@ export default function (app: Application, baseUrl: string) { extractJwtAuthorizedUser, validateFields([ idOf(query("deviceId")), - query("only-active").optional().isBoolean().toBoolean(), + booleanOf(query("only-active"), false), query("view-mode").optional().equals("user"), ]), // Should this require admin access to the device? @@ -1921,7 +1942,7 @@ export default function (app: Application, baseUrl: string) { extractJwtAuthorizedUser, validateFields([ idOf(param("deviceId")), - query("only-active").optional().isBoolean().toBoolean(), + booleanOf(query("only-active"), false), query("view-mode").optional().equals("user"), ]), // Should this require admin access to the device? @@ -2055,8 +2076,9 @@ export default function (app: Application, baseUrl: string) { nameOf(body("newGroup")), validNameOf(body("newName")), validPasswordOf(body("newPassword")), - // NOTE: Reregister only works on currently active devices + // NOTE: Re-register only works on currently active devices ]), + // FIXME: Should you really be allowed to move a device into a group you aren't an admin of? fetchUnauthorizedRequiredGroupByNameOrId(body("newGroup")), async function (request: Request, response: Response, next: NextFunction) { const requestDevice: Device = await models.Device.findByPk( @@ -2142,30 +2164,49 @@ export default function (app: Application, baseUrl: string) { */ app.post( `${apiUrl}/reregister-authorized`, + extractJwtAuthorisedDevice, + extractJwtAuthorizedUserFromBody("authorizedToken"), validateFields([ nameOf(body("newGroup")), validNameOf(body("newName")), validPasswordOf(body("newPassword")), body("authorizedToken").exists(), ]), - extractJwtAuthorisedDevice, - extractJwtAuthorizedUserFromBody("authorizedToken"), fetchAuthorizedRequiredGroupByNameOrId(body("newGroup")), - async function (request: Request, response: Response, next: NextFunction) { + async (request: Request, response: Response, next: NextFunction) => { + // The user should be the admin of both groups const requestDevice: Device = await models.Device.findByPk( response.locals.requestDevice.id ); - const newDevice = await requestDevice.reRegister( + if (!requestDevice) { + return next( + new ClientError( + `device not found: ${response.locals.requestDevice.id}` + ) + ); + } + response.locals.requestDevice = requestDevice; + response.locals.destGroup = response.locals.group; + if (response.locals.group.id !== response.locals.requestDevice.GroupId) { + await fetchAdminAuthorizedRequiredGroupByNameOrId( + requestDevice.GroupId + )(request, response, next); + } else { + return next(); + } + }, + async function (request: Request, response: Response, next: NextFunction) { + const newDevice = await response.locals.requestDevice.reRegister( models, request.body.newName, - response.locals.group, + response.locals.destGroup, request.body.newPassword, true ); if (newDevice === false) { return next( new ClientError( - `already a device in group '${response.locals.group.groupName}' with the name '${request.body.newName}'` + `already a device in group '${response.locals.destGroup.groupName}' with the name '${request.body.newName}'` ) ); } @@ -2365,7 +2406,7 @@ export default function (app: Application, baseUrl: string) { idOf(param("deviceId")), query("from").isISO8601().toDate().default(new Date()), integerOfWithDefault(query("window-size"), 2160), // Default to a three month rolling window - booleanOf(query("only-active")).optional(), + booleanOf(query("only-active"), false), ]), fetchAuthorizedRequiredDeviceById(param("deviceId")), async function (request: Request, response: Response) { @@ -2421,7 +2462,10 @@ export default function (app: Application, baseUrl: string) { app.get( `${apiUrl}/:deviceId/history`, extractJwtAuthorizedUser, - validateFields([idOf(param("deviceId"))]), + validateFields([ + idOf(param("deviceId")), + booleanOf(query("only-active"), false), + ]), fetchAuthorizedRequiredDeviceById(param("deviceId")), async function (request: Request, response: Response) { const history = await models.DeviceHistory.findAll({ diff --git a/api/api/V1/Event.ts b/api/api/V1/Event.ts index 09d533e4d..7451b5449 100755 --- a/api/api/V1/Event.ts +++ b/api/api/V1/Event.ts @@ -50,6 +50,7 @@ import { HttpStatusCode } from "@typedefs/api/consts.js"; import util from "@api/V1/util.js"; import { streamS3Object } from "@api/V1/signedUrl.js"; import config from "@config"; +import { QueryTypes } from "sequelize"; const models = await modelsInit(); const EVENT_TYPE_REGEXP = /^[A-Z0-9/-]+$/i; @@ -606,6 +607,65 @@ export default function (app: Application, baseUrl: string) { } ); + /** + * @api {get} /api/v1/events/event-types Return distinct known event-types. + * @apiName GetKnownEventTypes + * @apiGroup Events + * @apiDescription Get all known event types + * + * @apiUse V1UserAuthorizationHeader + * + * @apiUse V1ResponseSuccess + * @apiUse V1ResponseError + */ + app.get( + `${apiUrl}/event-types`, + extractJwtAuthorizedUser, + async (_request: Request, response: Response) => { + const eventTypes = await models.sequelize.query( + `select distinct type from "DetailSnapshots"`, + { type: QueryTypes.SELECT } + ); + return successResponse(response, "Got event types", { + eventTypes: (eventTypes as { type: string }[]).map(({ type }) => type), + }); + } + ); + + /** + * @api {get} /api/v1/events/event-types/for-device/:deviceId Return distinct known event-types for a given device. + * @apiName GetKnownRecentEventTypesForDevice + * @apiGroup Events + * @apiDescription Get known event types for a device in the last month + * + * @apiUse V1UserAuthorizationHeader + * + * @apiUse V1ResponseSuccess + * @apiUse V1ResponseError + */ + app.get( + `${apiUrl}/event-types/for-device/:deviceId`, + extractJwtAuthorizedUser, + fetchAuthorizedRequiredDeviceById(param("deviceId")), + async (_request: Request, response: Response) => { + const eventTypes = await models.sequelize.query( + ` + select distinct + type + from "DetailSnapshots" ds + inner join "Events" e + on ds.id = e."EventDetailId" + where e."DeviceId" = ${response.locals.device.id} + and e."dateTime" > now() - interval '1 month' + `, + { type: QueryTypes.SELECT } + ); + return successResponse(response, "Got event types", { + eventTypes: (eventTypes as { type: string }[]).map(({ type }) => type), + }); + } + ); + /** * @api {get} /api/v1/events/:id Return an event given an event id. * @apiName GetEventById diff --git a/api/api/V1/Recording.ts b/api/api/V1/Recording.ts index 7d5fb2798..036d6923e 100755 --- a/api/api/V1/Recording.ts +++ b/api/api/V1/Recording.ts @@ -2463,6 +2463,7 @@ export default (app: Application, baseUrl: string) => { query("sub-class-tags").default(true).isBoolean().toBoolean(), query("include-deleted").default(false).isBoolean().toBoolean(), query("time-sensitive").default(false).isBoolean().toBoolean(), + query("status-recordings").default(false).isBoolean().toBoolean(), query("tag-mode") .default(TagMode.Any) .isString() @@ -2553,6 +2554,9 @@ export default (app: Application, baseUrl: string) => { const includeDeletedRecordings = query[ "include-deleted" ] as unknown as boolean; + const statusRecordingsOnly = query[ + "status-recordings" + ] as unknown as boolean; const locations = ((query.locations || []) as string[]).map(Number); const devices = ((query.devices || []) as string[]).map(Number); const minDuration = query.duration as unknown as number; @@ -2631,6 +2635,7 @@ export default (app: Application, baseUrl: string) => { models, projectId, minDuration, + statusRecordingsOnly, includeDeletedRecordings, types, processingState, @@ -2662,6 +2667,7 @@ export default (app: Application, baseUrl: string) => { models, projectId, minDuration, + statusRecordingsOnly, includeDeletedRecordings, types, processingState, @@ -2696,11 +2702,13 @@ export default (app: Application, baseUrl: string) => { dateTimeMinusThreeMonths(untilDateTime) ); let timeLimitReached = false; + while (accumulatedRecordingIds.length < requestedLimit) { const recordings = await queryRecordingsInProject( models, projectId, minDuration, + statusRecordingsOnly, includeDeletedRecordings, types, processingState, diff --git a/api/api/V1/Station.ts b/api/api/V1/Station.ts index 0efc201fb..b129a3d82 100755 --- a/api/api/V1/Station.ts +++ b/api/api/V1/Station.ts @@ -440,11 +440,19 @@ export default function (app: Application, baseUrl: string) { newName && otherActiveStationsInTimeWindow.find(({ name }) => name === newName) ) { - return next( - new ClientError( - `An active station with the name ${newName} already exists between ${activeAt.toISOString()} and ${retiredAt.toISOString()}` - ) - ); + if (activeAt && retiredAt) { + return next( + new ClientError( + `An active station with the name '${newName}' already exists between ${activeAt.toISOString()} and ${retiredAt.toISOString()}` + ) + ); + } else { + return next( + new ClientError( + `An active station with the name '${newName}' already exists.` + ) + ); + } } if (positionUpdated) { diff --git a/api/api/V1/recordingUtil.ts b/api/api/V1/recordingUtil.ts index cf3d1befb..ad4c51da0 100755 --- a/api/api/V1/recordingUtil.ts +++ b/api/api/V1/recordingUtil.ts @@ -254,8 +254,13 @@ export async function getCPTVFrames( const totalFrames = header.totalFrames || null; let numFrames = 0; while (!finished) { - const frame: CptvFrame | null = await decoder.getNextFrame(); - if (frame && frame.meta.isBackgroundFrame) { + const frame: CptvFrame | null | string = await decoder.getNextFrame(); + if (typeof frame === "string") { + log.warning("CPTV Error '%s'", frame); + await decoder.close(); + return; + } + if (frame && frame.isBackgroundFrame) { // Skip over background frame without incrementing counter. continue; } @@ -425,10 +430,8 @@ async function createThumbnail( thumbnail: TrackFramePosition, colourPalette: string = THUMBNAIL_PALETTE ): Promise<{ data: Buffer; meta: { palette: string; region: any } }> { - const frameMeta = frame.meta.imageData; - const resX = frameMeta.width; - const resY = frameMeta.height; - + const resX = 160; + const resY = 120; // // padding already in region so probably dont need // let padding = Math.max(2,Math.floor(thumbnail.height * 0.2), Math.floor(thumbnail.width *0.2)); // padding = Math.floor(padding / 2) @@ -445,7 +448,7 @@ async function createThumbnail( for (let i = 0; i < size; i++) { frameStart = (i + thumbnail.y) * resX + thumbnail.x; for (let offset = 0; offset < thumbnail.width; offset++) { - const pixel = frame.data[frameStart + offset]; + const pixel = frame.imageData[frameStart + offset]; if (!min) { min = pixel; max = pixel; @@ -464,7 +467,7 @@ async function createThumbnail( for (let i = 0; i < size; i++) { frameStart = (i + thumbnail.y) * resX + thumbnail.x; for (let offset = 0; offset < thumbnail.width; offset++) { - let pixel = frame.data[frameStart + offset]; + let pixel = frame.imageData[frameStart + offset]; pixel = (255 * (pixel - min)) / (max - min); thumbnailData[thumbIndex] = pixel; thumbIndex++; @@ -1923,7 +1926,9 @@ export async function sendAlerts( string, { count: number; tracks: { track: Track; trackTag: TrackTag }[] } > = {}; - const excludedTags = [...NON_ANIMAL_TAGS, "false-positive"]; + let excludedTags = [...NON_ANIMAL_TAGS, "false-positive"]; + // NOTE: We are explicitly allowing unidentified tags to alert. + excludedTags = excludedTags.filter((tag) => tag !== "unidentified"); for (const track of recording.Tracks) { for (const trackTag of track.TrackTags.filter( (tag) => !excludedTags.includes(tag.what) diff --git a/api/api/V1/recordingsBulkQueryUtil.ts b/api/api/V1/recordingsBulkQueryUtil.ts index 9d3436fb3..80684b438 100644 --- a/api/api/V1/recordingsBulkQueryUtil.ts +++ b/api/api/V1/recordingsBulkQueryUtil.ts @@ -13,6 +13,7 @@ export const getFirstPass = ( models: ModelsDictionary, projectId: GroupId, minDuration: number, + statusRecordingsOnly: boolean, includeDeletedRecordings: boolean, types: RecordingType[], processingState: RecordingProcessingState | undefined, @@ -57,7 +58,9 @@ export const getFirstPass = ( : {}), GroupId: projectId, ...(types.includes(RecordingType.Audio) ? { redacted: false } : {}), - duration: { [Op.gte]: minDuration }, + duration: statusRecordingsOnly + ? { [Op.and]: [{ [Op.lt]: 2.5 }, { [Op.gt]: 0.0 }] } + : { [Op.gte]: minDuration }, [Op.and]: [ ...(tagMode === TagMode.UnTagged ? [ @@ -468,6 +471,7 @@ export const queryRecordingsInProject = async ( models: ModelsDictionary, projectId: GroupId, minDuration: number, + statusRecordingsOnly: boolean, includeDeletedRecordings: boolean, types: RecordingType[], processingState: RecordingProcessingState | undefined, @@ -491,6 +495,7 @@ export const queryRecordingsInProject = async ( models, projectId, minDuration, + statusRecordingsOnly, includeDeletedRecordings, types, processingState, diff --git a/api/api/cptv-decoder/decoder.ts b/api/api/cptv-decoder/decoder.ts index a525f4433..6659d2b9b 100644 --- a/api/api/cptv-decoder/decoder.ts +++ b/api/api/cptv-decoder/decoder.ts @@ -117,16 +117,6 @@ export class CptvDecoder { return (await this.waitForMessage(type)) as CptvFrame | null; } - /** - * If the file stream has completed, this gives the total number - * of playable frames in the file (including any background frame). - */ - async getTotalFrames(): Promise { - const type = "getTotalFrames"; - this.decoder && this.decoder.postMessage({ type }); - return (await this.waitForMessage(type)) as number | null; - } - /** * Get the header for the CPTV file as JSON. * Optional fields will always be present, but set to `undefined` @@ -137,15 +127,6 @@ export class CptvDecoder { return (await this.waitForMessage(type)) as CptvHeader; } - /** - * Stream load progress from 0..1 - */ - async getLoadProgress(): Promise { - const type = "getLoadProgress"; - this.decoder && this.decoder.postMessage({ type }); - return (await this.waitForMessage(type)) as number; - } - /** * If the decode halted with errors. Use this in the API to see if we should continue processing a file, or mark it * as damaged. @@ -192,19 +173,23 @@ export class CptvDecoder { } } +interface CptvString { + inner: string; +} + export interface CptvHeader { timestamp: number; width: number; height: number; compression: number; - deviceName: string; + deviceName: CptvString; fps: number; - brand: string | null; - model: string | null; + brand: CptvString | null; + model: CptvString | null; deviceId: number | null; serialNumber: number | null; - firmwareVersion: string | null; - motionConfig: string | null; + firmwareVersion: CptvString | null; + motionConfig: CptvString | null; previewSecs: number | null; latitude: number | null; longitude: number | null; @@ -229,28 +214,11 @@ export interface CptvFrameHeader { lastFfcTempC: number | null; frameTempC: number | null; isBackgroundFrame: boolean; - imageData: { - width: number; - height: number; - /** - * Minimum value for this frame - */ - min: number; - /** - * Maximum value for this frame - */ - max: number; - }; } -export interface CptvFrame { +export interface CptvFrame extends CptvFrameHeader { /** * Raw u16 data of `width` * `height` length where width and height can be found in the CptvHeader */ - data: Uint16Array; - - /** - * Frame header - */ - meta: CptvFrameHeader; + imageData: Uint16Array; } diff --git a/api/api/cptv-decoder/decoder.worker.ts b/api/api/cptv-decoder/decoder.worker.ts index 61bed6536..8b3d5ec86 100644 --- a/api/api/cptv-decoder/decoder.worker.ts +++ b/api/api/cptv-decoder/decoder.worker.ts @@ -1,8 +1,8 @@ import { parentPort } from "worker_threads"; -import type { CptvFrameHeader, CptvHeader } from "./decoder.js"; -import type { CptvPlayerContext as PlayerContext } from "./decoder/decoder.js"; +import type { CptvFrame, CptvFrameHeader, CptvHeader } from "./decoder.js"; +import type { CptvDecoderContext as DecoderContext } from "./decoder/cptv_decoder.js"; const context = parentPort; -import init, { CptvPlayerContext } from "./decoder/decoder.js"; +import init, { CptvDecoderContext } from "./decoder/cptv_decoder.js"; import { readFileSync } from "fs"; import type { ReadableStream } from "stream/web"; import { fileURLToPath } from "url"; @@ -79,7 +79,7 @@ class CptvDecoderInterface { private prevFrameHeader: CptvFrameHeader | null = null; private response: Response | null = null; private reader: ReadableStreamDefaultReader | null = null; - private playerContext: PlayerContext | null = null; + private playerContext: DecoderContext | null = null; private expectedSize = 0; private inited = false; private currentContentType = "application/x-cptv"; @@ -118,10 +118,12 @@ class CptvDecoderInterface { // eslint-disable-next-line no-undef const __dirname = path.dirname(__filename); const wasm = readFileSync( - path.join(__dirname, "./decoder/decoder_bg.wasm") + path.join(__dirname, "./decoder/cptv_decoder_bg.wasm") + ); + await init(wasm); + this.playerContext = CptvDecoderContext.newWithReadableStream( + this.reader ); - const _wasmInstance = await init(wasm); - this.playerContext = await CptvPlayerContext.newWithStream(this.reader); this.inited = true; result = true; } catch (e) { @@ -148,11 +150,13 @@ class CptvDecoderInterface { // eslint-disable-next-line no-undef const __dirname = path.dirname(__filename); wasmBytes = readFileSync( - path.join(__dirname, "./decoder/decoder_bg.wasm") + path.join(__dirname, "./decoder/cptv_decoder_bg.wasm") ); } - const _wasmInstance = await init(wasmBytes); - this.playerContext = await CptvPlayerContext.newWithStream(this.reader); + await init(wasmBytes); + this.playerContext = CptvDecoderContext.newWithReadableStream( + this.reader + ); this.inited = true; result = true; } catch (e) { @@ -179,69 +183,36 @@ class CptvDecoderInterface { const unlocker = new Unlocker(); await this.lockIsUncontended(unlocker); this.locked = true; + let frameData: CptvFrame | null | string = null; if (this.hasValidContext()) { - try { - await (this.playerContext as PlayerContext).fetchNextFrame(); - } catch (e: unknown) { - this.streamError = e as string | null; + frameData = await (this.playerContext as DecoderContext).nextFrameOwned(); + if (typeof frameData === "string") { + this.streamError = frameData as string; + } else { + this.streamError = null; } - } else { - console.warn("Fetch next failed"); } unlocker.unlock(); this.locked = false; if (this.hasStreamError()) { return null; } - if (this.hasValidContext()) { - const frameData = (this.playerContext as PlayerContext).getNextFrame(); - const frameHeader = ( - this.playerContext as PlayerContext - ).getFrameHeader(); - // NOTE(jon): Work around a bug where the mlx sensor doesn't report timeOn times, just hardcodes 60000 - if (frameHeader && frameHeader.imageData.width !== 32) { - const sameFrameAsPrev = - frameHeader && - this.prevFrameHeader && - frameHeader.timeOnMs === this.prevFrameHeader.timeOnMs; - if (sameFrameAsPrev && this.getTotalFrames() === null) { - this.prevFrameHeader = frameHeader; - return null; //await this.fetchNextFrame(); - } - this.prevFrameHeader = frameHeader; - } - if (frameData.length === 0) { + if (frameData && typeof frameData === "object") { + const sameFrameAsPrev = + frameData && + this.prevFrameHeader && + frameData.timeOnMs === this.prevFrameHeader.timeOnMs; + if (sameFrameAsPrev) { + this.prevFrameHeader = frameData; return null; } - this.framesRead++; - return { data: new Uint16Array(frameData), meta: frameHeader }; - } - return null; - } - - async countTotalFrames(): Promise { - if (!this.reader) { - console.warn( - "You need to initialise the player with the url of a CPTV file" - ); - return 0; + this.prevFrameHeader = frameData; } - if (this.hasValidContext()) { - const unlocker = new Unlocker(); - await this.lockIsUncontended(unlocker); - this.locked = true; - try { - await (this.playerContext as PlayerContext).countTotalFrames(); - } catch (e) { - this.streamError = e as string; - } - // We can't call any other methods that read frame data on this stream, - // since we've exhausted it and thrown away the data after scanning for the info we want. - this.consumed = true; - unlocker.unlock(); - this.locked = false; + if (!frameData) { + return null; } - return this.getTotalFrames(); + this.framesRead++; + return frameData; } async getMetadata(): Promise< @@ -255,7 +226,19 @@ class CptvDecoderInterface { if ((header as CptvHeader).totalFrames) { totalFrameCount = (header as CptvHeader).totalFrames; } else { - totalFrameCount = await this.countTotalFrames(); + let frame: CptvFrame | null; + let num = 0; + while ( + (frame = await (this.playerContext as DecoderContext).nextFrame()) + ) { + if (!frame.isBackgroundFrame) { + num++; + } + } + totalFrameCount = num; + } + if (this.hasStreamError()) { + return this.streamError; } const duration = (1 / (header as CptvHeader).fps) * totalFrameCount; return { @@ -295,49 +278,47 @@ class CptvDecoderInterface { }); } - async getHeader(): Promise { + async getHeader() { if (!this.reader) { return "You need to initialise the player with the url of a CPTV file"; } + let header: CptvHeader | string; if (this.hasValidContext()) { const unlocker = new Unlocker(); await this.lockIsUncontended(unlocker); - this.locked = true; - await (this.playerContext as PlayerContext).fetchHeader(); - const header = (this.playerContext as PlayerContext).getHeader(); - if (header === "Unable to parse header") { - this.streamError = header; - } - unlocker.unlock(); - this.locked = false; - return header; - } - return this.streamError; - } + if (this.playerContext) { + this.locked = true; - getTotalFrames() { - if (this.streamError) { - return this.framesRead; - } - if ( - !this.locked && - this.inited && - this.hasValidContext() && - (this.playerContext as PlayerContext).streamComplete() - ) { - return (this.playerContext as PlayerContext).totalFrames(); - } - return null; - } + header = await (this.playerContext as DecoderContext).getHeader(); - getLoadProgress(): number | null { - if (this.locked || !this.hasValidContext()) { - return null; + if (typeof header === "string") { + this.streamError = header; + console.warn(this.streamError); + } + + unlocker.unlock(); + this.locked = false; + if (typeof header === "object") { + const h: any = { ...header }; + h.deviceName = h.deviceName.inner; + if (h.brand) { + h.brand = h.brand.inner; + } + if (h.model) { + h.model = h.model.inner; + } + if (h.firmwareVersion) { + h.firmwareVersion = h.firmwareVersion.inner; + } + if (h.motionConfig) { + h.motionConfig = h.motionConfig.inner; + } + return h; + } + return null; + } } - // This doesn't actually tell us how much has downloaded, just how much has been lazily read. - return ( - (this.playerContext as PlayerContext).bytesLoaded() / this.expectedSize - ); + return this.streamError; } hasStreamError(): boolean { @@ -378,18 +359,6 @@ context.addListener("message", async (data) => { context.postMessage({ type: data.type, data: frame }); } break; - case "getTotalFrames": - { - const totalFrames = player.getTotalFrames(); - context.postMessage({ type: data.type, data: totalFrames }); - } - break; - case "getLoadProgress": - { - const progress = player.getLoadProgress(); - context.postMessage({ type: data.type, data: progress }); - } - break; case "getHeader": { const header = await player.getHeader(); diff --git a/api/api/cptv-decoder/decoder/cptv_decoder.d.ts b/api/api/cptv-decoder/decoder/cptv_decoder.d.ts new file mode 100644 index 000000000..5a1ee455c --- /dev/null +++ b/api/api/cptv-decoder/decoder/cptv_decoder.d.ts @@ -0,0 +1,126 @@ +/* tslint:disable */ +/* eslint-disable */ + +// interface CptvFrame { +// imageData: Uint16Array, +// timeOnMs: number, +// lastFfcTimeMs: number, +// lastFfcTempC: number, +// frameTempC: number, +// isBackgroundFrame: false +// } +// +// interface CptvHeader { +// version: number, +// timestamp: number, +// width: number, +// height: number, +// compression: number, +// deviceName: { inner: string }, +// fps: number, +// brand: { inner: string }, +// model: { inner: string }, +// deviceId: number, +// serialNumber: number, +// firmwareVersion: { inner: string }, +// motionConfig: { +// inner: string +// }, +// previewSecs: number, +// latitude: number, +// longitude: number, +// locTimestamp?: number, +// altitude?: number, +// accuracy?: number, +// hasBackgroundFrame?: boolean, +// totalFrames?: number, +// minValue?: number, +// maxValue?: number +// } + +import { CptvFrame, CptvHeader } from "@api/cptv-decoder/decoder.ts"; + +/** + */ +export class CptvDecoderContext { + free(): void; + /** + * @param {ReadableStreamDefaultReader} stream + * @returns {CptvDecoderContext} + */ + static newWithReadableStream( + stream: ReadableStreamDefaultReader + ): CptvDecoderContext; + /** + * @returns {Promise} + */ + getHeader(): Promise; + /** + * @returns {Promise} + */ + nextFrame(): Promise; + /** + * @returns {Promise} + */ + nextFrameOwned(): Promise; +} + +export type InitInput = + | RequestInfo + | URL + | Response + | BufferSource + | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_cptvdecodercontext_free: (a: number) => void; + readonly cptvdecodercontext_newWithReadableStream: (a: number) => number; + readonly cptvdecodercontext_getHeader: (a: number) => number; + readonly cptvdecodercontext_nextFrame: (a: number) => number; + readonly cptvdecodercontext_nextFrameOwned: (a: number) => number; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: ( + a: number, + b: number, + c: number, + d: number + ) => number; + readonly __wbindgen_export_2: WebAssembly.Table; + readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1ea9dee0275cc798: ( + a: number, + b: number, + c: number + ) => void; + readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly __wbindgen_exn_store: (a: number) => void; + readonly wasm_bindgen__convert__closures__invoke2_mut__h9a4c6a115fd98b93: ( + a: number, + b: number, + c: number, + d: number + ) => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {SyncInitInput} module + * + * @returns {InitOutput} + */ +export function initSync(module: SyncInitInput): InitOutput; + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {InitInput | Promise} module_or_path + * + * @returns {Promise} + */ +export default function __wbg_init( + module_or_path?: InitInput | Promise +): Promise; diff --git a/api/api/cptv-decoder/decoder/cptv_decoder.js b/api/api/cptv-decoder/decoder/cptv_decoder.js new file mode 100644 index 000000000..2754e3a5e --- /dev/null +++ b/api/api/cptv-decoder/decoder/cptv_decoder.js @@ -0,0 +1,594 @@ +let wasm; + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachedInt32Memory0 = null; + +function getInt32Memory0() { + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b) +}); + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + } else { + state.a = a; + } + } + }; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; +} +function __wbg_adapter_28(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1ea9dee0275cc798(arg0, arg1, addHeapObject(arg2)); +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} +function __wbg_adapter_65(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures__invoke2_mut__h9a4c6a115fd98b93(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); +} + +const CptvDecoderContextFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_cptvdecodercontext_free(ptr >>> 0)); +/** +*/ +export class CptvDecoderContext { + + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(CptvDecoderContext.prototype); + obj.__wbg_ptr = ptr; + CptvDecoderContextFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + CptvDecoderContextFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_cptvdecodercontext_free(ptr); + } + /** + * @param {ReadableStreamDefaultReader} stream + * @returns {CptvDecoderContext} + */ + static newWithReadableStream(stream) { + const ret = wasm.cptvdecodercontext_newWithReadableStream(addHeapObject(stream)); + return CptvDecoderContext.__wrap(ret); + } + /** + * @returns {Promise} + */ + getHeader() { + const ret = wasm.cptvdecodercontext_getHeader(this.__wbg_ptr); + return takeObject(ret); + } + /** + * @returns {Promise} + */ + nextFrame() { + const ret = wasm.cptvdecodercontext_nextFrame(this.__wbg_ptr); + return takeObject(ret); + } + /** + * @returns {Promise} + */ + nextFrameOwned() { + const ret = wasm.cptvdecodercontext_nextFrameOwned(this.__wbg_ptr); + return takeObject(ret); + } +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + return ret; + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_number_new = function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbg_new_abda76e883ba8a5f = function() { + const ret = new Error(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }; + imports.wbg.__wbindgen_error_new = function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_bigint_from_u64 = function(arg0) { + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_f975102236d3c502 = function(arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }; + imports.wbg.__wbg_queueMicrotask_3cbae2ec6b6cd3d6 = function(arg0) { + const ret = getObject(arg0).queueMicrotask; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; + imports.wbg.__wbg_queueMicrotask_481971b0d87f3dd4 = function(arg0) { + queueMicrotask(getObject(arg0)); + }; + imports.wbg.__wbg_read_e7d0f8a49be01d86 = function(arg0) { + const ret = getObject(arg0).read(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_debug_5fb96680aecf5dc8 = function(arg0) { + console.debug(getObject(arg0)); + }; + imports.wbg.__wbg_error_8e3928cfb8a43e2b = function(arg0) { + console.error(getObject(arg0)); + }; + imports.wbg.__wbg_info_530a29cb2e4e3304 = function(arg0) { + console.info(getObject(arg0)); + }; + imports.wbg.__wbg_log_5bb5f88f245d7762 = function(arg0) { + console.log(getObject(arg0)); + }; + imports.wbg.__wbg_warn_63bbae1730aead09 = function(arg0) { + console.warn(getObject(arg0)); + }; + imports.wbg.__wbg_newnoargs_e258087cd0daa0ea = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_e3c254076557e348 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_call_27c0f87801dedf93 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_new_72fb9a18b5ae2624 = function() { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_self_ce0dbfc45cf2f5be = function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_window_c6fb939a7f436783 = function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_globalThis_d1e6af4856ba331b = function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_global_207b558942527489 = function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_call_b3ca7c6051f9bec1 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_new_81740750da40724f = function(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_65(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return addHeapObject(ret); + } finally { + state0.a = state0.b = 0; + } + }; + imports.wbg.__wbg_resolve_b0083a7967828ec8 = function(arg0) { + const ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_0c86a60e8fcfe9f6 = function(arg0, arg1) { + const ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_a73caa9a87991566 = function(arg0, arg1, arg2) { + const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_buffer_12d079cc21e14bdb = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_63b92bc8671ed464 = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_a47bac70306a19a7 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_length_c20a40f15020d68a = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_newwithbyteoffsetandlength_9fd64654bc0b0817 = function(arg0, arg1, arg2) { + const ret = new Uint16Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_d8983c5c08eb8338 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_length_7cf3a99cadacb5b8 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_instanceof_Uint8Array_2b3bbecd033d19f6 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_newwithlength_fd09e918bc879aaf = function(arg0) { + const ret = new Uint16Array(arg0 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_1f9b04f170055d33 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper154 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 43, __wbg_adapter_28); + return addHeapObject(ret); + }; + + return imports; +} + +function __wbg_init_memory(imports, maybe_memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedInt32Memory0 = null; + cachedUint8Memory0 = null; + + + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(input) { + if (wasm !== undefined) return wasm; + + if (typeof input === 'undefined') { + input = new URL('cptv_decoder_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await input, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync } +export default __wbg_init; diff --git a/api/api/cptv-decoder/decoder/cptv_decoder_bg.wasm b/api/api/cptv-decoder/decoder/cptv_decoder_bg.wasm new file mode 100644 index 000000000..6b7adbb70 Binary files /dev/null and b/api/api/cptv-decoder/decoder/cptv_decoder_bg.wasm differ diff --git a/api/api/cptv-decoder/decoder/cptv_decoder_bg.wasm.d.ts b/api/api/cptv-decoder/decoder/cptv_decoder_bg.wasm.d.ts new file mode 100644 index 000000000..7ae57a12e --- /dev/null +++ b/api/api/cptv-decoder/decoder/cptv_decoder_bg.wasm.d.ts @@ -0,0 +1,29 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function __wbg_cptvdecodercontext_free(a: number): void; +export function cptvdecodercontext_newWithReadableStream(a: number): number; +export function cptvdecodercontext_getHeader(a: number): number; +export function cptvdecodercontext_nextFrame(a: number): number; +export function cptvdecodercontext_nextFrameOwned(a: number): number; +export function __wbindgen_malloc(a: number, b: number): number; +export function __wbindgen_realloc( + a: number, + b: number, + c: number, + d: number +): number; +export const __wbindgen_export_2: WebAssembly.Table; +export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1ea9dee0275cc798( + a: number, + b: number, + c: number +): void; +export function __wbindgen_free(a: number, b: number, c: number): void; +export function __wbindgen_exn_store(a: number): void; +export function wasm_bindgen__convert__closures__invoke2_mut__h9a4c6a115fd98b93( + a: number, + b: number, + c: number, + d: number +): void; diff --git a/api/api/cptv-decoder/decoder/decoder.d.ts b/api/api/cptv-decoder/decoder/decoder.d.ts deleted file mode 100644 index 21545d718..000000000 --- a/api/api/cptv-decoder/decoder/decoder.d.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - */ -export class CptvPlayerContext { - free(): void; - /** - * @param {ReadableStreamDefaultReader} stream - * @returns {Promise} - */ - static newWithStream( - stream: ReadableStreamDefaultReader - ): Promise; - /** - * @returns {boolean} - */ - streamComplete(): boolean; - /** - * @returns {Promise} - */ - countTotalFrames(): Promise; - /** - * @returns {Promise} - */ - fetchNextFrame(): Promise; - /** - * @returns {any} - */ - totalFrames(): any; - /** - * @returns {number} - */ - bytesLoaded(): number; - /** - * @returns {Uint16Array} - */ - getNextFrame(): Uint16Array; - /** - * @returns {any} - */ - getFrameHeader(): any; - /** - * @returns {number} - */ - getWidth(): number; - /** - * @returns {number} - */ - getHeight(): number; - /** - * @returns {number} - */ - getFrameRate(): number; - /** - * @returns {number} - */ - getFramesPerIframe(): number; - /** - * @returns {Promise} - */ - fetchHeader(): Promise; - /** - * @returns {any} - */ - getHeader(): any; -} - -export type InitInput = - | RequestInfo - | URL - | Response - | BufferSource - | WebAssembly.Module; - -export interface InitOutput { - readonly memory: WebAssembly.Memory; - readonly __wbg_cptvplayercontext_free: (a: number) => void; - readonly cptvplayercontext_newWithStream: (a: number) => number; - readonly cptvplayercontext_streamComplete: (a: number) => number; - readonly cptvplayercontext_countTotalFrames: (a: number) => number; - readonly cptvplayercontext_fetchNextFrame: (a: number) => number; - readonly cptvplayercontext_totalFrames: (a: number) => number; - readonly cptvplayercontext_bytesLoaded: (a: number) => number; - readonly cptvplayercontext_getNextFrame: (a: number) => number; - readonly cptvplayercontext_getFrameHeader: (a: number) => number; - readonly cptvplayercontext_getWidth: (a: number) => number; - readonly cptvplayercontext_getHeight: (a: number) => number; - readonly cptvplayercontext_getFrameRate: (a: number) => number; - readonly cptvplayercontext_getFramesPerIframe: (a: number) => number; - readonly cptvplayercontext_fetchHeader: (a: number) => number; - readonly cptvplayercontext_getHeader: (a: number) => number; - readonly __wbindgen_malloc: (a: number, b: number) => number; - readonly __wbindgen_realloc: ( - a: number, - b: number, - c: number, - d: number - ) => number; - readonly __wbindgen_export_2: WebAssembly.Table; - readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h7220e89b344ffef7: ( - a: number, - b: number, - c: number - ) => void; - readonly __wbindgen_free: (a: number, b: number, c: number) => void; - readonly __wbindgen_exn_store: (a: number) => void; - readonly wasm_bindgen__convert__closures__invoke2_mut__h18b31808c97857b2: ( - a: number, - b: number, - c: number, - d: number - ) => void; -} - -export type SyncInitInput = BufferSource | WebAssembly.Module; -/** - * Instantiates the given `module`, which can either be bytes or - * a precompiled `WebAssembly.Module`. - * - * @param {SyncInitInput} module - * - * @returns {InitOutput} - */ -export function initSync(module: SyncInitInput): InitOutput; - -/** - * If `module_or_path` is {RequestInfo} or {URL}, makes a request and - * for everything else, calls `WebAssembly.instantiate` directly. - * - * @param {InitInput | Promise} module_or_path - * - * @returns {Promise} - */ -export default function __wbg_init( - module_or_path?: InitInput | Promise -): Promise; diff --git a/api/api/cptv-decoder/decoder/decoder.js b/api/api/cptv-decoder/decoder/decoder.js deleted file mode 100644 index 24a94e1e4..000000000 --- a/api/api/cptv-decoder/decoder/decoder.js +++ /dev/null @@ -1,659 +0,0 @@ -let wasm; - -const heap = new Array(128).fill(undefined); - -heap.push(undefined, null, true, false); - -function getObject(idx) { - return heap[idx]; -} - -let heap_next = heap.length; - -function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; -} - -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} - -const cachedTextDecoder = - typeof TextDecoder !== "undefined" - ? new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }) - : { - decode: () => { - throw Error("TextDecoder not available"); - }, - }; - -if (typeof TextDecoder !== "undefined") { - cachedTextDecoder.decode(); -} - -let cachedUint8Memory0 = null; - -function getUint8Memory0() { - if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; -} - -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; -} - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == "number" || type == "boolean" || val == null) { - return `${val}`; - } - if (type == "string") { - return `"${val}"`; - } - if (type == "symbol") { - const description = val.description; - if (description == null) { - return "Symbol"; - } else { - return `Symbol(${description})`; - } - } - if (type == "function") { - const name = val.name; - if (typeof name == "string" && name.length > 0) { - return `Function(${name})`; - } else { - return "Function"; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = "["; - if (length > 0) { - debug += debugString(val[0]); - } - for (let i = 1; i < length; i++) { - debug += ", " + debugString(val[i]); - } - debug += "]"; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == "Object") { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return "Object(" + JSON.stringify(val) + ")"; - } catch (_) { - return "Object"; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} - -let WASM_VECTOR_LEN = 0; - -const cachedTextEncoder = - typeof TextEncoder !== "undefined" - ? new TextEncoder("utf-8") - : { - encode: () => { - throw Error("TextEncoder not available"); - }, - }; - -const encodeString = - typeof cachedTextEncoder.encodeInto === "function" - ? function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); - } - : function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length, - }; - }; - -function passStringToWasm0(arg, malloc, realloc) { - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8Memory0() - .subarray(ptr, ptr + buf.length) - .set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; - } - - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; - - const mem = getUint8Memory0(); - - let offset = 0; - - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7f) break; - mem[ptr + offset] = code; - } - - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; - const view = getUint8Memory0().subarray(ptr + offset, ptr + len); - const ret = encodeString(arg, view); - - offset += ret.written; - } - - WASM_VECTOR_LEN = offset; - return ptr; -} - -let cachedInt32Memory0 = null; - -function getInt32Memory0() { - if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); - } - return cachedInt32Memory0; -} - -function makeMutClosure(arg0, arg1, dtor, f) { - const state = { a: arg0, b: arg1, cnt: 1, dtor }; - const real = (...args) => { - // First up with a closure we increment the internal reference - // count. This ensures that the Rust closure environment won't - // be deallocated while we're invoking it. - state.cnt++; - const a = state.a; - state.a = 0; - try { - return f(a, state.b, ...args); - } finally { - if (--state.cnt === 0) { - wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); - } else { - state.a = a; - } - } - }; - real.original = state; - - return real; -} -function __wbg_adapter_26(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h7220e89b344ffef7( - arg0, - arg1, - addHeapObject(arg2) - ); -} - -function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } -} -function __wbg_adapter_67(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures__invoke2_mut__h18b31808c97857b2( - arg0, - arg1, - addHeapObject(arg2), - addHeapObject(arg3) - ); -} - -/** - */ -export class CptvPlayerContext { - static __wrap(ptr) { - ptr = ptr >>> 0; - const obj = Object.create(CptvPlayerContext.prototype); - obj.__wbg_ptr = ptr; - - return obj; - } - - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; - - return ptr; - } - - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_cptvplayercontext_free(ptr); - } - /** - * @param {ReadableStreamDefaultReader} stream - * @returns {Promise} - */ - static newWithStream(stream) { - const ret = wasm.cptvplayercontext_newWithStream(addHeapObject(stream)); - return takeObject(ret); - } - /** - * @returns {boolean} - */ - streamComplete() { - const ret = wasm.cptvplayercontext_streamComplete(this.__wbg_ptr); - return ret !== 0; - } - /** - * @returns {Promise} - */ - countTotalFrames() { - const ret = wasm.cptvplayercontext_countTotalFrames(this.__wbg_ptr); - return takeObject(ret); - } - /** - * @returns {Promise} - */ - fetchNextFrame() { - const ret = wasm.cptvplayercontext_fetchNextFrame(this.__wbg_ptr); - return takeObject(ret); - } - /** - * @returns {any} - */ - totalFrames() { - const ret = wasm.cptvplayercontext_totalFrames(this.__wbg_ptr); - return takeObject(ret); - } - /** - * @returns {number} - */ - bytesLoaded() { - const ret = wasm.cptvplayercontext_bytesLoaded(this.__wbg_ptr); - return ret >>> 0; - } - /** - * @returns {Uint16Array} - */ - getNextFrame() { - const ret = wasm.cptvplayercontext_getNextFrame(this.__wbg_ptr); - return takeObject(ret); - } - /** - * @returns {any} - */ - getFrameHeader() { - const ret = wasm.cptvplayercontext_getFrameHeader(this.__wbg_ptr); - return takeObject(ret); - } - /** - * @returns {number} - */ - getWidth() { - const ret = wasm.cptvplayercontext_getWidth(this.__wbg_ptr); - return ret >>> 0; - } - /** - * @returns {number} - */ - getHeight() { - const ret = wasm.cptvplayercontext_getHeight(this.__wbg_ptr); - return ret >>> 0; - } - /** - * @returns {number} - */ - getFrameRate() { - const ret = wasm.cptvplayercontext_getFrameRate(this.__wbg_ptr); - return ret; - } - /** - * @returns {number} - */ - getFramesPerIframe() { - const ret = wasm.cptvplayercontext_getFramesPerIframe(this.__wbg_ptr); - return ret; - } - /** - * @returns {Promise} - */ - fetchHeader() { - const ret = wasm.cptvplayercontext_fetchHeader(this.__wbg_ptr); - return takeObject(ret); - } - /** - * @returns {any} - */ - getHeader() { - const ret = wasm.cptvplayercontext_getHeader(this.__wbg_ptr); - return takeObject(ret); - } -} - -async function __wbg_load(module, imports) { - if (typeof Response === "function" && module instanceof Response) { - if (typeof WebAssembly.instantiateStreaming === "function") { - try { - return await WebAssembly.instantiateStreaming(module, imports); - } catch (e) { - if (module.headers.get("Content-Type") != "application/wasm") { - console.warn( - "`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", - e - ); - } else { - throw e; - } - } - } - - const bytes = await module.arrayBuffer(); - return await WebAssembly.instantiate(bytes, imports); - } else { - const instance = await WebAssembly.instantiate(module, imports); - - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; - } else { - return instance; - } - } -} - -function __wbg_get_imports() { - const imports = {}; - imports.wbg = {}; - imports.wbg.__wbindgen_object_drop_ref = function (arg0) { - takeObject(arg0); - }; - imports.wbg.__wbindgen_string_new = function (arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbg_cptvplayercontext_new = function (arg0) { - const ret = CptvPlayerContext.__wrap(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_boolean_get = function (arg0) { - const v = getObject(arg0); - const ret = typeof v === "boolean" ? (v ? 1 : 0) : 2; - return ret; - }; - imports.wbg.__wbindgen_is_undefined = function (arg0) { - const ret = getObject(arg0) === undefined; - return ret; - }; - imports.wbg.__wbindgen_number_new = function (arg0) { - const ret = arg0; - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_cb_drop = function (arg0) { - const obj = takeObject(arg0).original; - if (obj.cnt-- == 1) { - obj.a = 0; - return true; - } - const ret = false; - return ret; - }; - imports.wbg.__wbindgen_object_clone_ref = function (arg0) { - const ret = getObject(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_error_new = function (arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_bigint_from_u64 = function (arg0) { - const ret = BigInt.asUintN(64, arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbg_set_bd72c078edfa51ad = function (arg0, arg1, arg2) { - getObject(arg0)[takeObject(arg1)] = takeObject(arg2); - }; - imports.wbg.__wbg_new_abda76e883ba8a5f = function () { - const ret = new Error(); - return addHeapObject(ret); - }; - imports.wbg.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { - const ret = getObject(arg1).stack; - const ptr1 = passStringToWasm0( - ret, - wasm.__wbindgen_malloc, - wasm.__wbindgen_realloc - ); - const len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }; - imports.wbg.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { - let deferred0_0; - let deferred0_1; - try { - deferred0_0 = arg0; - deferred0_1 = arg1; - console.error(getStringFromWasm0(arg0, arg1)); - } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); - } - }; - imports.wbg.__wbg_debug_9a6b3243fbbebb61 = function (arg0) { - console.debug(getObject(arg0)); - }; - imports.wbg.__wbg_error_788ae33f81d3b84b = function (arg0) { - console.error(getObject(arg0)); - }; - imports.wbg.__wbg_info_2e30e8204b29d91d = function (arg0) { - console.info(getObject(arg0)); - }; - imports.wbg.__wbg_log_1d3ae0273d8f4f8a = function (arg0) { - console.log(getObject(arg0)); - }; - imports.wbg.__wbg_warn_d60e832f9882c1b2 = function (arg0) { - console.warn(getObject(arg0)); - }; - imports.wbg.__wbg_read_b40399852b2f7b2b = function (arg0) { - const ret = getObject(arg0).read(); - return addHeapObject(ret); - }; - imports.wbg.__wbg_get_97b561fb56f034b5 = function () { - return handleError(function (arg0, arg1) { - const ret = Reflect.get(getObject(arg0), getObject(arg1)); - return addHeapObject(ret); - }, arguments); - }; - imports.wbg.__wbg_new_b51585de1b234aff = function () { - const ret = new Object(); - return addHeapObject(ret); - }; - imports.wbg.__wbg_call_01734de55d61e11d = function () { - return handleError(function (arg0, arg1, arg2) { - const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); - return addHeapObject(ret); - }, arguments); - }; - imports.wbg.__wbg_new_43f1b47c28813cbd = function (arg0, arg1) { - try { - var state0 = { a: arg0, b: arg1 }; - var cb0 = (arg0, arg1) => { - const a = state0.a; - state0.a = 0; - try { - return __wbg_adapter_67(a, state0.b, arg0, arg1); - } finally { - state0.a = a; - } - }; - const ret = new Promise(cb0); - return addHeapObject(ret); - } finally { - state0.a = state0.b = 0; - } - }; - imports.wbg.__wbg_resolve_53698b95aaf7fcf8 = function (arg0) { - const ret = Promise.resolve(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_then_f7e06ee3c11698eb = function (arg0, arg1) { - const ret = getObject(arg0).then(getObject(arg1)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_then_b2267541e2a73865 = function (arg0, arg1, arg2) { - const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_buffer_085ec1f694018c4f = function (arg0) { - const ret = getObject(arg0).buffer; - return addHeapObject(ret); - }; - imports.wbg.__wbg_new_8125e318e6245eed = function (arg0) { - const ret = new Uint8Array(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { - getObject(arg0).set(getObject(arg1), arg2 >>> 0); - }; - imports.wbg.__wbg_length_72e2208bbc0efc61 = function (arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_newwithbyteoffsetandlength_31ff1024ef0c63c7 = function ( - arg0, - arg1, - arg2 - ) { - const ret = new Uint16Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); - return addHeapObject(ret); - }; - imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function (arg0) { - let result; - try { - result = getObject(arg0) instanceof Uint8Array; - } catch { - result = false; - } - const ret = result; - return ret; - }; - imports.wbg.__wbg_byteLength_47d11fa79875dee3 = function (arg0) { - const ret = getObject(arg0).byteLength; - return ret; - }; - imports.wbg.__wbg_newwithlength_1efd26b345def7b3 = function (arg0) { - const ret = new Uint16Array(arg0 >>> 0); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_debug_string = function (arg0, arg1) { - const ret = debugString(getObject(arg1)); - const ptr1 = passStringToWasm0( - ret, - wasm.__wbindgen_malloc, - wasm.__wbindgen_realloc - ); - const len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }; - imports.wbg.__wbindgen_throw = function (arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbindgen_memory = function () { - const ret = wasm.memory; - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_closure_wrapper182 = function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 58, __wbg_adapter_26); - return addHeapObject(ret); - }; - - return imports; -} - -function __wbg_init_memory(imports, maybe_memory) {} - -function __wbg_finalize_init(instance, module) { - wasm = instance.exports; - __wbg_init.__wbindgen_wasm_module = module; - cachedInt32Memory0 = null; - cachedUint8Memory0 = null; - - return wasm; -} - -function initSync(module) { - if (wasm !== undefined) return wasm; - - const imports = __wbg_get_imports(); - - __wbg_init_memory(imports); - - if (!(module instanceof WebAssembly.Module)) { - module = new WebAssembly.Module(module); - } - - const instance = new WebAssembly.Instance(module, imports); - - return __wbg_finalize_init(instance, module); -} - -async function __wbg_init(input) { - if (wasm !== undefined) return wasm; - - if (typeof input === "undefined") { - input = new URL("decoder_bg.wasm", import.meta.url); - } - const imports = __wbg_get_imports(); - - if ( - typeof input === "string" || - (typeof Request === "function" && input instanceof Request) || - (typeof URL === "function" && input instanceof URL) - ) { - input = fetch(input); - } - - __wbg_init_memory(imports); - - const { instance, module } = await __wbg_load(await input, imports); - - return __wbg_finalize_init(instance, module); -} - -export { initSync }; -export default __wbg_init; diff --git a/api/api/cptv-decoder/decoder/decoder_bg.wasm b/api/api/cptv-decoder/decoder/decoder_bg.wasm deleted file mode 100644 index 1359ca2b9..000000000 Binary files a/api/api/cptv-decoder/decoder/decoder_bg.wasm and /dev/null differ diff --git a/api/api/cptv-decoder/decoder/decoder_bg.wasm.d.ts b/api/api/cptv-decoder/decoder/decoder_bg.wasm.d.ts deleted file mode 100644 index da45ec949..000000000 --- a/api/api/cptv-decoder/decoder/decoder_bg.wasm.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -export const memory: WebAssembly.Memory; -export function __wbg_cptvplayercontext_free(a: number): void; -export function cptvplayercontext_newWithStream(a: number): number; -export function cptvplayercontext_streamComplete(a: number): number; -export function cptvplayercontext_countTotalFrames(a: number): number; -export function cptvplayercontext_fetchNextFrame(a: number): number; -export function cptvplayercontext_totalFrames(a: number): number; -export function cptvplayercontext_bytesLoaded(a: number): number; -export function cptvplayercontext_getNextFrame(a: number): number; -export function cptvplayercontext_getFrameHeader(a: number): number; -export function cptvplayercontext_getWidth(a: number): number; -export function cptvplayercontext_getHeight(a: number): number; -export function cptvplayercontext_getFrameRate(a: number): number; -export function cptvplayercontext_getFramesPerIframe(a: number): number; -export function cptvplayercontext_fetchHeader(a: number): number; -export function cptvplayercontext_getHeader(a: number): number; -export function __wbindgen_malloc(a: number, b: number): number; -export function __wbindgen_realloc( - a: number, - b: number, - c: number, - d: number -): number; -export const __wbindgen_export_2: WebAssembly.Table; -export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h7220e89b344ffef7( - a: number, - b: number, - c: number -): void; -export function __wbindgen_free(a: number, b: number, c: number): void; -export function __wbindgen_exn_store(a: number): void; -export function wasm_bindgen__convert__closures__invoke2_mut__h18b31808c97857b2( - a: number, - b: number, - c: number, - d: number -): void; diff --git a/api/api/extract-middleware.ts b/api/api/extract-middleware.ts index 9cfae65f4..3c1ce0401 100755 --- a/api/api/extract-middleware.ts +++ b/api/api/extract-middleware.ts @@ -1645,7 +1645,7 @@ export const fetchAuthorizedRequiredGroupByNameOrId = ( ); export const fetchAdminAuthorizedRequiredGroupByNameOrId = ( - groupNameOrId: ValidationChain + groupNameOrId: ValidationChain | number ) => fetchRequiredModel( models.Group, diff --git a/api/api/fileUploaders/uploadGenericRecording.ts b/api/api/fileUploaders/uploadGenericRecording.ts index 781f90f3a..d5dba9fb2 100644 --- a/api/api/fileUploaders/uploadGenericRecording.ts +++ b/api/api/fileUploaders/uploadGenericRecording.ts @@ -40,6 +40,9 @@ import { import type { Station } from "@models/Station.js"; import type { Group } from "@models/Group.js"; import { isLatLon } from "@models/util/validation.js"; +import { tryToMatchLocationToStationInGroup } from "@models/util/locationUtils.js"; +import { tryReadingM4aMetadata } from "@api/m4a-metadata-reader/m4a-metadata-reader.js"; +import logger from "@log"; const cameraTypes = [ RecordingType.ThermalRaw, @@ -199,7 +202,7 @@ interface RecordingFileUploadResult { isCorrupt: boolean; sha1Hash: string; fileLength: number; - embeddedMetadata?: CptvHeader; + embeddedMetadata?: CptvHeader | Record; fileName?: string; } @@ -214,6 +217,26 @@ const mapPartName = (partKey: string, partName: string): string => { } return partKey; }; + +function appendToArrayBuffer(originalBuffer, newData) { + // Create a new ArrayBuffer with the size of the original plus the new data + const newBuffer = new ArrayBuffer( + originalBuffer.byteLength + newData.byteLength + ); + + // Create typed arrays to work with the data + const originalView = new Uint8Array(originalBuffer); + const newView = new Uint8Array(newBuffer); + const additionalView = new Uint8Array(newData); + + // Copy the original data to the new buffer + newView.set(originalView, 0); + // Copy the new data to the new buffer + newView.set(additionalView, originalView.length); + + return newBuffer; // Return the new ArrayBuffer +} + const processFilePart = async ( partKey: string, part: MultipartFormPart, @@ -225,11 +248,20 @@ const processFilePart = async ( const sha1Hash = crypto.createHash("sha1"); console.assert(!!part.filename, "NO FILENAME"); - // NOTE: thermal-uploader calls the filename 'file' + // NOTE: thermal-uploader calls the filename 'file', + // so we need to check for m4a metadata as well as trying to parse as a CPTV file. const mightBeCptvFile = !("filename" in part) || (part.filename && (part.filename.endsWith(".cptv") || part.filename === "file")); + + const mightBeTc2AudioFile = + !("filename" in part) || + (part.filename && + (part.filename.endsWith(".aac") || part.filename === "file")); + let wasValidCptvFile = true; + let wasValidM4aFile = true; + const transform = new TransformStream({ transform(chunk, controller) { if (canceledRequest.canceled) { @@ -241,33 +273,45 @@ const processFilePart = async ( }, }); let uploaderStream; - let decodeStream; - if (mightBeCptvFile) { + let cptvDecodeStream; + let m4aDecodeStream; + if (mightBeCptvFile && mightBeTc2AudioFile) { const stream = Readable.toWeb(part); - [uploaderStream, decodeStream] = stream.pipeThrough(transform).tee(); + const [u, d] = stream.pipeThrough(transform).tee(); + uploaderStream = u; + [cptvDecodeStream, m4aDecodeStream] = d.tee(); + } else if (mightBeCptvFile && !mightBeTc2AudioFile) { + const stream = Readable.toWeb(part); + [uploaderStream, cptvDecodeStream] = stream.pipeThrough(transform).tee(); + } else if (mightBeTc2AudioFile && !mightBeCptvFile) { + const stream = Readable.toWeb(part); + [uploaderStream, m4aDecodeStream] = stream.pipeThrough(transform).tee(); } else { uploaderStream = Readable.toWeb(part).pipeThrough(transform); } // TODO: If there are multiple file uploads, and *any* fail or are prematurely aborted, we need to exit early. // Upload part, while piping it through a transform that performs sha1 + checks length. const upload = uploadStream(partKey, uploaderStream); - // Special treatment for "file" part, since that is the "raw" file. // NOTE: Maybe validate stream, depending on upload recording type. // If there have been recordings from this device previously, we can get the // expected type from the device kind. let isCorrupt = false; - let embeddedMetadata: CptvHeader | string; + let embeddedMetadata: CptvHeader | string | Record; + let cptvStreamError = ""; + let uploaded = false; + if (mightBeCptvFile) { // If the device is a known thermal camera, we can validate the cptv file, and potentially // exit early if it is found to be corrupt. const decoder = new CptvDecoder(); - embeddedMetadata = await decoder.getStreamMetadata(decodeStream); + embeddedMetadata = await decoder.getStreamMetadata(cptvDecodeStream); if (!canceledRequest.canceled) { if (typeof embeddedMetadata === "string") { - log.error("Stream error %s", embeddedMetadata); + cptvStreamError = embeddedMetadata; // NOTE: we don't abort corrupt files, we just mark them as corrupt and keep them. isCorrupt = true; + wasValidCptvFile = false; // TODO: The file could be corrupt, but we could still get a valid CPTV header out. // test this case. const header = await decoder.getHeader(); @@ -284,9 +328,38 @@ const processFilePart = async ( ); } }); + uploaded = true; } await decoder.close(); - } else { + } + if (mightBeTc2AudioFile && (!mightBeCptvFile || !wasValidCptvFile)) { + const metadata = await tryReadingM4aMetadata(m4aDecodeStream); + if (typeof metadata === "string") { + log.warning("Failed parsing m4a metadata: %s", metadata); + wasValidM4aFile = false; + // Probably wasn't a valid .aac file? + // isCorrupt = true; + } else if (typeof metadata === "object") { + embeddedMetadata = metadata as Record; + isCorrupt = false; + } + if (!canceledRequest.canceled && !uploaded) { + await upload.done().catch((error) => { + if (error.name !== "AbortError") { + log.error("Upload error: %s", error.toString()); + part.emit( + "error", + new UnprocessableError(`Upload error: '${part.name}'`) + ); + } + }); + uploaded = true; + } + } + if (mightBeCptvFile && !wasValidCptvFile && !wasValidM4aFile) { + log.error("Stream error %s", cptvStreamError); + } + if (!mightBeCptvFile && !mightBeTc2AudioFile && !uploaded) { await upload.done().catch((error) => { if (error.name !== "AbortError") { log.error("DONE? %s", error.toString()); @@ -297,6 +370,7 @@ const processFilePart = async ( } }); } + const payload: RecordingFileUploadResult = { partName: part.name, isCorrupt, @@ -305,7 +379,9 @@ const processFilePart = async ( fileLength: length, }; if (embeddedMetadata && typeof embeddedMetadata !== "string") { - payload.embeddedMetadata = embeddedMetadata as CptvHeader; + payload.embeddedMetadata = embeddedMetadata as + | CptvHeader + | Record; } if (part.filename) { payload.fileName = part.filename; @@ -493,6 +569,16 @@ export const uploadGenericRecording = return; } } + // NOTE: Temporary until we get audio files with embedded location metadata: + if ( + data.type === RecordingType.Audio && + !data.location && + recordingDevice && + recordingDevice.location + ) { + data.location = recordingDevice.location; + } + const recordingTemplate = createRecording( models, data, @@ -560,25 +646,55 @@ export const uploadGenericRecording = } let recordingDeviceUpdatePayload = {}; if (fromDevice) { + let shouldSetActive = false; + if (!recordingDevice.active) { + // Check if the device has been re-assigned to another group: + const activeDevice = await models.Device.findOne({ + where: { + saltId: recordingDevice.saltId, + active: true, + }, + }); + if (!activeDevice) { + shouldSetActive = true; + } + } // Set the device active and update its connection time. recordingDeviceUpdatePayload = { lastConnectionTime: new Date(), - active: true, }; + if (shouldSetActive) { + (recordingDeviceUpdatePayload as any).active = true; + } } else if ( !fromDevice && (recordingTemplate.recordingDateTime > recordingDevice.lastConnectionTime || !recordingDevice.lastConnectionTime) ) { + let shouldSetActive = false; + if (!recordingDevice.active) { + // Check if the device has been re-assigned to another group: + const activeDevice = await models.Device.findOne({ + where: { + saltId: recordingDevice.saltId, + active: true, + }, + }); + if (!activeDevice) { + shouldSetActive = true; + } + } // If we're getting a recording via sidekick that's later than a previous lastConnectionTime, // or there is no previous lastConnectionTime, we can null out the lastConnectionTime, // which indicates that this device is now "offline". // As such, it will no longer be targeted by stopped device emails, and can show up as offline in browse. recordingDeviceUpdatePayload = { lastConnectionTime: null, - active: true, }; + if (shouldSetActive) { + (recordingDeviceUpdatePayload as any).active = true; + } } const wouldHaveSuppliedTracks = dataHasSuppliedTracks(data); @@ -594,6 +710,7 @@ export const uploadGenericRecording = recordingTemplate.save(), maybeUpdateLastRecordingTimesForStation( recordingTemplate, + fromDevice, stationToAssignToRecording ), maybeUpdateLastRecordingTimesForDeviceAndGroup( @@ -758,6 +875,7 @@ const assignGroupAndStationToRecording = async ( const maybeUpdateLastRecordingTimesForStation = async ( recordingData: Recording, + isDeviceUpload: boolean, station?: Station ): Promise => { let stationUpdatePromise: Promise = new Promise( @@ -769,6 +887,8 @@ const maybeUpdateLastRecordingTimesForStation = async ( if (station) { recordingData.StationId = station.id; + // Update lastActiveTimes + // Only update our times for non-status recordings if (recordingData.duration >= 3) { // Update station lastRecordingTimes if needed. @@ -778,6 +898,9 @@ const maybeUpdateLastRecordingTimesForStation = async ( recordingData.recordingDateTime > station.lastAudioRecordingTime) ) { station.lastAudioRecordingTime = recordingData.recordingDateTime; + if (isDeviceUpload) { + station.lastActiveThermalTime = new Date(); + } stationUpdatePromise = station.save(); } else if ( cameraTypes.includes(recordingData.type) && @@ -785,6 +908,9 @@ const maybeUpdateLastRecordingTimesForStation = async ( recordingData.recordingDateTime > station.lastThermalRecordingTime) ) { station.lastThermalRecordingTime = recordingData.recordingDateTime; + if (isDeviceUpload) { + station.lastActiveThermalTime = new Date(); + } stationUpdatePromise = station.save(); } } diff --git a/api/api/m4a-metadata-reader/m4a-metadata-reader.ts b/api/api/m4a-metadata-reader/m4a-metadata-reader.ts new file mode 100644 index 000000000..4137d7771 --- /dev/null +++ b/api/api/m4a-metadata-reader/m4a-metadata-reader.ts @@ -0,0 +1,42 @@ +import loadWasm, { M4aReaderContext } from "./m4a_metadata.js"; +import fs from "fs"; +import { fileURLToPath } from "url"; +import path from "path"; +export const tryReadingM4aMetadata = async ( + stream: ReadableStream +): Promise | string> => { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + await loadWasm( + fs.readFileSync(path.join(__dirname, "./m4a_metadata_bg.wasm")) + ); + const readerContext = M4aReaderContext.newWithReadableStream( + stream.getReader() + ); + const result = await readerContext.getMetadata(); + if (typeof result === "object") { + if (result.longitude) { + result.longitude = parseFloat(result.longitude); + } + if (result.latitude) { + result.latitude = parseFloat(result.latitude); + } + if (result.locTimestamp) { + result.locTimestamp = parseInt(result.locTimestamp); + } + if (result.recordingDateTime) { + result.recordingDateTime = new Date(result.recordingDateTime); + } + if (result.deviceId) { + result.deviceId = parseInt(result.deviceId); + } + if (result.locAccuracy) { + result.locAccuracy = parseFloat(result.locAccuracy); + } + if (result.duration) { + result.duration = parseFloat(result.duration); + } + } + readerContext.free(); + return result; +}; diff --git a/api/api/m4a-metadata-reader/m4a_metadata.d.ts b/api/api/m4a-metadata-reader/m4a_metadata.d.ts new file mode 100644 index 000000000..c662ec271 --- /dev/null +++ b/api/api/m4a-metadata-reader/m4a_metadata.d.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + */ +export class M4aReaderContext { + free(): void; + /** + * @param {ReadableStreamDefaultReader} stream + * @returns {M4aReaderContext} + */ + static newWithReadableStream( + stream: ReadableStreamDefaultReader + ): M4aReaderContext; + /** + * @returns {Promise} + */ + getMetadata(): Promise; +} + +export type InitInput = + | RequestInfo + | URL + | Response + | BufferSource + | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_m4areadercontext_free: (a: number, b: number) => void; + readonly m4areadercontext_newWithReadableStream: (a: number) => number; + readonly m4areadercontext_getMetadata: (a: number) => number; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: ( + a: number, + b: number, + c: number, + d: number + ) => number; + readonly __wbindgen_export_2: WebAssembly.Table; + readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h99011da901f2b23b: ( + a: number, + b: number, + c: number + ) => void; + readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly __wbindgen_exn_store: (a: number) => void; + readonly wasm_bindgen__convert__closures__invoke2_mut__h695f61305e585c80: ( + a: number, + b: number, + c: number, + d: number + ) => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** + * Instantiates the given `module`, which can either be bytes or + * a precompiled `WebAssembly.Module`. + * + * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. + * + * @returns {InitOutput} + */ +export function initSync( + module: { module: SyncInitInput } | SyncInitInput +): InitOutput; + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. + * + * @returns {Promise} + */ +export default function __wbg_init( + module_or_path?: + | { module_or_path: InitInput | Promise } + | InitInput + | Promise +): Promise; diff --git a/api/api/m4a-metadata-reader/m4a_metadata.js b/api/api/m4a-metadata-reader/m4a_metadata.js new file mode 100644 index 000000000..bf76c32ba --- /dev/null +++ b/api/api/m4a-metadata-reader/m4a_metadata.js @@ -0,0 +1,559 @@ +let wasm; + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b) +}); + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + } else { + state.a = a; + } + } + }; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; +} +function __wbg_adapter_22(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h99011da901f2b23b(arg0, arg1, addHeapObject(arg2)); +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} +function __wbg_adapter_55(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures__invoke2_mut__h695f61305e585c80(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); +} + +const M4aReaderContextFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_m4areadercontext_free(ptr >>> 0, 1)); +/** +*/ +export class M4aReaderContext { + + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(M4aReaderContext.prototype); + obj.__wbg_ptr = ptr; + M4aReaderContextFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + M4aReaderContextFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_m4areadercontext_free(ptr, 0); + } + /** + * @param {ReadableStreamDefaultReader} stream + * @returns {M4aReaderContext} + */ + static newWithReadableStream(stream) { + const ret = wasm.m4areadercontext_newWithReadableStream(addHeapObject(stream)); + return M4aReaderContext.__wrap(ret); + } + /** + * @returns {Promise} + */ + getMetadata() { + const ret = wasm.m4areadercontext_getMetadata(this.__wbg_ptr); + return takeObject(ret); + } +} + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_boolean_get = function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbg_new_abda76e883ba8a5f = function() { + const ret = new Error(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + return ret; + }; + imports.wbg.__wbg_queueMicrotask_12a30234db4045d3 = function(arg0) { + queueMicrotask(getObject(arg0)); + }; + imports.wbg.__wbg_queueMicrotask_48421b3cc9052b68 = function(arg0) { + const ret = getObject(arg0).queueMicrotask; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; + imports.wbg.__wbg_read_e48a676fb81ea800 = function(arg0) { + const ret = getObject(arg0).read(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_debug_5a33c41aeac15ee6 = function(arg0) { + console.debug(getObject(arg0)); + }; + imports.wbg.__wbg_error_09480e4aadca50ad = function(arg0) { + console.error(getObject(arg0)); + }; + imports.wbg.__wbg_info_c261acb2deacd903 = function(arg0) { + console.info(getObject(arg0)); + }; + imports.wbg.__wbg_log_b103404cc5920657 = function(arg0) { + console.log(getObject(arg0)); + }; + imports.wbg.__wbg_warn_2b3adb99ce26c314 = function(arg0) { + console.warn(getObject(arg0)); + }; + imports.wbg.__wbg_newnoargs_76313bd6ff35d0f2 = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_525245e2b9901204 = function() { + const ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_self_3093d5d1f7bcb682 = function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_window_3bcfc4d31bc012f8 = function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_globalThis_86b222e13bdf32ed = function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_global_e5a3fe56f8be9485 = function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_call_1084a111329e68ce = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_call_89af060b4e1523f2 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_new_b85e72ed1bfd57f9 = function(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_55(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return addHeapObject(ret); + } finally { + state0.a = state0.b = 0; + } + }; + imports.wbg.__wbg_resolve_570458cb99d56a43 = function(arg0) { + const ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_95e6edc0f89b73b1 = function(arg0, arg1) { + const ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_876bb3c633745cc6 = function(arg0, arg1, arg2) { + const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_buffer_b7b08af79b0b0974 = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_ea1883e1e5e86686 = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_Uint8Array_247a91427532499e = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_length_8339fcf5d8ecd12e = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_set_d1e79e2388520f18 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_get_224d16597dbbfd96 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_set_eacc7d73fefaafdf = function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper216 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 35, __wbg_adapter_22); + return addHeapObject(ret); + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; + + + + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined' && Object.getPrototypeOf(module) === Object.prototype) + ({module} = module) + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined' && Object.getPrototypeOf(module_or_path) === Object.prototype) + ({module_or_path} = module_or_path) + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('m4a_metadata_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/api/api/m4a-metadata-reader/m4a_metadata_bg.wasm b/api/api/m4a-metadata-reader/m4a_metadata_bg.wasm new file mode 100644 index 000000000..54d29472a Binary files /dev/null and b/api/api/m4a-metadata-reader/m4a_metadata_bg.wasm differ diff --git a/api/api/m4a-metadata-reader/m4a_metadata_bg.wasm.d.ts b/api/api/m4a-metadata-reader/m4a_metadata_bg.wasm.d.ts new file mode 100644 index 000000000..62bf47e0b --- /dev/null +++ b/api/api/m4a-metadata-reader/m4a_metadata_bg.wasm.d.ts @@ -0,0 +1,27 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function __wbg_m4areadercontext_free(a: number, b: number): void; +export function m4areadercontext_newWithReadableStream(a: number): number; +export function m4areadercontext_getMetadata(a: number): number; +export function __wbindgen_malloc(a: number, b: number): number; +export function __wbindgen_realloc( + a: number, + b: number, + c: number, + d: number +): number; +export const __wbindgen_export_2: WebAssembly.Table; +export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h99011da901f2b23b( + a: number, + b: number, + c: number +): void; +export function __wbindgen_free(a: number, b: number, c: number): void; +export function __wbindgen_exn_store(a: number): void; +export function wasm_bindgen__convert__closures__invoke2_mut__h695f61305e585c80( + a: number, + b: number, + c: number, + d: number +): void; diff --git a/api/emails/transactionalEmails.ts b/api/emails/transactionalEmails.ts index 699d61eb7..f81801fc5 100644 --- a/api/emails/transactionalEmails.ts +++ b/api/emails/transactionalEmails.ts @@ -727,7 +727,7 @@ export const sendProjectActivityDigestEmail = async ( while (classificationPath.length !== 0) { const pathPart = classificationPath.pop(); iconExists = await embedImage( - pathPart, + species.species, imageAttachments, `classification-icons/${pathPart}.svg`, false diff --git a/api/models/Device.ts b/api/models/Device.ts index 530acc678..278a183d3 100755 --- a/api/models/Device.ts +++ b/api/models/Device.ts @@ -26,7 +26,6 @@ import type { User } from "./User.js"; import type { Group } from "./Group.js"; import type { Event } from "./Event.js"; import logger from "../logging.js"; -import log from "@log"; import { DeviceType } from "@typedefs/api/consts.js"; import type { DeviceId, @@ -38,6 +37,7 @@ import type { import type { Station } from "@models/Station.js"; import { tryToMatchLocationToStationInGroup } from "@models/util/locationUtils.js"; import { locationField } from "@models/util/util.js"; +import { ClientError } from "@api/customErrors.js"; const Op = Sequelize.Op; @@ -610,7 +610,12 @@ order by hour; let newDevice: Device; const now = new Date(); let stationToAssign; + // NOTE: As far as we're aware this API is only called directly + // from the device, and assumes the device is connected, so we will set the + // lastConnectionTime on the device we create/update. + const deviceIsMovingBetweenGroups = newGroup.id !== this.GroupId; + let shouldDeleteExistingDevice = false; if (this.location) { // NOTE: This needs to happen outside the transaction to succeed. stationToAssign = await tryToMatchLocationToStationInGroup( @@ -627,6 +632,15 @@ order by hour; Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ, }, async (t) => { + const deviceHasRecordingsInCurrentGroup = + !!(await models.Recording.findOne({ + where: { + DeviceId: this.id, + GroupId: this.GroupId, + deletedAt: null, + }, + transaction: t, + })); const conflictingDevice = await models.Device.findOne({ where: { deviceName: newName, @@ -634,35 +648,131 @@ order by hour; }, transaction: t, }); + // NOTE: If we're moving a device back into the same group as a conflicting device, + // we really want to *become* that device, and inherit all its recording history. if (reassign) { - if ( - conflictingDevice !== null && - conflictingDevice.id !== this.id && - !conflictingDevice.active - ) { - // rename the conflicting device - await conflictingDevice.update( - { deviceName: `${newName}_old` }, - { transaction: t } - ); + let newKind = this.kind; + if (conflictingDevice !== null) { + if (conflictingDevice.kind !== newKind) { + if (conflictingDevice.kind === DeviceType.Hybrid) { + newKind = conflictingDevice.kind; + } + if ( + (conflictingDevice.kind === DeviceType.Audio && + newKind === DeviceType.Thermal) || + (conflictingDevice.kind === DeviceType.Thermal && + newKind === DeviceType.Audio) + ) { + newKind = DeviceType.Hybrid; + } + if ( + newKind === DeviceType.Unknown && + conflictingDevice.kind !== DeviceType.Unknown + ) { + newKind = conflictingDevice.kind; + } + } + } + if (deviceIsMovingBetweenGroups) { + // If the device in the old group has recordings, set it inactive, otherwise it's safe to delete it from + // the group that we're moving it from. + if (deviceHasRecordingsInCurrentGroup) { + await this.update({ active: false }, { transaction: t }); + } else { + shouldDeleteExistingDevice = true; + } + if ( + conflictingDevice !== null && + conflictingDevice.id !== this.id && + !conflictingDevice.active + ) { + // There's an inactive device in the destination group with the same name. + // We want to *become* that device and set it active. + await conflictingDevice.update( + { + password: newPassword, + // This could be a replacement device, so overwrite the old saltId and uuid + saltId: this.saltId, + uuid: this.uuid, + lastConnectionTime: now, + location: this.location, + kind: newKind, + active: true, + }, + { transaction: t } + ); + newDevice = conflictingDevice; + } else { + // Just create the new device in the destination group. + newDevice = (await models.Device.create( + { + deviceName: newName, + GroupId: newGroup.id, + password: newPassword, + saltId: this.saltId, + uuid: this.uuid, + lastConnectionTime: now, + location: this.location, + kind: this.kind, + }, + { + transaction: t, + } + )) as Device; + } + } else { + // Device is being reassigned to the same group it's currently in. + if (conflictingDevice !== null) { + // Create a new device in the new group, which becomes the existing device, inheriting its history. + await conflictingDevice.update( + { + password: newPassword, + // This could be a replacement device, so overwrite the old saltId and uuid + saltId: this.saltId, + uuid: this.uuid, + // NOTE: As far as we're aware this API is only called directly + // from the device, and assumes the device is connected. + lastConnectionTime: now, + location: this.location, + kind: newKind, + active: true, + }, + { transaction: t } + ); + shouldDeleteExistingDevice = false; + newDevice = conflictingDevice; + } else { + newDevice = (await models.Device.create( + { + deviceName: newName, + GroupId: newGroup.id, + password: newPassword, + saltId: this.saltId, + uuid: this.uuid, + // NOTE: As far as we're aware this API is only called directly + // from the device, and assumes the device is connected. + lastConnectionTime: now, + location: this.location, + kind: this.kind, + }, + { + transaction: t, + } + )) as Device; + } } - await this.update( - { - deviceName: newName, - GroupId: newGroup.id, - password: newPassword, - lastConnectionTime: now, - active: true, - }, - { transaction: t } - ); - newDevice = this; } else { if (conflictingDevice !== null) { - logger.warn("Got conflicting device %s", conflictingDevice); - throw new Error(); + throw new ClientError( + `A device with the name '${newName}' already exists in '${newGroup.groupName}'` + ); + } + if (deviceHasRecordingsInCurrentGroup) { + await this.update({ active: false }, { transaction: t }); + } else { + // We can safely delete the existing device. + shouldDeleteExistingDevice = true; } - await this.update({ active: false }, { transaction: t }); // We need to either find an existing station for this DeviceHistory entry, or create a new one: // NOTE: When a device is re-registered it keeps the last known location. newDevice = (await models.Device.create( @@ -672,6 +782,8 @@ order by hour; password: newPassword, saltId: this.saltId, uuid: this.uuid, + // NOTE: As far as we're aware this API is only called directly + // from the device, and assumes the device is connected. lastConnectionTime: now, location: this.location, kind: this.kind, @@ -714,6 +826,21 @@ order by hour; await models.DeviceHistory.create(newDeviceHistoryEntry, { transaction: t, }); + // NOTE: Special case: If the device is moving out of the `new` group, + // we delete the old device and all its recordings + const group = await models.Group.findByPk(this.GroupId, { + transaction: t, + }); + if (group && group.groupName === "new") { + // Delete every recording properly + await models.Recording.update( + { deletedAt: new Date() }, + { where: { DeviceId: this.id }, transaction: t } + ); + await this.destroy({ transaction: t }); + } else if (shouldDeleteExistingDevice) { + await this.destroy({ transaction: t }); + } } ); } catch (e) { diff --git a/api/waitForApi.js b/api/waitForApi.js index eea10e81e..3cfd3f9fb 100755 --- a/api/waitForApi.js +++ b/api/waitForApi.js @@ -24,7 +24,7 @@ const apiServerIsUp = async (url) => { const fiveMinutesFromNow = new Date( now.setMinutes(now.getMinutes() + waitMins) ); - console.log(`Waiting up to ${waitMins} minutes for API sever...`); + console.log(`Waiting up to ${waitMins} minutes for API server...`); while (new Date() < fiveMinutesFromNow) { let up = false; diff --git a/browse-next/.eslintignore b/browse-next/.eslintignore deleted file mode 100644 index c8b38d87e..000000000 --- a/browse-next/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -.eslintrc.js -*.d.ts -node_modules -dist diff --git a/browse-next/.eslintrc.js b/browse-next/.eslintrc.js deleted file mode 100644 index 9ef4e0bca..000000000 --- a/browse-next/.eslintrc.js +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-env node */ -require("@rushstack/eslint-patch/modern-module-resolution"); - -module.exports = { - root: true, - extends: [ - "plugin:vue/vue3-essential", - "eslint:recommended", - "@vue/eslint-config-typescript/recommended", - "@vue/eslint-config-prettier", - ], - env: { - "vue/setup-compiler-macros": true, - browser: true, - es2022: true, - }, - parserOptions: { - ecmaVersion: "latest", - lib: ["es2022", "DOM"], - sourceType: "module", - parser: "@typescript-eslint/parser", - }, - rules: { - "no-prototype-builtins": "off", - "linebreak-style": ["warn", "unix"], - quotes: "off", - semi: ["warn", "always"], - curly: ["warn", "all"], - "no-console": ["warn", { allow: ["warn", "error", "assert"] }], - "no-debugger": ["warn"], - "no-undef": ["warn"], - "@typescript-eslint/no-unused-vars": [ - "warn", - { - // Allow unused vars prefaced by an underscore - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - caughtErrorsIgnorePattern: "^_", - }, - ], - "no-unused-vars": ["off"], - "brace-style": ["warn"], - "prefer-const": ["warn"], - "vue/no-unused-vars": [ - "warn", - { - ignorePattern: "^_", - }, - ], - "@typescript-eslint/no-explicit-any": "warn", - "@typescript-eslint/no-non-null-assertion": ["off"], - "vue/no-setup-props-destructure": ["off"], - }, -}; diff --git a/browse-next/components.d.ts b/browse-next/components.d.ts index 9619a6697..9a5570940 100644 --- a/browse-next/components.d.ts +++ b/browse-next/components.d.ts @@ -12,6 +12,7 @@ declare module 'vue' { BAlert: typeof import('bootstrap-vue-next')['BAlert'] BBadge: typeof import('bootstrap-vue-next')['BBadge'] BButton: typeof import('bootstrap-vue-next')['BButton'] + BButtonGroup: typeof import('bootstrap-vue-next')['BButtonGroup'] BDropdown: typeof import('bootstrap-vue-next')['BDropdown'] BDropdownDivider: typeof import('bootstrap-vue-next')['BDropdownDivider'] BDropdownGroup: typeof import('bootstrap-vue-next')['BDropdownGroup'] @@ -27,6 +28,7 @@ declare module 'vue' { BFormRadioGroup: typeof import('bootstrap-vue-next')['BFormRadioGroup'] BFormSelect: typeof import('bootstrap-vue-next')['BFormSelect'] BimodalSwitch: typeof import('./src/components/BimodalSwitch.vue')['default'] + BInput: typeof import('bootstrap-vue-next')['BInput'] BLink: typeof import('bootstrap-vue-next')['BLink'] BlockingUserActionRequiredModal: typeof import('./src/components/BlockingUserActionRequiredModal.vue')['default'] BModal: typeof import('bootstrap-vue-next')['BModal'] @@ -34,7 +36,6 @@ declare module 'vue' { BPopover: typeof import('bootstrap-vue-next')['BPopover'] BProgress: typeof import('bootstrap-vue-next')['BProgress'] BSpinner: typeof import('bootstrap-vue-next')['BSpinner'] - BTable: typeof import('bootstrap-vue-next')['BTable'] CardTable: typeof import('./src/components/CardTable.vue')['default'] CptvPlayer: typeof import('./src/components/cptv-player/CptvPlayer.vue')['default'] CptvSingleFrame: typeof import('./src/components/CptvSingleFrame.vue')['default'] @@ -67,13 +68,13 @@ declare module 'vue' { RecordingViewActionButtons: typeof import('./src/components/RecordingViewActionButtons.vue')['default'] RecordingViewLabels: typeof import('./src/components/RecordingViewLabels.vue')['default'] RecordingViewTracks: typeof import('./src/components/RecordingViewTracks.vue')['default'] + RenameableLocationName: typeof import('./src/components/RenameableLocationName.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] ScrubberWrapper: typeof import('./src/components/ScrubberWrapper.vue')['default'] SectionHeader: typeof import('./src/components/SectionHeader.vue')['default'] SwitchProjectsModal: typeof import('./src/components/SwitchProjectsModal.vue')['default'] TagImage: typeof import('./src/components/TagImage.vue')['default'] - Temp: typeof import('./src/components/Temp.vue')['default'] TracksScrubber: typeof import('./src/components/TracksScrubber.vue')['default'] TrackTaggerRow: typeof import('./src/components/TrackTaggerRow.vue')['default'] TwoStepActionButton: typeof import('./src/components/TwoStepActionButton.vue')['default'] diff --git a/browse-next/eslint.config.mjs b/browse-next/eslint.config.mjs new file mode 100644 index 000000000..b686cc179 --- /dev/null +++ b/browse-next/eslint.config.mjs @@ -0,0 +1,74 @@ +import globals from "globals"; + +import jsLint from "@eslint/js"; +import tsLint from "typescript-eslint"; +import vueLint from "eslint-plugin-vue"; +import { includeIgnoreFile } from "@eslint/compat"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const gitignorePath = path.resolve(__dirname, ".gitignore"); + +export default [ + includeIgnoreFile(gitignorePath), + // config parsers + { + files: ["**/*.{js,mjs,cjs,ts,mts,jsx,tsx}"], + ignores: ["node_modules", "dist", "eslint.config.mjs", "*.d.ts", "public"] + }, + { + files: ["*.vue", "**/*.vue"], + ignores: ["node_modules", "dist", "eslint.config.mjs", "*.d.ts", "public"], + languageOptions: { + parserOptions: { + parser: "@typescript-eslint/parser", + sourceType: "module" + } + }, + }, + // config envs + { + languageOptions: { + globals: { ...globals.browser, ...globals.node } + } + }, + // syntax rules + jsLint.configs.recommended, + ...tsLint.configs.recommended, + ...vueLint.configs["flat/essential"], + { + rules: { + "no-prototype-builtins": "off", + "linebreak-style": ["warn", "unix"], + quotes: "off", + semi: ["warn", "always"], + curly: ["warn", "all"], + "no-console": ["warn", { allow: ["warn", "error", "assert"] }], + "no-debugger": ["warn"], + "no-undef": ["warn"], + "no-unused-vars": ["off"], + "brace-style": ["warn"], + "prefer-const": ["warn"], + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-non-null-assertion": ["off"], + "vue/no-setup-props-destructure": ["off"], + "@typescript-eslint/no-unused-expressions": ["off"], + "@typescript-eslint/no-unused-vars": [ + "off", + { + // Allow unused vars prefaced by an underscore + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }], + "vue/no-unused-vars": [ + "warn", + { + ignorePattern: "^_", + }, + ], + } + } +]; diff --git a/browse-next/index.html b/browse-next/index.html index c0be8b38a..918b98d09 100644 --- a/browse-next/index.html +++ b/browse-next/index.html @@ -9,9 +9,8 @@ - + - diff --git a/browse-next/package-lock.json b/browse-next/package-lock.json index c95766aef..156b8f99d 100644 --- a/browse-next/package-lock.json +++ b/browse-next/package-lock.json @@ -8,17 +8,18 @@ "name": "browse-next", "version": "0.0.1", "dependencies": { + "@floating-ui/vue": "^1.1.4", "@fortawesome/fontawesome-svg-core": "^6.3.0", "@fortawesome/free-regular-svg-icons": "^6.3.0", "@fortawesome/free-solid-svg-icons": "^6.3.0", "@fortawesome/vue-fontawesome": "^3.0.3", "@jsquash/webp": "^1.1.3", "@popperjs/core": "^2.11.7", - "@vueform/multiselect": "^2.6.9", + "@vueform/multiselect": "^2.6.10", "@vuepic/vue-datepicker": "^4.2.0", "@vueuse/core": "^9.13.0", "bootstrap": "^5.3.2", - "bootstrap-vue-next": "^0.21.2", + "bootstrap-vue-next": "^0.24.12", "chartist": "^1.3.0", "exif-js_fixed": "^2.3.1", "hash-wasm": "^4.9.0", @@ -28,28 +29,32 @@ "suncalc": "^1.9.0", "tesseract-wasm": "^0.10.0", "tz-lookup-oss": "^6.3.0", - "vue": "^3.4.19", - "vue-multiselect": "^3.0.0-beta.1", + "vue": "^3.5.1", "vue-router": "^4.1.6" }, "devDependencies": { + "@eslint/compat": "^1.2.0", + "@eslint/js": "^9.12.0", "@rushstack/eslint-patch": "^1.2.0", + "@stylistic/eslint-plugin": "^2.9.0", "@tsconfig/node18": "^2.0.1", "@types/leaflet": "^1.9.3", "@types/luxon": "^3.2.0", "@types/node": "^18.16.16", "@types/suncalc": "^1.8.1", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.8.0", + "@typescript-eslint/parser": "^8.8.0", "@vitejs/plugin-vue": "^4.2.3", "@vue/eslint-config-prettier": "^7.1.0", - "@vue/eslint-config-typescript": "^12.0.0", + "@vue/eslint-config-typescript": "^14.0.0", "@vue/tsconfig": "^0.4.0", - "eslint": "^8.40.0", + "eslint": "^9.12.0", "eslint-plugin-vue": "^9.13.0", + "globals": "^15.10.0", "less": "^4.1.3", "prettier": "^2.8.8", "typescript": "5.3.3", + "typescript-eslint": "^8.8.0", "unplugin-vue-components": "^0.26.0", "vite": "^4.3.7", "vite-plugin-eslint": "^1.8.1", @@ -59,18 +64,41 @@ } }, "node_modules/@antfu/utils": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", - "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -79,11 +107,26 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", - "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" @@ -97,6 +140,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -113,6 +157,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -129,6 +174,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -145,6 +191,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -161,6 +208,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -177,6 +225,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -193,6 +242,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -209,6 +259,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -225,6 +276,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -241,6 +293,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -257,6 +310,7 @@ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -273,6 +327,7 @@ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -289,6 +344,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -305,6 +361,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -321,6 +378,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -337,6 +395,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -353,6 +412,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -369,6 +429,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -385,6 +446,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -401,6 +463,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -417,6 +480,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -433,6 +497,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -446,6 +511,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -457,24 +523,92 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.0.tgz", + "integrity": "sha512-CkPWddN7J9JPrQedEr2X7AjK9y1jaMJtxZ4A/+jTMFA2+n5BWhcKHW/EbJyARqg2zzQfgtWUtVmG3hrG6+nGpg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -482,58 +616,122 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.8" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", + "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "license": "MIT" }, "node_modules/@floating-ui/vue": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.0.6.tgz", - "integrity": "sha512-EdrOljjkpkkqZnrpqUcPoz9NvHxuTjUtSInh6GMv3+Mcy+giY2cE2pHh9rpacRcZ2eMSCxel9jWkWXTjLmY55w==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.5.tgz", + "integrity": "sha512-ynL1p5Z+woPVSwgMGqeDrx6HrJfGIDzFyESFkyqJKilGW1+h/8yVY29Khn0LaU6wHBRwZ13ntG6reiHWK6jyzw==", + "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.6.1", - "@floating-ui/utils": "^0.2.1", + "@floating-ui/dom": "^1.0.0", + "@floating-ui/utils": "^0.2.8", "vue-demi": ">=0.13.0" } }, "node_modules/@floating-ui/vue/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, + "license": "MIT", "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" @@ -555,72 +753,82 @@ } }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", - "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==", - "hasInstallScript": true, + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", - "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", - "hasInstallScript": true, + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.4.0" + "@fortawesome/fontawesome-common-types": "6.6.0" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", - "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", - "hasInstallScript": true, + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", + "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.4.0" + "@fortawesome/fontawesome-common-types": "6.6.0" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", - "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", - "hasInstallScript": true, + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "license": "(CC-BY-4.0 AND MIT)", "dependencies": { - "@fortawesome/fontawesome-common-types": "6.4.0" + "@fortawesome/fontawesome-common-types": "6.6.0" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/vue-fontawesome": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.3.tgz", - "integrity": "sha512-KCPHi9QemVXGMrfuwf3nNnNo129resAIQWut9QTAMXmXqL2ErABC6ohd2yY5Ipq0CLWNbKHk8TMdTXL/Zf3ZhA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz", + "integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==", + "license": "MIT", "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~1 || ~6", "vue": ">= 3.0.0 < 4" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -628,6 +836,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -636,22 +845,31 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jsquash/webp": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@jsquash/webp/-/webp-1.1.3.tgz", - "integrity": "sha512-lvIc+zczLMDPmBl0f4JCie7U7eqbcMu87Z+Txd5GNzIeKUQBeZ5ss/sbakWKfja8Lx8/UtH1BfewzPvb8/o4aA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@jsquash/webp/-/webp-1.4.0.tgz", + "integrity": "sha512-yKJb6Hilq+qV/4C4qTDEalBobNwJO09LeHHtilmWg5mYHFUDwMunfeAap/r3cL5KsHkGOoI0IjY2nKbTaHq9Bw==", + "license": "Apache-2.0", "dependencies": { "wasm-feature-detect": "^1.2.11" } @@ -661,6 +879,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -674,6 +893,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -683,6 +903,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -695,21 +916,23 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, "node_modules/@rollup/plugin-virtual": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.1.tgz", - "integrity": "sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -718,10 +941,11 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", + "integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -740,17 +964,66 @@ } }, "node_modules/@rushstack/eslint-patch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.1.tgz", - "integrity": "sha512-RkmuBcqiNioeeBKbgzMlOdreUkJfYaSjwgx9XDgGGpjvWgyaxWvDmZVSN9CS6LjEASadhgPv2BcFp+SeouWXXA==", - "dev": true + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.9.0.tgz", + "integrity": "sha512-OrDyFAYjBT61122MIY1a3SfEgy3YCMgt2vL4eoPmvTwDBwyQhAXurxNQznlRD/jESNfYWfID8Ej+31LljvF7Xg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^8.8.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/@swc/core": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.62.tgz", - "integrity": "sha512-J58hWY+/G8vOr4J6ZH9hLg0lMSijZtqIIf4HofZezGog/pVX6sJyBJ40dZ1ploFkDIlWTWvJyqtpesBKS73gkQ==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.26.tgz", + "integrity": "sha512-f5uYFf+TmMQyYIoxkn/evWhNGuUzC730dFwAKGwBVHHVoPyak1/GvJUm6i1SKl+2Hrj9oN0i3WSoWWZ4pgI8lw==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.12" + }, "engines": { "node": ">=10" }, @@ -759,19 +1032,19 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.62", - "@swc/core-darwin-x64": "1.3.62", - "@swc/core-linux-arm-gnueabihf": "1.3.62", - "@swc/core-linux-arm64-gnu": "1.3.62", - "@swc/core-linux-arm64-musl": "1.3.62", - "@swc/core-linux-x64-gnu": "1.3.62", - "@swc/core-linux-x64-musl": "1.3.62", - "@swc/core-win32-arm64-msvc": "1.3.62", - "@swc/core-win32-ia32-msvc": "1.3.62", - "@swc/core-win32-x64-msvc": "1.3.62" + "@swc/core-darwin-arm64": "1.7.26", + "@swc/core-darwin-x64": "1.7.26", + "@swc/core-linux-arm-gnueabihf": "1.7.26", + "@swc/core-linux-arm64-gnu": "1.7.26", + "@swc/core-linux-arm64-musl": "1.7.26", + "@swc/core-linux-x64-gnu": "1.7.26", + "@swc/core-linux-x64-musl": "1.7.26", + "@swc/core-win32-arm64-msvc": "1.7.26", + "@swc/core-win32-ia32-msvc": "1.7.26", + "@swc/core-win32-x64-msvc": "1.7.26" }, "peerDependencies": { - "@swc/helpers": "^0.5.0" + "@swc/helpers": "*" }, "peerDependenciesMeta": { "@swc/helpers": { @@ -780,13 +1053,14 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.62.tgz", - "integrity": "sha512-MmGilibITz68LEje6vJlKzc2gUUSgzvB3wGLSjEORikTNeM7P8jXVxE4A8fgZqDeudJUm9HVWrxCV+pHDSwXhA==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.26.tgz", + "integrity": "sha512-FF3CRYTg6a7ZVW4yT9mesxoVVZTrcSWtmZhxKCYJX9brH4CS/7PRPjAKNk6kzWgWuRoglP7hkjQcd6EpMcZEAw==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -796,13 +1070,14 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.62.tgz", - "integrity": "sha512-Xl93MMB3sCWVlYWuQIB+v6EQgzoiuQYK5tNt9lsHoIEVu2zLdkQjae+5FUHZb1VYqCXIiWcULFfVz0R4Sjb7JQ==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.26.tgz", + "integrity": "sha512-az3cibZdsay2HNKmc4bjf62QVukuiMRh5sfM5kHR/JMTrLyS6vSw7Ihs3UTkZjUxkLTT8ro54LI6sV6sUQUbLQ==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -812,13 +1087,14 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.62.tgz", - "integrity": "sha512-nJsp6O7kCtAjTTMcIjVB0g5y1JNiYAa5q630eiwrnaHUusEFoANDdORI3Z9vXeikMkng+6yIv9/V8Rb093xLjQ==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.26.tgz", + "integrity": "sha512-VYPFVJDO5zT5U3RpCdHE5v1gz4mmR8BfHecUZTmD2v1JeFY6fv9KArJUpjrHEEsjK/ucXkQFmJ0jaiWXmpOV9Q==", "cpu": [ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -828,13 +1104,14 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.62.tgz", - "integrity": "sha512-XGsV93vpUAopDt5y6vPwbK1Nc/MlL55L77bAZUPIiosWD1cWWPHNtNSpriE6+I+JiMHe0pqtfS/SSTk6ZkFQVw==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.26.tgz", + "integrity": "sha512-YKevOV7abpjcAzXrhsl+W48Z9mZvgoVs2eP5nY+uoMAdP2b3GxC0Df1Co0I90o2lkzO4jYBpTMcZlmUXLdXn+Q==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -844,13 +1121,14 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.62.tgz", - "integrity": "sha512-ESUmJjSlTTkoBy9dMG49opcNn8BmviqStMhwyeD1G8XRnmRVCZZgoBOKdvCXmJhw8bQXDhZumeaTUB+OFUKVXg==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.26.tgz", + "integrity": "sha512-3w8iZICMkQQON0uIcvz7+Q1MPOW6hJ4O5ETjA0LSP/tuKqx30hIniCGOgPDnv3UTMruLUnQbtBwVCZTBKR3Rkg==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -860,13 +1138,14 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.62.tgz", - "integrity": "sha512-wnHJkt3ZBrax3SFnUHDcncG6mrSg9ZZjMhQV9Mc3JL1x1s1Gy9rGZCoBNnV/BUZWTemxIBcQbANRSDut/WO+9A==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.26.tgz", + "integrity": "sha512-c+pp9Zkk2lqb06bNGkR2Looxrs7FtGDMA4/aHjZcCqATgp348hOKH5WPvNLBl+yPrISuWjbKDVn3NgAvfvpH4w==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -876,13 +1155,14 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.62.tgz", - "integrity": "sha512-9oRbuTC/VshB66Rgwi3pTq3sPxSTIb8k9L1vJjES+dDMKa29DAjPtWCXG/pyZ00ufpFZgkGEuAHH5uqUcr1JQg==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.26.tgz", + "integrity": "sha512-PgtyfHBF6xG87dUSSdTJHwZ3/8vWZfNIXQV2GlwEpslrOkGqy+WaiiyE7Of7z9AvDILfBBBcJvJ/r8u980wAfQ==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -892,13 +1172,14 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.62.tgz", - "integrity": "sha512-zv14vlF2VRrxS061XkfzGjCYnOrEo5glKJjLK5PwUKysIoVrx/L8nAbFxjkX5cObdlyoqo+ekelyBPAO+4bS0w==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.26.tgz", + "integrity": "sha512-9TNXPIJqFynlAOrRD6tUQjMq7KApSklK3R/tXgIxc7Qx+lWu8hlDQ/kVPLpU7PWvMMwC/3hKBW+p5f+Tms1hmA==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -908,13 +1189,14 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.62.tgz", - "integrity": "sha512-8MC/PZQSsOP2iA/81tAfNRqMWyEqTS/8zKUI67vPuLvpx6NAjRn3E9qBv7iFqH79iqZNzqSMo3awnLrKZyFbcw==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.26.tgz", + "integrity": "sha512-9YngxNcG3177GYdsTum4V98Re+TlCeJEP4kEwEg9EagT5s3YejYdKwVAkAsJszzkXuyRDdnHUpYbTrPG6FiXrQ==", "cpu": [ "ia32" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -924,13 +1206,14 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.62", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.62.tgz", - "integrity": "sha512-GJSmUJ95HKHZXAxiuPUmrcm/S3ivQvEzXhOZaIqYBIwUsm02vFZkClsV7eIKzWjso1t0+I/8MjrnUNaSWqh1rQ==", + "version": "1.7.26", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.26.tgz", + "integrity": "sha512-VR+hzg9XqucgLjXxA13MtV5O3C0bK0ywtLIBw/+a+O+Oc6mxFWHtdUeXDbIi5AiPbn0fjgVJMqYnyjGyyX8u0w==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -939,106 +1222,129 @@ "node": ">=10" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", + "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@tsconfig/node18": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-2.0.1.tgz", "integrity": "sha512-UqdfvuJK0SArA2CxhKWwwAWfnVSXiYe63bVpMutc27vpngCntGUZQETO24pEJ46zU6XM+7SpqYoMgcO3bM11Ew==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/eslint": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.0.tgz", - "integrity": "sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==", + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/geojson": { - "version": "7946.0.10", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", - "dev": true + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/leaflet": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.3.tgz", - "integrity": "sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==", + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", + "integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==", "dev": true, + "license": "MIT", "dependencies": { "@types/geojson": "*" } }, "node_modules/@types/luxon": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz", - "integrity": "sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "18.16.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.16.tgz", - "integrity": "sha512-NpaM49IGQQAUlBhHMF82QH80J08os4ZmyF9MkpCzWAGuOHqE4gTEbhzd7L3l5LmWuZ6E0OiC1FweQ4tsiW35+g==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true + "version": "18.19.54", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.54.tgz", + "integrity": "sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/suncalc": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@types/suncalc/-/suncalc-1.9.0.tgz", - "integrity": "sha512-+c2wOhgyG5ZTtxfC8XG5SGY36HNlXYaPqv/mqBbjXn3iKWx0Lj375AShoKbjF1MCGoXFhSl3jLlmxTksjaGyZQ==", - "dev": true + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/suncalc/-/suncalc-1.9.2.tgz", + "integrity": "sha512-ATAGBHHfA1TlE2tjfidLyTcysjoT2JHHEAmWRULh73SU9UTn++j5fqHEW16X6Y/2Li87jEQXzgu4R/OOdlDqzw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/web-bluetooth": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", - "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", + "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1047,26 +1353,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", + "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1075,16 +1382,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", + "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1092,26 +1400,24 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", + "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -1119,12 +1425,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1132,22 +1439,23 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", + "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1159,231 +1467,154 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", + "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.8.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/@vitejs/plugin-vue": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", - "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", "dev": true, + "license": "MIT", "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.0.0", + "vite": "^4.0.0 || ^5.0.0", "vue": "^3.2.25" } }, "node_modules/@volar/language-core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.4.1.tgz", - "integrity": "sha512-EIY+Swv+TjsWpxOxujjMf1ZXqOjg9MT2VMXZ+1dKva0wD8W0L6EtptFFcCJdBbcKmGMFkr57Qzz9VNMWhs3jXQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", "dev": true, + "license": "MIT", "dependencies": { - "@volar/source-map": "1.4.1" + "@volar/source-map": "1.11.1" } }, "node_modules/@volar/source-map": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.4.1.tgz", - "integrity": "sha512-bZ46ad72dsbzuOWPUtJjBXkzSQzzSejuR3CT81+GvTEI2E994D8JPXzM3tl98zyCNnjgs4OkRyliImL1dvJ5BA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", "dev": true, + "license": "MIT", "dependencies": { - "muggle-string": "^0.2.2" + "muggle-string": "^0.3.1" } }, "node_modules/@volar/typescript": { - "version": "1.4.1-patch.2", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.4.1-patch.2.tgz", - "integrity": "sha512-lPFYaGt8OdMEzNGJJChF40uYqMO4Z/7Q9fHPQC/NRVtht43KotSXLrkPandVVMf9aPbiJ059eAT+fwHGX16k4w==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", "dev": true, + "license": "MIT", "dependencies": { - "@volar/language-core": "1.4.1" - }, - "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/@volar/vue-language-core": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@volar/vue-language-core/-/vue-language-core-1.6.5.tgz", - "integrity": "sha512-IF2b6hW4QAxfsLd5mePmLgtkXzNi+YnH6ltCd80gb7+cbdpFMjM1I+w+nSg2kfBTyfu+W8useCZvW89kPTBpzg==", - "dev": true, - "dependencies": { - "@volar/language-core": "1.4.1", - "@volar/source-map": "1.4.1", - "@vue/compiler-dom": "^3.3.0", - "@vue/compiler-sfc": "^3.3.0", - "@vue/reactivity": "^3.3.0", - "@vue/shared": "^3.3.0", - "minimatch": "^9.0.0", - "muggle-string": "^0.2.2", - "vue-template-compiler": "^2.7.14" - } - }, - "node_modules/@volar/vue-language-core/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@volar/vue-language-core/node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@volar/vue-typescript": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/@volar/vue-typescript/-/vue-typescript-1.6.5.tgz", - "integrity": "sha512-er9rVClS4PHztMUmtPMDTl+7c7JyrxweKSAEe/o/Noeq2bQx6v3/jZHVHBe8ZNUti5ubJL/+Tg8L3bzmlalV8A==", - "dev": true, - "dependencies": { - "@volar/typescript": "1.4.1-patch.2", - "@volar/vue-language-core": "1.6.5" - }, - "peerDependencies": { - "typescript": "*" + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" } }, "node_modules/@vue/compiler-core": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz", - "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.11.tgz", + "integrity": "sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.29", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.11", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz", - "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.11.tgz", + "integrity": "sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==", + "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-core": "3.5.11", + "@vue/shared": "3.5.11" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz", - "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==", - "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.29", - "@vue/compiler-dom": "3.4.29", - "@vue/compiler-ssr": "3.4.29", - "@vue/shared": "3.4.29", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.11.tgz", + "integrity": "sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.11", + "@vue/compiler-dom": "3.5.11", + "@vue/compiler-ssr": "3.5.11", + "@vue/shared": "3.5.11", "estree-walker": "^2.0.2", - "magic-string": "^0.30.10", - "postcss": "^8.4.38", + "magic-string": "^0.30.11", + "postcss": "^8.4.47", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz", - "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.11.tgz", + "integrity": "sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA==", + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-dom": "3.5.11", + "@vue/shared": "3.5.11" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", - "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" }, "node_modules/@vue/eslint-config-prettier": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", "integrity": "sha512-Pv/lVr0bAzSIHLd9iz0KnvAr4GKyCEl+h52bc4e5yWuDVtLgFwycF7nrbWTAQAS+FU6q1geVd07lc6EWfJiWKQ==", "dev": true, + "license": "MIT", "dependencies": { "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0" @@ -1394,21 +1625,49 @@ } }, "node_modules/@vue/eslint-config-typescript": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz", - "integrity": "sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.0.0.tgz", + "integrity": "sha512-JNlgQivBBkj7EvvOchSbGQ1ATO9AquVK6hzn6RsXZO/7anI8A9PGq7w6ca2NepuRXNOF6A9C5T00Qi+zfWL1gg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "^6.7.0", - "@typescript-eslint/parser": "^6.7.0", - "vue-eslint-parser": "^9.3.1" + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", + "typescript-eslint": "^8.6.0", + "vue-eslint-parser": "^9.4.3" }, "engines": { - "node": "^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0", - "eslint-plugin-vue": "^9.0.0", "typescript": "*" }, "peerDependenciesMeta": { @@ -1418,65 +1677,72 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", - "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.11.tgz", + "integrity": "sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w==", + "license": "MIT", "dependencies": { - "@vue/shared": "3.4.29" + "@vue/shared": "3.5.11" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz", - "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.11.tgz", + "integrity": "sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA==", + "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/reactivity": "3.5.11", + "@vue/shared": "3.5.11" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz", - "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==", - "dependencies": { - "@vue/reactivity": "3.4.29", - "@vue/runtime-core": "3.4.29", - "@vue/shared": "3.4.29", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.11.tgz", + "integrity": "sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.11", + "@vue/runtime-core": "3.5.11", + "@vue/shared": "3.5.11", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz", - "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.11.tgz", + "integrity": "sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA==", + "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-ssr": "3.5.11", + "@vue/shared": "3.5.11" }, "peerDependencies": { - "vue": "3.4.29" + "vue": "3.5.11" } }, "node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==" + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.11.tgz", + "integrity": "sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==", + "license": "MIT" }, "node_modules/@vue/tsconfig": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.4.0.tgz", "integrity": "sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@vueform/multiselect": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/@vueform/multiselect/-/multiselect-2.6.9.tgz", - "integrity": "sha512-eW+pMaWFGnK+WGvUPcB/mEwLx/5Y4VB8wRFGFQZ9c5Wi+K/xFrm/nQ/NbtrFQUcX0nbuQWKYnFBmwa5MENGQxw==" + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/@vueform/multiselect/-/multiselect-2.6.10.tgz", + "integrity": "sha512-w1otA5P2F7Bg0Er9ohIsBg/Xkda/9ZzCO62PmXXmjS2mOEKWPdb/852FQbc8Gv99EYRotfxgyJ5lTC+c7YeCTA==" }, "node_modules/@vuepic/vue-datepicker": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-4.5.1.tgz", "integrity": "sha512-zdG37Q8iuwTPWnQEI9/WvabqmrDDfDLgdXUTW08FXHSbiHuNN2S+bVeN1Wm9yeD+onHgzEn6DiW6J3fYXEfa0Q==", + "license": "MIT", "dependencies": { "date-fns": "^2.29.3", "date-fns-tz": "^1.3.7" @@ -1492,6 +1758,7 @@ "version": "9.13.0", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.16", "@vueuse/metadata": "9.13.0", @@ -1503,10 +1770,11 @@ } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", - "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, + "license": "MIT", "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" @@ -1531,6 +1799,7 @@ "version": "9.13.0", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } @@ -1539,6 +1808,7 @@ "version": "9.13.0", "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", "dependencies": { "vue-demi": "*" }, @@ -1547,10 +1817,11 @@ } }, "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", - "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", "hasInstallScript": true, + "license": "MIT", "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" @@ -1572,10 +1843,11 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1588,6 +1860,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1597,6 +1870,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1608,20 +1882,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1637,6 +1903,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1649,42 +1916,40 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "Python-2.0" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/bootstrap": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", - "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "funding": [ { "type": "github", @@ -1695,131 +1960,42 @@ "url": "https://opencollective.com/bootstrap" } ], + "license": "MIT", "peerDependencies": { "@popperjs/core": "^2.11.8" } }, "node_modules/bootstrap-vue-next": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/bootstrap-vue-next/-/bootstrap-vue-next-0.21.2.tgz", - "integrity": "sha512-2VMlrTETlT2d57T+rG7SKCGPD2CdcPjNHil6I3H/rW5+zXcSvx7luEKIBgnNRN+vDJaDomo7iOHqIGphJTlrdw==", - "dependencies": { - "@floating-ui/vue": "^1.0.6", - "@vueuse/core": "^10.10.0" - }, + "version": "0.24.23", + "resolved": "https://registry.npmjs.org/bootstrap-vue-next/-/bootstrap-vue-next-0.24.23.tgz", + "integrity": "sha512-oUxe4nfQjgcznsQKVCiRzYmvi8dEBq6FZSvnNDAqRvL/O8G8FcW0Lnzg2fAItKX4/kZzgiffO+el2qDfVKmXDg==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/bootstrap-vue-next" }, "peerDependencies": { - "vue": "^3.4.27" - } - }, - "node_modules/bootstrap-vue-next/node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" - }, - "node_modules/bootstrap-vue-next/node_modules/@vueuse/core": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz", - "integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.0", - "@vueuse/shared": "10.11.0", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/bootstrap-vue-next/node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/bootstrap-vue-next/node_modules/@vueuse/metadata": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz", - "integrity": "sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/bootstrap-vue-next/node_modules/@vueuse/shared": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.0.tgz", - "integrity": "sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/bootstrap-vue-next/node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.8", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", - "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } + "vue": "^3.5.3" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1830,6 +2006,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1839,6 +2016,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1854,6 +2032,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/chartist/-/chartist-1.3.0.tgz", "integrity": "sha512-M3ckI3ua7EHt08WLZvdi3QXn5g+in27qU6TxjI5bpriS6QwIZlWtisyUhFbpGclW546SlT3SL9oq0vFFDiAo6g==", + "license": "MIT OR WTFPL", "engines": { "node": ">=14" } @@ -1863,6 +2042,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1882,11 +2062,25 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1898,24 +2092,35 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/comlink": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.1.tgz", - "integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==" + "integrity": "sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==", + "license": "Apache-2.0" + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true, + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/copy-anything": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", "dev": true, + "license": "MIT", "dependencies": { "is-what": "^3.14.1" }, @@ -1928,6 +2133,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1942,6 +2148,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -1952,12 +2159,14 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -1973,6 +2182,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.8.tgz", "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==", + "license": "MIT", "peerDependencies": { "date-fns": ">=2.0.0" } @@ -1981,15 +2191,17 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2004,36 +2216,14 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "MIT" }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -2046,6 +2236,7 @@ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "prr": "~1.0.1" @@ -2060,6 +2251,7 @@ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -2096,6 +2288,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -2104,65 +2297,72 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.12.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -2175,6 +2375,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, + "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0" }, @@ -2192,47 +2393,67 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz", - "integrity": "sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==", + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz", + "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.3.0", + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", "natural-compare": "^1.4.0", - "nth-check": "^2.0.1", - "postcss-selector-parser": "^6.0.9", - "semver": "^7.3.5", - "vue-eslint-parser": "^9.3.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", "xml-name-validator": "^4.0.0" }, "engines": { "node": "^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint-scope": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2240,63 +2461,85 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=4.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10.13.0" + "node": "*" } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=0.10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": ">=4.0" + "node": ">=0.10" } }, "node_modules/esrecurse": { @@ -2304,6 +2547,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -2311,11 +2555,12 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -2323,13 +2568,15 @@ "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -2337,25 +2584,29 @@ "node_modules/exif-js_fixed": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/exif-js_fixed/-/exif-js_fixed-2.3.1.tgz", - "integrity": "sha512-NrXAZcUKZtunV/297HbzztmKhf50up9rqqs62KXH/22RR9UH9wIrDgKw4K+jxMoUv0xUQXwLkvMdZBKOvRHoWw==" + "integrity": "sha512-NrXAZcUKZtunV/297HbzztmKhf50up9rqqs62KXH/22RR9UH9wIrDgKw4K+jxMoUv0xUQXwLkvMdZBKOvRHoWw==", + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -2367,44 +2618,62 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2417,6 +2686,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -2429,29 +2699,25 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2459,6 +2725,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2472,72 +2739,31 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "version": "15.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.10.0.tgz", + "integrity": "sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==", "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2548,33 +2774,38 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, + "license": "ISC", "optional": true }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/hash-wasm": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.9.0.tgz", - "integrity": "sha512-7SW7ejyfnRxuOc7ptQHSf4LDoZaWOivfzqw+5rpcQku0nHfmicPKE51ra9BiRLAmT8+gGLestr1XroUkqdjL6w==" + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.11.0.tgz", + "integrity": "sha512-HVusNXlVqHe0fzIzdQOGolnFN6mX/fqcrSAOcTBXdvzrXVHwTz11vXeKRmkR5gTuwVpvHZEIyKoePDvuAR+XwQ==", + "license": "MIT" }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -2587,6 +2818,7 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, + "license": "MIT", "bin": { "he": "bin/he" } @@ -2596,6 +2828,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -2605,10 +2838,11 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -2618,6 +2852,7 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true, + "license": "MIT", "optional": true, "bin": { "image-size": "bin/image-size.js" @@ -2631,6 +2866,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2647,31 +2883,17 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -2680,12 +2902,16 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2696,6 +2922,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2705,6 +2932,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -2717,36 +2945,31 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -2754,28 +2977,49 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } }, "node_modules/leaflet": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==" + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" }, "node_modules/less": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", - "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -2802,6 +3046,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -2815,6 +3060,7 @@ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2827,6 +3073,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -2841,28 +3088,32 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/luxon": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", - "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -2870,6 +3121,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "pify": "^4.0.1", @@ -2880,10 +3132,11 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, + "license": "ISC", "optional": true, "bin": { "semver": "bin/semver" @@ -2894,17 +3147,19 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2916,6 +3171,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, + "license": "MIT", "optional": true, "bin": { "mime": "cli.js" @@ -2925,28 +3181,34 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/muggle-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.2.2.tgz", - "integrity": "sha512-YVE1mIJ4VpUMqZObFndk9CJu6DBJR/GB13p3tXuNbwD4XExaI5EOuRl6BHeIDxIqXZVxSfAC+y6U1Z/IxCfKUg==", - "dev": true + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true, + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.7", @@ -2958,6 +3220,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2969,16 +3232,17 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/needle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", - "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { - "debug": "^3.2.6", "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, @@ -2989,21 +3253,12 @@ "node": ">= 4.4.x" } }, - "node_modules/needle/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "optional": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3013,6 +3268,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -3020,20 +3276,12 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -3051,6 +3299,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -3066,6 +3315,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -3081,6 +3331,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -3093,33 +3344,34 @@ "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.10" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3128,27 +3380,21 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -3161,15 +3407,16 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=6" } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "funding": [ { "type": "opencollective", @@ -3184,20 +3431,22 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3211,6 +3460,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -3220,6 +3470,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin-prettier.js" }, @@ -3235,6 +3486,7 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -3247,6 +3499,7 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/punycode": { @@ -3254,6 +3507,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3276,13 +3530,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -3291,15 +3547,17 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -3317,6 +3575,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -3326,31 +3585,18 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -3381,6 +3627,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -3390,25 +3637,29 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, + "license": "ISC", "optional": true }, "node_modules/scale-color-perceptual": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/scale-color-perceptual/-/scale-color-perceptual-1.1.2.tgz", - "integrity": "sha512-xuadE0xPnV+maBOLTl4ZcN0tGeBiU8ucRlLTIv74t/9dMEr3tiCAW+WfHePZ/JI3d452+kPvNmW6i3ty15iiIw==" + "integrity": "sha512-xuadE0xPnV+maBOLTl4ZcN0tGeBiU8ucRlLTIv74t/9dMEr3tiCAW+WfHePZ/JI3d452+kPvNmW6i3ty15iiIw==", + "license": "ISC" }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -3421,6 +3672,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -3433,15 +3685,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3451,36 +3695,27 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -3498,6 +3733,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -3510,6 +3746,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3521,6 +3758,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/tesseract-wasm/-/tesseract-wasm-0.10.0.tgz", "integrity": "sha512-xQHaeU4aGwda97Ojk+uQXVX/QP0dcE5gQggs3+wW5qLLb1ZfR3/VvUTpTo8EIdYA7YIpPfpWMqtzy4DQhzDeCA==", + "license": "BSD-2-Clause", "dependencies": { "comlink": "^4.3.1" } @@ -3529,13 +3767,24 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3548,6 +3797,7 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -3556,16 +3806,18 @@ } }, "node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", - "dev": true + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true, + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -3578,6 +3830,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -3590,6 +3843,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "devOptional": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3598,21 +3852,62 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.8.0.tgz", + "integrity": "sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.8.0", + "@typescript-eslint/parser": "8.8.0", + "@typescript-eslint/utils": "8.8.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tz-lookup-oss": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/tz-lookup-oss/-/tz-lookup-oss-6.3.0.tgz", - "integrity": "sha512-op/XCQ2e6rW2j83IaGb9jm18kVULw4enadTYoNPys23xQHA3dZ5IuLz/VdJH2XV16S6Z5u1oF9ULLuH0yINGqQ==" + "integrity": "sha512-op/XCQ2e6rW2j83IaGb9jm18kVULw4enadTYoNPys23xQHA3dZ5IuLz/VdJH2XV16S6Z5u1oF9ULLuH0yINGqQ==", + "license": "CC0-1.0" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" }, "node_modules/unplugin": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.7.1.tgz", - "integrity": "sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz", + "integrity": "sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==", "dev": true, + "license": "MIT", "dependencies": { - "acorn": "^8.11.3", - "chokidar": "^3.5.3", - "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.6.1" + "acorn": "^8.12.1", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "webpack-sources": "^3" + }, + "peerDependenciesMeta": { + "webpack-sources": { + "optional": true + } } }, "node_modules/unplugin-vue-components": { @@ -3620,6 +3915,7 @@ "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz", "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==", "dev": true, + "license": "MIT", "dependencies": { "@antfu/utils": "^0.7.6", "@rollup/pluginutils": "^5.0.4", @@ -3652,35 +3948,12 @@ } } }, - "node_modules/unplugin-vue-components/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/unplugin-vue-components/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -3689,22 +3962,29 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", + "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -3760,6 +4040,7 @@ "resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz", "integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^4.2.1", "@types/eslint": "^8.4.5", @@ -3775,6 +4056,7 @@ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", "dev": true, + "license": "MIT", "dependencies": { "estree-walker": "^2.0.1", "picomatch": "^2.2.2" @@ -3784,10 +4066,11 @@ } }, "node_modules/vite-plugin-eslint/node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -3799,38 +4082,41 @@ } }, "node_modules/vite-plugin-top-level-await": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.3.1.tgz", - "integrity": "sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.4.4.tgz", + "integrity": "sha512-QyxQbvcMkgt+kDb12m2P8Ed35Sp6nXP+l8ptGrnHV9zgYDUpraO0CPdlqLSeBqvY2DToR52nutDG7mIHuysdiw==", "dev": true, + "license": "MIT", "dependencies": { - "@rollup/plugin-virtual": "^3.0.1", - "@swc/core": "^1.3.10", - "uuid": "^9.0.0" + "@rollup/plugin-virtual": "^3.0.2", + "@swc/core": "^1.7.0", + "uuid": "^10.0.0" }, "peerDependencies": { "vite": ">=2.8" } }, "node_modules/vite-plugin-wasm": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.2.2.tgz", - "integrity": "sha512-cdbBUNR850AEoMd5nvLmnyeq63CSfoP1ctD/L2vLk/5+wsgAPlAVAzUK5nGKWO/jtehNlrSSHLteN+gFQw7VOA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.3.0.tgz", + "integrity": "sha512-tVhz6w+W9MVsOCHzxo6SSMSswCeIw4HTrXEi6qL3IRzATl83jl09JVO1djBqPSwfjgnpVHNLYcaMbaDX5WB/pg==", "dev": true, + "license": "MIT", "peerDependencies": { - "vite": "^2 || ^3 || ^4" + "vite": "^2 || ^3 || ^4 || ^5" } }, "node_modules/vue": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz", - "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==", + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.11.tgz", + "integrity": "sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg==", + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.29", - "@vue/compiler-sfc": "3.4.29", - "@vue/runtime-dom": "3.4.29", - "@vue/server-renderer": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-dom": "3.5.11", + "@vue/compiler-sfc": "3.5.11", + "@vue/runtime-dom": "3.5.11", + "@vue/server-renderer": "3.5.11", + "@vue/shared": "3.5.11" }, "peerDependencies": { "typescript": "*" @@ -3846,6 +4132,7 @@ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", @@ -3866,10 +4153,11 @@ } }, "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -3881,30 +4169,31 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/vue-eslint-parser/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=4.0" - } - }, - "node_modules/vue-multiselect": { - "version": "3.0.0-beta.2", - "resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.0.0-beta.2.tgz", - "integrity": "sha512-TFVHtI/KdWoD3Opzbkso8OIqkZlZEqFF7f2jlYx1ttgC4Jv/48IGlU5zn6cBR4p2bFDFGCHF5SkLCaadLhnBPQ==", - "engines": { - "node": ">= 4.0.0", - "npm": ">= 3.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/vue-router": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.2.tgz", - "integrity": "sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", + "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==", + "license": "MIT", "dependencies": { - "@vue/devtools-api": "^6.5.0" + "@vue/devtools-api": "^6.6.4" }, "funding": { "url": "https://github.com/sponsors/posva" @@ -3914,24 +4203,26 @@ } }, "node_modules/vue-template-compiler": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", - "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", "dev": true, + "license": "MIT", "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "node_modules/vue-tsc": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.6.5.tgz", - "integrity": "sha512-Wtw3J7CC+JM2OR56huRd5iKlvFWpvDiU+fO1+rqyu4V2nMTotShz4zbOZpW5g9fUOcjnyZYfBo5q5q+D/q27JA==", + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", "dev": true, + "license": "MIT", "dependencies": { - "@volar/vue-language-core": "1.6.5", - "@volar/vue-typescript": "1.6.5", - "semver": "^7.3.8" + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -3941,30 +4232,24 @@ } }, "node_modules/wasm-feature-detect": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.5.1.tgz", - "integrity": "sha512-GHr23qmuehNXHY4902/hJ6EV5sUANIJC3R/yMfQ7hWDg3nfhlcJfnIL96R2ohpIwa62araN6aN4bLzzzq5GXkg==" - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" }, "node_modules/webpack-virtual-modules": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", - "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", - "dev": true + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -3980,21 +4265,17 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12" } @@ -4004,6 +4285,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/browse-next/package.json b/browse-next/package.json index 9c620654c..3ff9442d7 100644 --- a/browse-next/package.json +++ b/browse-next/package.json @@ -6,20 +6,21 @@ "build": "vite build", "preview": "vite preview --port 5050", "typecheck": "vue-tsc --noEmit", - "lint": "./node_modules/.bin/eslint ./ --ext .vue,.ts --fix --ignore-path .gitignore --ignore-pattern '*.d.ts'" + "lint": "./node_modules/.bin/eslint ./ --fix --ignore-pattern \"/public/\" --ignore-pattern \"**/*.d.ts\" --ignore-pattern \"**/*.js\"" }, "dependencies": { + "@floating-ui/vue": "^1.1.4", "@fortawesome/fontawesome-svg-core": "^6.3.0", "@fortawesome/free-regular-svg-icons": "^6.3.0", "@fortawesome/free-solid-svg-icons": "^6.3.0", "@fortawesome/vue-fontawesome": "^3.0.3", "@jsquash/webp": "^1.1.3", "@popperjs/core": "^2.11.7", - "@vueform/multiselect": "^2.6.9", + "@vueform/multiselect": "^2.6.10", "@vuepic/vue-datepicker": "^4.2.0", "@vueuse/core": "^9.13.0", "bootstrap": "^5.3.2", - "bootstrap-vue-next": "^0.21.2", + "bootstrap-vue-next": "^0.24.12", "chartist": "^1.3.0", "exif-js_fixed": "^2.3.1", "hash-wasm": "^4.9.0", @@ -29,28 +30,32 @@ "suncalc": "^1.9.0", "tesseract-wasm": "^0.10.0", "tz-lookup-oss": "^6.3.0", - "vue": "^3.4.19", - "vue-multiselect": "^3.0.0-beta.1", + "vue": "^3.5.1", "vue-router": "^4.1.6" }, "devDependencies": { + "@eslint/compat": "^1.2.0", + "@eslint/js": "^9.12.0", "@rushstack/eslint-patch": "^1.2.0", + "@stylistic/eslint-plugin": "^2.9.0", "@tsconfig/node18": "^2.0.1", "@types/leaflet": "^1.9.3", "@types/luxon": "^3.2.0", "@types/node": "^18.16.16", "@types/suncalc": "^1.8.1", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^8.8.0", + "@typescript-eslint/parser": "^8.8.0", "@vitejs/plugin-vue": "^4.2.3", "@vue/eslint-config-prettier": "^7.1.0", - "@vue/eslint-config-typescript": "^12.0.0", + "@vue/eslint-config-typescript": "^14.0.0", "@vue/tsconfig": "^0.4.0", - "eslint": "^8.40.0", + "eslint": "^9.12.0", "eslint-plugin-vue": "^9.13.0", + "globals": "^15.10.0", "less": "^4.1.3", "prettier": "^2.8.8", "typescript": "5.3.3", + "typescript-eslint": "^8.8.0", "unplugin-vue-components": "^0.26.0", "vite": "^4.3.7", "vite-plugin-eslint": "^1.8.1", diff --git a/browse-next/src/App.vue b/browse-next/src/App.vue index 8b19b02d2..aac52ba38 100644 --- a/browse-next/src/App.vue +++ b/browse-next/src/App.vue @@ -287,9 +287,7 @@ onMounted(() => { data-cy="join existing project button" @click.stop.prevent="joiningNewProject.enabled = true" > - Join an existing project + Join an existing project @@ -833,5 +831,6 @@ main { padding: 10px; } - - + diff --git a/browse-next/src/api/Device.ts b/browse-next/src/api/Device.ts index 005d7bace..691c257aa 100644 --- a/browse-next/src/api/Device.ts +++ b/browse-next/src/api/Device.ts @@ -13,7 +13,6 @@ import type { ApiDeviceHistorySettings, ApiDeviceResponse, ApiMaskRegionsData, - WindowsSettings, } from "@typedefs/api/device"; import type { ScheduleId } from "@typedefs/api/common"; import type { @@ -43,16 +42,31 @@ export const deleteDevice = ( group: projectNameOrId, }) as Promise>; -export const getDeviceById = (deviceId: DeviceId) => - CacophonyApi.get(`/api/v1/devices/${deviceId}`) as Promise< - FetchResult<{ device: ApiDeviceResponse }> - >; +export const getDeviceById = ( + deviceId: DeviceId, + activeAndInactive = false +) => { + const params = new URLSearchParams(); + if (activeAndInactive) { + params.append("only-active", false.toString()); + } + return CacophonyApi.get( + `/api/v1/devices/${deviceId}${optionalQueryString(params)}` + ) as Promise>; +}; -export const getDeviceLocationAtTime = (deviceId: DeviceId, date?: Date) => { +export const getDeviceLocationAtTime = ( + deviceId: DeviceId, + activeAndInactiveDevices: boolean = false, + date?: Date +) => { const params = new URLSearchParams(); if (date) { params.append("at-time", date.toISOString()); } + if (activeAndInactiveDevices) { + params.append("only-active", false.toString()); + } return new Promise((resolve) => { ( CacophonyApi.get( @@ -71,11 +85,21 @@ export const getDeviceLocationAtTime = (deviceId: DeviceId, date?: Date) => { export interface EventApiParams { limit?: number; offset?: number; - type?: DeviceEventType | DeviceEventType[]; + type?: DeviceEventType | DeviceEventType[] | string | string[]; endTime?: IsoFormattedString; // Or in the format YYYY-MM-DD hh:mm:ss startTime?: IsoFormattedString; } +export const getKnownEventTypes = () => + CacophonyApi.get(`/api/v1/events/event-types`) as Promise< + FetchResult<{ eventTypes: string[] }> + >; + +export const getKnownEventTypesForDeviceInLastMonth = (deviceId: DeviceId) => + CacophonyApi.get( + `/api/v1/events/event-types/for-device/${deviceId}` + ) as Promise>; + export const getLatestEventsByDeviceId = ( deviceId: number, eventParams?: EventApiParams @@ -87,7 +111,13 @@ export const getLatestEventsByDeviceId = ( params.append("include-count", false.toString()); if (eventParams) { for (const [key, val] of Object.entries(eventParams)) { - params.append(key, val.toString()); + if (Array.isArray(val)) { + for (const item of val) { + params.append(key, item.toString()); + } + } else { + params.append(key, val.toString()); + } } } return CacophonyApi.get(`/api/v1/events?${params}`) as Promise< @@ -199,6 +229,10 @@ export const getBatteryInfo = ( untilDateTime = new Date(events[events.length - 1].dateTime); } } else { + if (!response) { + // We aborted the request + resolve(null); + } stillHasEvents = false; } } @@ -207,7 +241,7 @@ export const getBatteryInfo = ( } else { resolve(false); } - }) as Promise; + }) as Promise; }; export const getEarliestEventAfterTime = ( @@ -241,6 +275,17 @@ export const getDeviceVersionInfo = (deviceId: DeviceId) => { }) as Promise | false>; }; +export const getDeviceLatestVersionInfo = async () => { + return unwrapLoadedResource( + CacophonyApi.get(`/api/v1/devices/latest-software-versions`) as Promise< + FetchResult<{ + versions: Record>>; + }> + >, + "versions" + ) as Promise>>>; +}; + export const getLocationHistory = ( deviceId: DeviceId ): Promise< @@ -293,23 +338,22 @@ export const getLatestStatusRecordingForDevice = ( ) => { return new Promise((resolve) => { const params = new URLSearchParams(); - params.append("limit", "1"); - params.append("type", "thermalRaw"); - params.append("countAll", false.toString()); - const where = { - duration: use2SecondRecordings ? { $gte: 2, $lte: 3 } : { $gte: 2 }, - GroupId: projectId, - DeviceId: deviceId, - }; - params.append("where", JSON.stringify(where)); + params.append("max-results", "1"); + params.append("types", "thermal"); + params.append("include-false-positives", true.toString()); + if (use2SecondRecordings) { + params.append("status-recordings", true.toString()); + } ( - CacophonyApi.get(`/api/v1/recordings?${params}`) as Promise< - FetchResult<{ rows: ApiRecordingResponse[] }> - > + CacophonyApi.get( + `/api/v1/recordings/for-project/${projectId}/${optionalQueryString( + params + )}` + ) as Promise> ).then((response) => { if (response.success) { - if (response.result.rows.length) { - resolve(response.result.rows[0]); + if (response.result.recordings.length) { + resolve(response.result.recordings[0]); } else { if (use2SecondRecordings) { // 2 Second recording may not be available, get the latest regular recording: @@ -481,14 +525,22 @@ export const updateReferenceImageForDeviceAtCurrentLocation = ( export const getReferenceImageForDeviceAtCurrentLocation = ( deviceId: DeviceId ) => { + const params = new URLSearchParams(); return CacophonyApi.get( - `/api/v1/devices/${deviceId}/reference-image` + `/api/v1/devices/${deviceId}/reference-image${optionalQueryString(params)}` ) as Promise>; }; -export const getMaskRegionsForDevice = (deviceId: DeviceId, atTime?: Date) => { +export const getMaskRegionsForDevice = ( + deviceId: DeviceId, + activeAndInactive = true, + atTime?: Date +) => { const params = new URLSearchParams(); params.append("at-time", (atTime || new Date()).toISOString()); + if (!activeAndInactive) { + params.append("only-active", true.toString()); + } const queryString = params.toString(); return CacophonyApi.get( @@ -538,24 +590,34 @@ export const updateMaskRegionsForDevice = ( export const getReferenceImageForDeviceAtTime = ( deviceId: DeviceId, - atTime: Date + atTime: Date, + activeAndInactive: boolean = false ) => { const params = new URLSearchParams(); params.append("at-time", atTime.toISOString()); + if (!activeAndInactive) { + params.append("only-active", true.toString()); + } return CacophonyApi.get( - `/api/v1/devices/${deviceId}/reference-image?${params}` + `/api/v1/devices/${deviceId}/reference-image?${optionalQueryString(params)}` ) as Promise>; }; export const hasReferenceImageForDeviceAtTime = ( deviceId: DeviceId, - atTime: Date + atTime: Date, + activeAndInactive: boolean = false ) => { const params = new URLSearchParams(); params.append("at-time", atTime.toISOString()); + if (!activeAndInactive) { + params.append("only-active", true.toString()); + } // Set the reference image for the location start time? Or create a new entry for this reference image starting now? return CacophonyApi.get( - `/api/v1/devices/${deviceId}/reference-image/exists?${params}` + `/api/v1/devices/${deviceId}/reference-image/exists?${optionalQueryString( + params + )}` ) as Promise< FetchResult<{ fromDateTime: IsoFormattedDateString; @@ -565,11 +627,18 @@ export const hasReferenceImageForDeviceAtTime = ( }; export const hasReferenceImageForDeviceAtCurrentLocation = ( - deviceId: DeviceId + deviceId: DeviceId, + activeAndInactive: boolean = false ) => { + const params = new URLSearchParams(); + if (!activeAndInactive) { + params.append("only-active", true.toString()); + } // Set the reference image for the location start time? Or create a new entry for this reference image starting now? return CacophonyApi.get( - `/api/v1/devices/${deviceId}/reference-image/exists` + `/api/v1/devices/${deviceId}/reference-image/exists${optionalQueryString( + params + )}` ) as Promise< FetchResult<{ fromDateTime: IsoFormattedDateString; @@ -580,12 +649,14 @@ export const hasReferenceImageForDeviceAtCurrentLocation = ( export const getLastKnownDeviceBatteryLevel = ( deviceId: DeviceId -): Promise => { +): Promise => { const last25Hours = new Date(); last25Hours.setHours(last25Hours.getHours() - 25); return new Promise((resolve) => { getBatteryInfo(deviceId, last25Hours, 1, 1).then((result) => { - if (result === false || result.length === 0) { + if (result === null) { + resolve(null); + } else if (result === false || result.length === 0) { resolve(false); } resolve((result as BatteryInfoEvent[])[0]); diff --git a/browse-next/src/api/Location.ts b/browse-next/src/api/Location.ts index 113462144..af5f42ea8 100644 --- a/browse-next/src/api/Location.ts +++ b/browse-next/src/api/Location.ts @@ -49,3 +49,11 @@ export const createNewLocationForProject = async ( }); }); }; + +export const changeLocationName = (newName: string, locationId: LocationId) => { + return CacophonyApi.patch(`/api/v1/stations/${locationId}`, { + "station-updates": { + name: newName, + }, + }) as Promise>; +}; diff --git a/browse-next/src/api/api.ts b/browse-next/src/api/api.ts index 874682db3..945c239f8 100644 --- a/browse-next/src/api/api.ts +++ b/browse-next/src/api/api.ts @@ -17,6 +17,7 @@ const fetchJsonWithMethod = async ( headers: { "Content-Type": "application/json; charset=utf-8", }, + // eslint-disable-next-line no-undef } as RequestInit; if (body) { payload.body = JSON.stringify(body); diff --git a/browse-next/src/api/fetch.ts b/browse-next/src/api/fetch.ts index eaea9312f..a0b3f0a9f 100644 --- a/browse-next/src/api/fetch.ts +++ b/browse-next/src/api/fetch.ts @@ -197,6 +197,7 @@ export const networkConnectionError = reactive({ */ export async function fetch( url: string, + // eslint-disable-next-line no-undef request: RequestInit = {}, abortable = true ): Promise | void> { diff --git a/browse-next/src/components/ActivitySearchDescription.vue b/browse-next/src/components/ActivitySearchDescription.vue index ccd670371..33e801d65 100644 --- a/browse-next/src/components/ActivitySearchDescription.vue +++ b/browse-next/src/components/ActivitySearchDescription.vue @@ -126,7 +126,9 @@ const otherLabels = computed( ? upperFirst(searchParams.displayMode) : ` ${searchParams.displayMode}` }} - + at {{ diff --git a/browse-next/src/components/ActivitySearchParameters.vue b/browse-next/src/components/ActivitySearchParameters.vue index d9b1d2e33..667075e66 100644 --- a/browse-next/src/components/ActivitySearchParameters.vue +++ b/browse-next/src/components/ActivitySearchParameters.vue @@ -123,10 +123,12 @@ const lastTwentyFourHours: [Date, Date] = [oneDayAgo, now]; const dateRangePicker = ref(); // Initialise this to a zero range -const selectedDateRange = ref<[Date, Date] | "custom">([now, now]); +interface DateRangeOption { label: string; value: [Date, Date] | "custom", urlLabel: string }; + +const selectedDateRange = ref({ value: [now, now], label: "Now", urlLabel: "now" }); const customDateRange = ref<[Date, Date] | null>(null); const combinedDateRange = computed<[Date, Date]>(() => { - if (selectedDateRange.value === "custom") { + if (selectedDateRange.value.value === "custom") { if (customDateRange.value !== null) { // FIXME: Timezones for project when you're viewing from outside the projects timezone // Make custom range be from beginning of start date til end of end date. @@ -158,7 +160,7 @@ const combinedDateRange = computed<[Date, Date]>(() => { } return [new Date(), new Date()]; } else { - return selectedDateRange.value as [Date, Date]; + return selectedDateRange.value.value as [Date, Date]; } }); @@ -259,11 +261,11 @@ const maxDateForSelectedLocations = computed(() => { return latest; }); -const maybeSelectDatePicker = (value: [Date, Date] | string) => { - if (value === "custom" && !props.customSet) { +const maybeSelectDatePicker = (value: { label: string; value: [Date, Date] | string, urlLabel: string }) => { + if (value.value === "custom" && !props.customSet) { nextTick(() => { if (dateRangePicker.value) { - (dateRangePicker.value as DatePickerMethods).openMenu(); + dateRangePicker.value.openMenu(); } }); } else if (props.customSet) { @@ -310,7 +312,7 @@ watch( combinedDateRange.value[1] > maxDateForProject.value ) { console.warn("Should adjust range"); - selectedDateRange.value = commonDateRanges.value[0].value; + selectedDateRange.value = commonDateRanges.value[0]; customDateRange.value = null; } } @@ -747,13 +749,13 @@ const syncParams = ( ); } if (foundRange) { - selectedDateRange.value = foundRange.value; + selectedDateRange.value = foundRange; } else if (next.from && !next.until) { // Try to match to the common date ranges and pick an option. - selectedDateRange.value = commonDateRanges.value[0].value; + selectedDateRange.value = commonDateRanges.value[0]; updateDateRouteComponent(combinedDateRange.value, [now, now]); } else if (next.from && next.until) { - selectedDateRange.value = "custom"; + selectedDateRange.value = commonDateRanges.value[commonDateRanges.value.length - 1]; // Validate the custom range being passed, constrain it to the min/max const areValidDates = queryValueIsDate(next.from) && queryValueIsDate(next.until); @@ -778,7 +780,7 @@ const syncParams = ( updateDateRouteComponent(combinedDateRange.value, [now, now]); } } else { - selectedDateRange.value = commonDateRanges.value[0].value; + selectedDateRange.value = commonDateRanges.value[0]; } } }; @@ -967,8 +969,9 @@ const scrolledToStickyPosition = computed(() => { (() => { /> (() => { border-color: var(--bs-secondary); } - - + + diff --git a/browse-next/src/components/CptvSingleFrame.vue b/browse-next/src/components/CptvSingleFrame.vue index 0e226a0dd..6895223c8 100644 --- a/browse-next/src/components/CptvSingleFrame.vue +++ b/browse-next/src/components/CptvSingleFrame.vue @@ -92,27 +92,35 @@ const loadRecording = async () => { creds.value.apiToken ); if (result === true) { - let frame = await cptvDecoder.getNextFrame(); - if (frame?.meta.isBackgroundFrame) { + let gotGoodFrame = false; + let frame; + + while (!gotGoodFrame) { frame = await cptvDecoder.getNextFrame(); - } - if (frame) { - let max = Number.MIN_SAFE_INTEGER; - let min = Number.MAX_SAFE_INTEGER; - for (const px of frame.data) { - max = Math.max(px, max); - min = Math.min(px, min); + if (frame?.isBackgroundFrame) { + continue; + } + if (frame) { + let max = Number.MIN_SAFE_INTEGER; + let min = Number.MAX_SAFE_INTEGER; + for (const px of frame.imageData) { + max = Math.max(px, max); + min = Math.min(px, min); + } + if (max - min > 0) { + gotGoodFrame = true; + } + const buffer = new Uint8ClampedArray(160 * 120 * 4); + renderFrameIntoFrameBuffer( + buffer, + frame.imageData, + defaultPalette.value[1], + min, + max + ); + frameData.value = new ImageData(buffer, 160, 120); + renderFrame(); } - const buffer = new Uint8ClampedArray(160 * 120 * 4); - renderFrameIntoFrameBuffer( - buffer, - frame.data, - defaultPalette.value[1], - min, - max - ); - frameData.value = new ImageData(buffer, 160, 120); - renderFrame(); } } await cptvDecoder.close(); diff --git a/browse-next/src/components/DeviceBatteryLevel.vue b/browse-next/src/components/DeviceBatteryLevel.vue index d1c2cb9aa..425dec9a5 100644 --- a/browse-next/src/components/DeviceBatteryLevel.vue +++ b/browse-next/src/components/DeviceBatteryLevel.vue @@ -45,17 +45,29 @@ const loadInfo = async () => { // batteryType: "lime", // battery: Math.round(Math.random() * 100), // }; - (window as unknown as BatteryInfoMapContainer).deviceBatteryInfoMap[ - `__${props.device.id}` - ] = batteryLevelInfo.value; + if (batteryLevelInfo.value !== null) { + (window as unknown as BatteryInfoMapContainer).deviceBatteryInfoMap[ + `__${props.device.id}` + ] = batteryLevelInfo.value; + } } }; -onBeforeMount(loadInfo); +onBeforeMount(() => { + if (props.device.active && props.device.isHealthy) { + loadInfo(); + } else { + batteryLevelInfo.value = false; + } +}); watch( () => props.device.id, (next, prev) => { if (next !== prev) { - loadInfo(); + if (props.device.active && props.device.isHealthy) { + loadInfo(); + } else { + batteryLevelInfo.value = false; + } } } ); diff --git a/browse-next/src/components/DeviceRecordingSetup.vue b/browse-next/src/components/DeviceRecordingSetup.vue index 941258bef..6006131ed 100644 --- a/browse-next/src/components/DeviceRecordingSetup.vue +++ b/browse-next/src/components/DeviceRecordingSetup.vue @@ -16,6 +16,7 @@ import { import Datepicker from "@vuepic/vue-datepicker"; import { projectDevicesLoaded } from "@models/LoggedInUser.ts"; import { resourceIsLoading } from "@/helpers/utils.ts"; +import { DeviceType } from "@typedefs/api/consts.ts"; type Time = { hours: number; minutes: number; seconds: number }; const devices = inject(selectedProjectDevices) as Ref< ApiDeviceResponse[] | null @@ -321,8 +322,7 @@ watch(customRecordingWindowStop, async () => {
@@ -466,4 +466,6 @@ watch(customRecordingWindowStop, async () => { max-width: 640px; } - + diff --git a/browse-next/src/components/ImageUpload.worker.ts b/browse-next/src/components/ImageUpload.worker.ts index d46861888..e2fec26af 100644 --- a/browse-next/src/components/ImageUpload.worker.ts +++ b/browse-next/src/components/ImageUpload.worker.ts @@ -15,8 +15,9 @@ import * as webpEncoder from "@jsquash/webp/codec/enc/webp_enc"; import { defaultOptions } from "@jsquash/webp/meta"; import { initEmscriptenModule } from "@jsquash/webp/utils"; -let emscriptenModule: never; -async function initWebpEncode(module: never) { +import type { WebPModule } from "../../public/webp-enc/webp_enc"; +let emscriptenModule: Promise; +async function initWebpEncode(module: WebAssembly.Module) { if (supportsFastBuild()) { emscriptenModule = initEmscriptenModule(webpEncoderFast.default, module); return emscriptenModule; diff --git a/browse-next/src/components/InlineViewModal.vue b/browse-next/src/components/InlineViewModal.vue index 3fcf1d851..7bd0ea5f9 100644 --- a/browse-next/src/components/InlineViewModal.vue +++ b/browse-next/src/components/InlineViewModal.vue @@ -67,7 +67,7 @@ const onShown = () => { emit("shown"); }; -// eslint-disable-next-line @typescript-eslint/no-empty-function + const updatedRecording = (recordingId: RecordingId, action: string) => {}; const isBusy = ref(false); diff --git a/browse-next/src/components/LocationsOverviewTable.vue b/browse-next/src/components/LocationsOverviewTable.vue index 01e57e2a1..988213892 100644 --- a/browse-next/src/components/LocationsOverviewTable.vue +++ b/browse-next/src/components/LocationsOverviewTable.vue @@ -1,3 +1,85 @@ + - + diff --git a/browse-next/src/vue-multiselect.d.ts b/browse-next/src/vue-multiselect.d.ts deleted file mode 100644 index bce282bad..000000000 --- a/browse-next/src/vue-multiselect.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "vue-multiselect"; diff --git a/browse/package.json b/browse/package.json index b35d7cd03..972bda28e 100644 --- a/browse/package.json +++ b/browse/package.json @@ -24,7 +24,7 @@ "chart.js": "^3.6.2", "chroma-js": "^2.4.2", "colormap": "^2.3.2", - "cptv-player-vue": "github:TheCacophonyProject/cptv-player-vue#v1.4.1", + "cptv-player-vue": "github:TheCacophonyProject/cptv-player-vue#v1.5.2", "cross-fetch": "^3.0.6", "csvtojson": "^2.0.10", "highcharts": "^9.1.1", diff --git a/browse/src/api/Device.api.ts b/browse/src/api/Device.api.ts index 88792ef66..4dfc34043 100644 --- a/browse/src/api/Device.api.ts +++ b/browse/src/api/Device.api.ts @@ -263,12 +263,25 @@ async function getType( return "UnknownDeviceType"; } -async function getDeviceSettings( - deviceId: DeviceId -): Promise< - FetchResult<{ settings: ApiDeviceHistorySettings; location: LatLng }> -> { - return CacophonyApi.get(`/api/v1/devices/${deviceId}/settings`); +function getSettingsForDevice( + deviceId: DeviceId, + atTime?: Date, + lastSynced = false +) { + const params = new URLSearchParams(); + params.append("at-time", (atTime || new Date()).toISOString()); + if (lastSynced) { + params.append("latest-synced", true.toString()); + } + const queryString = params.toString(); + return CacophonyApi.get( + `/api/v1/devices/${deviceId}/settings?${queryString}` + ) as Promise< + FetchResult<{ + settings: ApiDeviceHistorySettings | null; + location: LatLng; + }> + >; } async function updateDeviceSettings( @@ -280,10 +293,8 @@ async function updateDeviceSettings( }); } -async function toggleUseLowPowerMode( - deviceId: DeviceId -): Promise> { - const currentSettingsResponse = await getDeviceSettings(deviceId); +async function toggleUseLowPowerMode(deviceId: DeviceId) { + const currentSettingsResponse = await getSettingsForDevice(deviceId); if (currentSettingsResponse.success) { const currentSettings = currentSettingsResponse.result.settings; const newSettings: ApiDeviceHistorySettings = { @@ -300,10 +311,8 @@ async function toggleUseLowPowerMode( } } -async function setDefaultRecordingWindows( - deviceId: DeviceId -): Promise> { - const currentSettingsResponse = await getDeviceSettings(deviceId); +async function setDefaultRecordingWindows(deviceId: DeviceId) { + const currentSettingsResponse = await getSettingsForDevice(deviceId); if (currentSettingsResponse.success) { const currentSettings = currentSettingsResponse.result.settings; const newSettings: ApiDeviceHistorySettings = { @@ -322,10 +331,8 @@ async function setDefaultRecordingWindows( } } -async function set24HourRecordingWindows( - deviceId: DeviceId -): Promise> { - const currentSettingsResponse = await getDeviceSettings(deviceId); +async function set24HourRecordingWindows(deviceId: DeviceId) { + const currentSettingsResponse = await getSettingsForDevice(deviceId); if (currentSettingsResponse.success) { const currentSettings = currentSettingsResponse.result.settings; const newSettings: ApiDeviceHistorySettings = { @@ -347,8 +354,8 @@ async function set24HourRecordingWindows( async function setCustomRecordingWindows( deviceId: DeviceId, customSettings: Omit -): Promise> { - const currentSettingsResponse = await getDeviceSettings(deviceId); +) { + const currentSettingsResponse = await getSettingsForDevice(deviceId); if (currentSettingsResponse.success) { const currentSettings = currentSettingsResponse.result.settings; const newSettings: ApiDeviceHistorySettings = { @@ -363,6 +370,43 @@ async function setCustomRecordingWindows( throw new Error("Failed to fetch current settings."); } } + +const getLatestEventsByDeviceId = ( + deviceId: number, + eventParams?: EventApiParams +) => { + const params = new URLSearchParams(); + params.append("deviceId", deviceId.toString()); + params.append("latest", true.toString()); + params.append("only-active", false.toString()); + params.append("include-count", false.toString()); + if (eventParams) { + for (const [key, val] of Object.entries(eventParams)) { + params.append(key, val.toString()); + } + } + return CacophonyApi.get(`/api/v1/events?${params}`) as Promise< + FetchResult<{ rows: DeviceEvent[] }> + >; +}; + +const getDeviceNodeGroup = (deviceId: DeviceId) => { + return new Promise((resolve) => { + getLatestEventsByDeviceId(deviceId, { + type: "salt-update", + limit: 1, + }).then((response) => { + if (response.success && response.result.rows.length) { + resolve( + response.result.rows[0].EventDetail.details.nodegroup || + "unknown channel" + ); + } else { + resolve(false); + } + }); + }) as Promise; +}; export default { getDevices, getDevice, @@ -379,9 +423,11 @@ export default { getDeviceSpeciesCountBulk, getDeviceDaysActive, updateDeviceSettings, - getDeviceSettings, + getSettingsForDevice, toggleUseLowPowerMode, setDefaultRecordingWindows, set24HourRecordingWindows, setCustomRecordingWindows, + getDeviceNodeGroup, + getLatestEventsByDeviceId, }; diff --git a/browse/src/components/Devices/DeviceInfo.vue b/browse/src/components/Devices/DeviceInfo.vue index 1575e5bf1..9d00e0422 100644 --- a/browse/src/components/Devices/DeviceInfo.vue +++ b/browse/src/components/Devices/DeviceInfo.vue @@ -1,132 +1,170 @@ @@ -138,9 +176,13 @@ import { ref, onMounted, computed, + watch, } from "@vue/composition-api"; import DeviceApi from "@/api/Device.api"; -import { WindowsSettings } from "@typedefs/api/device"; +import { + ApiDeviceHistorySettings, + WindowsSettings, +} from "@typedefs/api/device"; export default defineComponent({ name: "DeviceDetail", @@ -155,7 +197,7 @@ export default defineComponent({ }, }, setup(props) { - const settings = ref(null); + const settings = ref(null); const fields = [ { key: "name", label: "Setting" }, { key: "value", label: "Value" }, @@ -163,16 +205,10 @@ export default defineComponent({ ]; const showCustomModal = ref(false); - const customSettings = ref({ - powerOn: null, - powerOff: null, - startRecording: null, - stopRecording: null, - }); - const fetchSettings = async () => { try { - const response = await DeviceApi.getDeviceSettings(props.deviceId); + const response = await DeviceApi.getSettingsForDevice(props.deviceId); + debugger; if (response.success) { settings.value = response.result.settings; } @@ -232,44 +268,6 @@ export default defineComponent({ } }); - const enableCustomRecordingWindows = () => { - const windows = settings.value.windows; - customSettings.value = { - powerOn: windows.powerOn, - powerOff: windows.powerOff, - startRecording: windows.startRecording, - stopRecording: windows.stopRecording, - }; - showCustomModal.value = true; - }; - - const handleOk = (bvModalEvent) => { - bvModalEvent.preventDefault(); - saveCustomRecordingWindows(bvModalEvent); - }; - - const handleCancel = (bvModalEvent) => { - bvModalEvent.preventDefault(); - cancelCustomRecordingWindows(bvModalEvent); - }; - - const saveCustomRecordingWindows = async (event) => { - event.preventDefault(); - const response = await DeviceApi.setCustomRecordingWindows( - props.deviceId, - customSettings.value - ); - if (response.success) { - settings.value = response.result.settings; - } - showCustomModal.value = false; - }; - - const cancelCustomRecordingWindows = (event) => { - event.preventDefault(); - showCustomModal.value = false; - }; - const formatTime = (timeString) => { if (timeString[0] === "+" || timeString[0] === "-") { return timeString; @@ -294,6 +292,21 @@ export default defineComponent({ } }; + const useLowPowerMode = computed({ + get: () => { + return ( + (settings.value as ApiDeviceHistorySettings)?.thermalRecording + ?.useLowPowerMode ?? false + ); + }, + set: async (val: boolean) => { + (settings.value as ApiDeviceHistorySettings).thermalRecording = { + useLowPowerMode: val, + updated: new Date().toISOString(), + }; + await DeviceApi.updateDeviceSettings(props.deviceId, settings.value); + }, + }); const settingsTable = computed(() => { const rows = [ { @@ -317,25 +330,201 @@ export default defineComponent({ return rows; }); + const saltNodeGroup = ref(null); + const isTc2Device = computed(() => { + return (saltNodeGroup.value || "").includes("tc2"); + }); + const defaultWindows = { + powerOn: "-30m", + powerOff: "+30m", + startRecording: "-30m", + stopRecording: "+30m", + }; + const recordingWindowSetting = computed<"default" | "always" | "custom">({ + get: () => { + const s = settings.value as ApiDeviceHistorySettings; + if ( + s && + s.windows && + s.windows.startRecording && + s.windows.stopRecording + ) { + const start = s.windows.startRecording; + const stop = s.windows.stopRecording; + if ( + (start.startsWith("+") || start.startsWith("-")) && + (stop.startsWith("+") || stop.startsWith("-")) + ) { + return "default"; + } else if (start === stop) { + return "always"; + } else { + return "custom"; + } + } else { + return "default"; + } + }, + set: async (val: "default" | "always" | "custom") => { + if (settings.value) { + if (val === "default" && settings.value) { + settings.value.windows = { + ...defaultWindows, + updated: new Date().toISOString(), + }; + } else if (val === "always") { + settings.value.windows = { + ...(!isTc2Device.value + ? { + powerOn: "12:00", + powerOff: "12:00", + } + : {}), + startRecording: "12:00", + stopRecording: "12:00", + updated: new Date().toISOString(), + }; + } else { + settings.value.windows = { + ...(!isTc2Device.value + ? { + powerOn: "09:00", + powerOff: "17:00", + } + : {}), + startRecording: "09:00", + stopRecording: "17:00", + updated: new Date().toISOString(), + }; + } + + await DeviceApi.updateDeviceSettings(props.deviceId, settings.value); + } + }, + }); + + const customPowerWindowStart = computed({ + get: () => { + if (settings.value) { + return ( + (settings.value as ApiDeviceHistorySettings).windows + ?.startRecording || "" + ); + } else { + return "12:00"; + } + }, + set: async (val: string) => { + if (settings.value) { + settings.value.windows = settings.value.windows || { + ...defaultWindows, + updated: new Date().toISOString(), + }; + settings.value.windows.powerOn = val; + settings.value.windows.updated = new Date().toISOString(); + await DeviceApi.updateDeviceSettings(props.deviceId, settings.value); + } + }, + }); + + const customPowerWindowStop = computed({ + get: () => { + if (settings.value) { + return ( + (settings.value as ApiDeviceHistorySettings).windows + ?.stopRecording || "" + ); + } else { + return "12:00"; + } + }, + set: async (val: string) => { + if (settings.value) { + settings.value.windows = settings.value.windows || { + ...defaultWindows, + updated: new Date().toISOString(), + }; + settings.value.windows.powerOff = val; + settings.value.windows.updated = new Date().toISOString(); + await DeviceApi.updateDeviceSettings(props.deviceId, settings.value); + } + }, + }); + + const customRecordingWindowStart = computed({ + get: () => { + if (settings.value) { + return ( + (settings.value as ApiDeviceHistorySettings).windows + ?.startRecording || "" + ); + } else { + return "12:00"; + } + }, + set: async (val: string) => { + if (settings.value) { + settings.value.windows = settings.value.windows || { + ...defaultWindows, + updated: new Date().toISOString(), + }; + settings.value.windows.startRecording = val; + settings.value.windows.updated = new Date().toISOString(); + await DeviceApi.updateDeviceSettings(props.deviceId, settings.value); + } + }, + }); + + const customRecordingWindowStop = computed({ + get: () => { + if (settings.value) { + return ( + (settings.value as ApiDeviceHistorySettings).windows + ?.stopRecording || "" + ); + } else { + return "12:00"; + } + }, + set: async (val: string) => { + if (settings.value) { + settings.value.windows = settings.value.windows || { + ...defaultWindows, + updated: new Date().toISOString(), + }; + settings.value.windows.stopRecording = val; + settings.value.windows.updated = new Date().toISOString(); + await DeviceApi.updateDeviceSettings(props.deviceId, settings.value); + } + }, + }); + const initialized = ref(false); + onMounted(async () => { await fetchSettings(); + initialized.value = true; + const nodeGroupRes = await DeviceApi.getDeviceNodeGroup(props.deviceId); + if (nodeGroupRes) { + saltNodeGroup.value = nodeGroupRes; + } }); return { + isTc2Device, settings, currentWindowsType, fields, showCustomModal, - customSettings, + useLowPowerMode, toggleUseLowPowerMode, setDefaultRecordingWindows, set24HourRecordingWindows, - enableCustomRecordingWindows, - saveCustomRecordingWindows, - cancelCustomRecordingWindows, - handleOk, - handleCancel, settingsTable, + recordingWindowSetting, + customRecordingWindowStart, + customRecordingWindowStop, + customPowerWindowStart, + customPowerWindowStop, }; }, computed: mapState({ @@ -361,7 +550,7 @@ export default defineComponent({ padding-top: 1em; } -.versions p { +.versions > p { padding-top: 1em; } diff --git a/integration-tests/.eslintrc.js b/integration-tests/.eslintrc.js index cbb24c5a8..11630dd5e 100644 --- a/integration-tests/.eslintrc.js +++ b/integration-tests/.eslintrc.js @@ -33,4 +33,12 @@ module.exports = { "no-only-tests/no-only-tests": ["warn"], "cypress/no-assigning-return-values": ["off"], }, + overrides: [ + { + files: ["./**/*.d.ts"], + rules: { + "no-undef": "off", + }, + }, + ], }; diff --git a/integration-tests/cypress-api-batch-1.config.ts b/integration-tests/cypress-api-batch-1.config.ts new file mode 100644 index 000000000..775629b0f --- /dev/null +++ b/integration-tests/cypress-api-batch-1.config.ts @@ -0,0 +1,38 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + projectId: "dyez6t", + + env: { + "cacophony-api-server": "http://localhost:1080", + "cacophony-processing-api-server": "http://localhost:2008", + running_in_a_dev_environment: true, + "base-url-returned-in-links": "http://test.site", + testCreds: { + superuser: { + name: "admin_test", + password: "admin_test", + email: "admin@email.com", + }, + }, + }, + + chromeWebSecurity: false, + screenshotOnRunFailure: false, + + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require("./cypress/plugins/index.js")(on, config); + }, + specPattern: "cypress/e2e/api/batch-1/**/*.{js,jsx,ts,tsx}", + }, + + component: { + devServer: { + framework: "vue-cli", + bundler: "webpack", + }, + }, +}); diff --git a/integration-tests/cypress-api-batch-2.config.ts b/integration-tests/cypress-api-batch-2.config.ts new file mode 100644 index 000000000..32b9f3938 --- /dev/null +++ b/integration-tests/cypress-api-batch-2.config.ts @@ -0,0 +1,38 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + projectId: "dyez6t", + + env: { + "cacophony-api-server": "http://localhost:1080", + "cacophony-processing-api-server": "http://localhost:2008", + running_in_a_dev_environment: true, + "base-url-returned-in-links": "http://test.site", + testCreds: { + superuser: { + name: "admin_test", + password: "admin_test", + email: "admin@email.com", + }, + }, + }, + + chromeWebSecurity: false, + screenshotOnRunFailure: false, + + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require("./cypress/plugins/index.js")(on, config); + }, + specPattern: "cypress/e2e/api/batch-2/**/*.{js,jsx,ts,tsx}", + }, + + component: { + devServer: { + framework: "vue-cli", + bundler: "webpack", + }, + }, +}); diff --git a/integration-tests/cypress-performance.json.TEMPLATE b/integration-tests/cypress-performance.json.TEMPLATE deleted file mode 100644 index a04d8b08d..000000000 --- a/integration-tests/cypress-performance.json.TEMPLATE +++ /dev/null @@ -1,9 +0,0 @@ -{ - "projectId": "dyez6t", - "integrationFolder": "cypress/performance", - "env" : { - "cacophony-api-server": "http://localhost:1080", - "testCreds" : {} - }, - screenshotOnRunFailure: false, -} diff --git a/integration-tests/cypress/browse/users/group.ts b/integration-tests/cypress/browse/users/group.ts index 222236b84..c19e7007e 100644 --- a/integration-tests/cypress/browse/users/group.ts +++ b/integration-tests/cypress/browse/users/group.ts @@ -1,5 +1,4 @@ -/// -import { getTestName } from "../../commands/names"; +import { getTestName } from "@commands/names"; describe("Group Admin Pages", () => { const Anna = "Anna"; diff --git a/integration-tests/cypress/browse/users/new_user_and_camera.ts b/integration-tests/cypress/browse/users/new_user_and_camera.ts index e6403259b..fc84235ac 100644 --- a/integration-tests/cypress/browse/users/new_user_and_camera.ts +++ b/integration-tests/cypress/browse/users/new_user_and_camera.ts @@ -1,4 +1,4 @@ -/// +import { RecordingType } from "@typedefs/api/consts"; context("Users can see footage from their cameras", () => { const username = "integration"; @@ -29,7 +29,7 @@ context("Users can see footage from their cameras", () => { it("A camera can trigger and upload a new recording", () => { cy.apiSignInAs(username); - cy.apiRecordingAdd(camera, {}); + cy.apiRecordingAdd(camera, { type: RecordingType.ThermalRaw }); // for video to be uploaded cy.wait(3 * 1000); cy.testCheckDeviceHasRecordings(username, camera, 1); diff --git a/integration-tests/cypress/browse/users/registeruser.ts b/integration-tests/cypress/browse/users/registeruser.ts index 82c8d4c17..63d39789a 100644 --- a/integration-tests/cypress/browse/users/registeruser.ts +++ b/integration-tests/cypress/browse/users/registeruser.ts @@ -1,5 +1,3 @@ -/// - context("Register as new user", () => { const username = "new"; diff --git a/integration-tests/cypress/commands/api/alerts.d.ts b/integration-tests/cypress/commands/api/alerts.d.ts index 4ea08b683..82ea5d025 100644 --- a/integration-tests/cypress/commands/api/alerts.d.ts +++ b/integration-tests/cypress/commands/api/alerts.d.ts @@ -1,10 +1,4 @@ -/// - declare namespace Cypress { - type ApiAlertCondition = import("@typedefs/api/alerts").ApiAlertCondition; - type AlertId = import("@typedefs/api/common").AlertId; - type ApiAlertResponse = import("@typedefs/api/alerts").ApiAlertResponse; - type StationIdAlias2 = import("@typedefs/api/common").StationId; interface Chainable { /** * Create an alert for a device. Optioanlly expect to fail with code: failCode @@ -49,7 +43,7 @@ declare namespace Cypress { */ apiStationAlertCheck( userName: string, - stationId: StationIdAlias2, + stationId: StationId, expectedAlert: any, statusCode?: number ): Cypress.Chainable; diff --git a/integration-tests/cypress/commands/api/alerts.ts b/integration-tests/cypress/commands/api/alerts.ts index 6a2ba63fb..e915655d5 100644 --- a/integration-tests/cypress/commands/api/alerts.ts +++ b/integration-tests/cypress/commands/api/alerts.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { v1ApiPath, getCreds, diff --git a/integration-tests/cypress/commands/api/authenticate.d.ts b/integration-tests/cypress/commands/api/authenticate.d.ts index bdd635c84..5b9a4ae28 100644 --- a/integration-tests/cypress/commands/api/authenticate.d.ts +++ b/integration-tests/cypress/commands/api/authenticate.d.ts @@ -1,8 +1,4 @@ -// load the global Cypress types -/// - declare namespace Cypress { - type ApiAuthenticateAccess = import("../types").ApiAuthenticateAccess; interface Chainable { /** * Sign is as user using supplied username and session-unique suffix. diff --git a/integration-tests/cypress/commands/api/authenticate.ts b/integration-tests/cypress/commands/api/authenticate.ts index 995818023..8e35f7135 100644 --- a/integration-tests/cypress/commands/api/authenticate.ts +++ b/integration-tests/cypress/commands/api/authenticate.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { ApiAuthenticateAccess } from "@commands/types"; import { getTestEmail, getTestName } from "../names"; import { diff --git a/integration-tests/cypress/commands/api/device.d.ts b/integration-tests/cypress/commands/api/device.d.ts index 28ab8aae7..e264d22ce 100644 --- a/integration-tests/cypress/commands/api/device.d.ts +++ b/integration-tests/cypress/commands/api/device.d.ts @@ -1,16 +1,4 @@ -// load the global Cypress types -/// - declare namespace Cypress { - type ApiDeviceResponse = import("@typedefs/api/device").ApiDeviceResponse; - type LatLng = import("@typedefs/api/common").LatLng; - type ApiGroupsUserRelationshipResponse = - import("@typedefs/api/group").ApiGroupUserResponse; - type DeviceType = import("@typedefs/api/consts").DeviceType; - type DeviceHistoryEntry = import("@commands/types").DeviceHistoryEntry; - type DeviceId = import("@typedefs/api/common").DeviceId; - type ApiMaskRegionsData = import("@typedefs/api/device").ApiMaskRegionsData; - interface Chainable { /** * create a device in the given group @@ -26,6 +14,15 @@ declare namespace Cypress { statusCode?: number ): Cypress.Chainable; + /** + * Set an active device inactive. Returns `true` on success + */ + apiDeviceDeleteOrSetInactive( + userName: string, + deviceName: string, + groupName: string + ): Cypress.Chainable; + /** * Upate a device's station (deviceHistory) at a given time * optionally check for non-200 statusCode @@ -76,6 +73,21 @@ declare namespace Cypress { statusCode?: number ): any; + /** + * register a device under a new group or name + * optionally check for an error response (statusCode!=200OK) + * optionally supply a password (autogenerate if not) + * optionally check for non-200 statusCode + */ + apiDeviceReregisterAuthorized( + oldName: string, + newName: string, + newGroup: string, + adminUserName: string, + newPassword?: string, + statusCode?: number + ): any; + /** * Retrieve devices list from /devices * compare with expected device details (JSON equivalent to that returned by API) @@ -100,10 +112,14 @@ declare namespace Cypress { ): any; /** - * Retrieve device details using name and groupname from /device/XX/in-group/YY - * use groupId if provided, otherwise groupName - the unused parameter should be set to null + * Retrieve device details using id */ - apiDevice(userName: string, deviceName: string, statusCode?: number): any; + apiDevice( + userName: string, + deviceName: string, + activeAndInactive?: boolean, + statusCode?: number + ): Cypress.Chainable; /** * Retrieve device details using name and groupname from /device/XX/in-group/YY @@ -114,7 +130,6 @@ declare namespace Cypress { deviceName: string, groupName: string | null, groupId: number | null, - expectedDevices: ApiDeviceResponse, params?: any, statusCode?: number ): any; diff --git a/integration-tests/cypress/commands/api/device.ts b/integration-tests/cypress/commands/api/device.ts index 89c5a9f9b..281a5f741 100644 --- a/integration-tests/cypress/commands/api/device.ts +++ b/integration-tests/cypress/commands/api/device.ts @@ -1,5 +1,3 @@ -/// - import { getTestName } from "../names"; import { v1ApiPath, @@ -231,6 +229,51 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + "apiDeviceReregisterAuthorized", + ( + oldName: string, + newName: string, + newGroup: string, + adminUserName: string, + password: string | null = null, + statusCode: number = 200 + ) => { + logTestDescription( + `Reregister camera '${newName}' in group '${newGroup}'`, + { + camera: newName, + group: newGroup, + }, + true + ); + const uniqueName = getTestName(newName); + if (password === null) { + password = "p" + getTestName(uniqueName); + } + + const data = { + newName: uniqueName, + newPassword: password, + newGroup: getTestName(newGroup), + authorizedToken: getCreds(adminUserName).jwt, + }; + + makeAuthorizedRequestWithStatus( + { + method: "POST", + url: v1ApiPath("devices/reregister-authorized"), + body: data, + }, + oldName, + statusCode + ).then((response) => { + const id = response.body.id; + saveCreds(response, newName, id); + }); + } +); + function createDevice( deviceName: string, groupName: string, @@ -374,17 +417,22 @@ Cypress.Commands.add( Cypress.Commands.add( "apiDevice", - (userName: string, deviceName: string, statusCode: number = 200) => { + ( + userName: string, + deviceName: string, + activeAndInactive: boolean = false, + statusCode: number = 200 + ) => { logTestDescription(`Get device ${deviceName} for ${userName}`, { deviceName, userName, }); - const fullUrl = v1ApiPath("devices/" + getCreds(deviceName).id); - return makeAuthorizedRequestWithStatus( { method: "GET", - url: fullUrl, + url: v1ApiPath(`devices/${getCreds(deviceName).id}`, { + "only-active": !activeAndInactive, + }), }, userName, statusCode @@ -723,6 +771,24 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + "apiDeviceDeleteOrSetInactive", + (userName: string, deviceName: string, groupName: string) => { + const device = getCreds(deviceName); + const group = getCreds(groupName); + makeAuthorizedRequest( + { + method: "DELETE", + url: v1ApiPath(`devices/${device.id}`), + body: { + group: group.id, + }, + }, + userName + ); + } +); + export function TestCreateExpectedDevice( deviceName: string, groupName: string, diff --git a/integration-tests/cypress/commands/api/events.d.ts b/integration-tests/cypress/commands/api/events.d.ts index fe7d06de3..c3140edb5 100644 --- a/integration-tests/cypress/commands/api/events.d.ts +++ b/integration-tests/cypress/commands/api/events.d.ts @@ -1,11 +1,4 @@ -/// - declare namespace Cypress { - type TestComparablePowerEvent = import("../types").TestComparablePowerEvent; - type ApiEventDetail = import("../types").ApiEventDetail; - type ApiEventReturned = import("../types").ApiEventReturned; - type ApiEventErrorCategory = import("../types").ApiEventErrorCategory; - type ApiPowerEventReturned = import("../types").ApiPowerEventReturned; interface Chainable { /** * Record a event for this device using device's credentials diff --git a/integration-tests/cypress/commands/api/events.ts b/integration-tests/cypress/commands/api/events.ts index 7be46f4ab..7012b9505 100644 --- a/integration-tests/cypress/commands/api/events.ts +++ b/integration-tests/cypress/commands/api/events.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { v1ApiPath, getCreds, diff --git a/integration-tests/cypress/commands/api/group-station.d.ts b/integration-tests/cypress/commands/api/group-station.d.ts index be50ec091..efb6aa66d 100644 --- a/integration-tests/cypress/commands/api/group-station.d.ts +++ b/integration-tests/cypress/commands/api/group-station.d.ts @@ -1,11 +1,4 @@ -// load the global Cypress types -/// declare namespace Cypress { - // @ts-ignore - type ApiStationResponse = import("@typedefs/api/station").ApiStationResponse; - // @ts-ignore - type ApiStationData = import("../types").ApiStationData; - type StationIdAlias = import("@typedefs/api/common").StationId; interface Chainable { /** * POST to api/v1/groups//station to add a single station @@ -22,7 +15,7 @@ declare namespace Cypress { untilDate?: string, statusCode?: number, additionalChecks?: any - ): Cypress.Chainable; + ): Cypress.Chainable; /** * Call api/v1/groups//station and check that returned values match expectedS tation diff --git a/integration-tests/cypress/commands/api/group-station.ts b/integration-tests/cypress/commands/api/group-station.ts index b177978c3..4e9dff2f7 100644 --- a/integration-tests/cypress/commands/api/group-station.ts +++ b/integration-tests/cypress/commands/api/group-station.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { checkRecording } from "./recording-tests"; import { ApiStationData } from "../types"; import { ApiStationResponse } from "@typedefs/api/station"; @@ -105,7 +102,7 @@ Cypress.Commands.add( groupIdOrName: string, stationName: string, expectedStation: ApiStationResponse, - excludeCheckOn: any = [], + excludeCheckOn: any = [".lastActiveThermalTime"], statusCode: number = 200, additionalChecks: any = {} ) => { @@ -168,7 +165,10 @@ Cypress.Commands.add( userName: string, groupIdOrName: string, expectedStations: ApiStationResponse[], - excludeCheckOn: any = [], + excludeCheckOn: any = [ + ".lastActiveThermalTime", + "[].lastActiveThermalTime", + ], statusCode: number = 200, additionalChecks: any = {} ) => { diff --git a/integration-tests/cypress/commands/api/group.d.ts b/integration-tests/cypress/commands/api/group.d.ts index 25df27883..fcb3d5b4d 100644 --- a/integration-tests/cypress/commands/api/group.d.ts +++ b/integration-tests/cypress/commands/api/group.d.ts @@ -1,17 +1,4 @@ -// load the global Cypress types -/// -type ApiStationResponse = import("@typedefs/api/station").ApiStationResponse; declare namespace Cypress { - type ApiGroupReturned = import("../types").ApiGroupReturned; - type ApiDeviceIdAndName = import("../types").ApiDeviceIdAndName; - type ApiGroupsDevice = import("../types").ApiGroupsDevice; - type ApiStationDataAlias = import("../types").ApiStationData; - type ApiStationDataReturned = import("../types").ApiStationDataReturned; - type ApiDeviceResponseAlias = - import("@typedefs/api/device").ApiDeviceResponse; - type ApiGroupUserRelationshipResponse = - import("@typedefs/api/group").ApiGroupUserResponse; - interface Chainable { /** * create a group for the given user (who has already been referenced in the test) diff --git a/integration-tests/cypress/commands/api/group.ts b/integration-tests/cypress/commands/api/group.ts index 4c4feb81c..a23021517 100644 --- a/integration-tests/cypress/commands/api/group.ts +++ b/integration-tests/cypress/commands/api/group.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { getTestEmail, getTestName } from "../names"; import { logTestDescription } from "../descriptions"; diff --git a/integration-tests/cypress/commands/api/monitoring.d.ts b/integration-tests/cypress/commands/api/monitoring.d.ts index 922089594..a19bd3871 100644 --- a/integration-tests/cypress/commands/api/monitoring.d.ts +++ b/integration-tests/cypress/commands/api/monitoring.d.ts @@ -1,9 +1,4 @@ -// load the global Cypress types -/// - declare namespace Cypress { - type TestVisitSearchParams = import("../types").TestVisitSearchParams; - type TestComparableVisit = import("../types").TestComparableVisit; interface Chainable { /** * check the visits returned match the listed visits specified. Only the specified information will be checked. diff --git a/integration-tests/cypress/commands/api/monitoring.ts b/integration-tests/cypress/commands/api/monitoring.ts index 35d36869d..ff4ce0f2e 100644 --- a/integration-tests/cypress/commands/api/monitoring.ts +++ b/integration-tests/cypress/commands/api/monitoring.ts @@ -1,7 +1,3 @@ -// load the global Cypress types -/// -/// - import { v1ApiPath, getCreds, convertToDate } from "../server"; import { logTestDescription, prettyLog } from "../descriptions"; import { stripBackName } from "../names"; diff --git a/integration-tests/cypress/commands/api/recording-tag.d.ts b/integration-tests/cypress/commands/api/recording-tag.d.ts index 050467c8e..61306ba80 100644 --- a/integration-tests/cypress/commands/api/recording-tag.d.ts +++ b/integration-tests/cypress/commands/api/recording-tag.d.ts @@ -1,9 +1,4 @@ -/// - declare namespace Cypress { - type ApiRecordingTagRequest = - import("@typedefs/api/tag").ApiRecordingTagRequest; - interface Chainable { /** * Add a tag to a recording. diff --git a/integration-tests/cypress/commands/api/recording-tag.ts b/integration-tests/cypress/commands/api/recording-tag.ts index 085155526..65237819b 100644 --- a/integration-tests/cypress/commands/api/recording-tag.ts +++ b/integration-tests/cypress/commands/api/recording-tag.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { v1ApiPath, getCreds, diff --git a/integration-tests/cypress/commands/api/recording-tests.d.ts b/integration-tests/cypress/commands/api/recording-tests.d.ts index 4c9558708..98c9b66b8 100644 --- a/integration-tests/cypress/commands/api/recording-tests.d.ts +++ b/integration-tests/cypress/commands/api/recording-tests.d.ts @@ -1,12 +1,4 @@ -// load the global Cypress types -/// - declare namespace Cypress { - // Avoiding redefinition in this namespace - type TestThermalRecordingInfoAlias = - import("../types").TestThermalRecordingInfo; - type RecordingIdAlias = import("@typedefs/api/common").RecordingId; - interface Chainable { /** * Upload a single recording to for a particular camera using pre-rolled test metadata diff --git a/integration-tests/cypress/commands/api/recording-tests.ts b/integration-tests/cypress/commands/api/recording-tests.ts index 9bddc8fd5..64b0e22db 100644 --- a/integration-tests/cypress/commands/api/recording-tests.ts +++ b/integration-tests/cypress/commands/api/recording-tests.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { getTestName } from "../names"; import { convertToDate, diff --git a/integration-tests/cypress/commands/api/recording.d.ts b/integration-tests/cypress/commands/api/recording.d.ts index 482de77ff..022d89ff1 100644 --- a/integration-tests/cypress/commands/api/recording.d.ts +++ b/integration-tests/cypress/commands/api/recording.d.ts @@ -1,24 +1,4 @@ -// load the global Cypress types -/// - declare namespace Cypress { - type ApiRecordingSet = import("../types").ApiRecordingSet; - type ApiRecordingReturned = import("../types").ApiRecordingReturned; - type ApiRecordingColumns = import("../types").ApiRecordingColumns; - type ApiRecordingNeedsTagReturned = - import("../types").ApiRecordingNeedsTagReturned; - type ApiRecordingDataMetadata = import("../types").ApiRecordingDataMetadata; - type Interception = import("cypress/types/net-stubbing").Interception; - type ApiRecordingResponse = - import("@typedefs/api/recording").ApiRecordingResponse; - type TestThermalRecordingInfo = import("../types").TestThermalRecordingInfo; - type RecordingId = number; - - type ApiAudioRecordingResponse = - import("@typedefs/api/recording").ApiAudioRecordingResponse; - type ApiThermalRecordingResponse = - import("@typedefs/api/recording").ApiThermalRecordingResponse; - interface Chainable { /** Check the values returned by /api/fileProcessing (get) * specify type and processingState (state) @@ -142,7 +122,8 @@ declare namespace Cypress { fileName?: string | { filename: string; key: string }[], recordingName?: string, statusCode?: number, - additionalChecks?: any + additionalChecks?: any, + useFileName?: string ): Cypress.Chainable; /** @@ -164,6 +145,8 @@ declare namespace Cypress { statusCode?: number ): any; + apiRecordingGetFile(userName: string, recordingNameOrId: RecordingId): any; + /** Get a single recording using api/v1/recordings/{id} * Verify that the recording data matched the expectedRecording * Optionally: check for a non-200 statusCode @@ -180,6 +163,8 @@ declare namespace Cypress { additionalChecks?: any ): any; + apiRecordingDownloadCheck(userName: string, recordingNameOrId: string): any; + /** Get a single recording that needs tagging using api/v1/recordings/needs-tag * Verify that the recording data matches (one of) the expectedRecordings * Optionally: check for a non-200 statusCode diff --git a/integration-tests/cypress/commands/api/recording.ts b/integration-tests/cypress/commands/api/recording.ts index 05afa45ba..f4f89effb 100644 --- a/integration-tests/cypress/commands/api/recording.ts +++ b/integration-tests/cypress/commands/api/recording.ts @@ -1,11 +1,7 @@ -// load the global Cypress types -/// - import { uploadFile } from "../fileUpload"; import { getTestName } from "../names"; import { v1ApiPath, - processingApiPath, getCreds, makeAuthorizedRequestWithStatus, saveIdOnly, @@ -20,7 +16,7 @@ import { ApiRecordingNeedsTagReturned, ApiRecordingColumns, } from "../types"; -import { ApiRecordingColumnNames, NOT_NULL } from "../constants"; +import { ApiRecordingColumnNames } from "../constants"; import { ApiAudioRecordingResponse, ApiRecordingResponse, @@ -28,10 +24,8 @@ import { } from "@typedefs/api/recording"; import { HttpStatusCode } from "@typedefs/api/consts"; import { RecordingId } from "@typedefs/api/common"; -import { ApiTrackResponse } from "@typedefs/api/track"; import { TEMPLATE_THERMAL_RECORDING, - TEMPLATE_THERMAL_RECORDING_RESPONSE, TEMPLATE_TRACK, } from "@commands/dataTemplate"; import { TestCreateRecordingData } from "@commands/api/recording-tests"; @@ -284,7 +278,8 @@ Cypress.Commands.add( fileName: string | { filename: string; key: string }[] = "invalid.cptv", recordingName: string = "recording1", statusCode: number = 200, - additionalChecks: any = {} + additionalChecks: any = {}, + filenameToUse?: string ) => { logTestDescription( `Upload recording ${recordingName} to '${deviceName}'`, @@ -300,7 +295,8 @@ Cypress.Commands.add( data.type, data, "@addRecording", - statusCode + statusCode, + filenameToUse ).then((p) => { const x = p as unknown as { recordingId: RecordingId; @@ -441,6 +437,24 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + "apiRecordingGetFile", + (userName: string, recordingId: RecordingId, statusCode: number = 200) => { + const url = v1ApiPath(`recordings/raw/${recordingId}`); + makeAuthorizedRequestWithStatus( + { + method: "GET", + url, + encoding: null, + }, + userName, + statusCode + ).then((response) => { + cy.wrap(response); + }); + } +); + Cypress.Commands.add( "apiRecordingDelete", ( @@ -588,6 +602,31 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + "apiRecordingDownloadCheck", + (userName: string, recordingNameOrId: string) => { + logTestDescription( + `Check downloaded recording hash for ${recordingNameOrId} `, + { + recordingName: recordingNameOrId, + } + ); + const recordingId: RecordingId = getCreds(recordingNameOrId).id; + cy.apiRecordingGet(userName, recordingId as RecordingId, 200).then( + (response) => { + expect(response.body.rawSize).to.exist; + expect(response.body.downloadRawJWT).to.exist; + const rawSize = response.body.rawSize; + cy.apiRecordingGetFile(userName, recordingId as RecordingId).then( + (response) => { + expect(response.body.byteLength).to.equal(rawSize); + } + ); + } + ); + } +); + Cypress.Commands.add( "apiRecordingCheck", ( diff --git a/integration-tests/cypress/commands/api/station.d.ts b/integration-tests/cypress/commands/api/station.d.ts index 48d44a03b..9e47afbc8 100644 --- a/integration-tests/cypress/commands/api/station.d.ts +++ b/integration-tests/cypress/commands/api/station.d.ts @@ -1,10 +1,4 @@ -// load the global Cypress types -/// - declare namespace Cypress { - // @ts-ignore - type ApiStationData = import("../types").ApiStationData; - type ApiStationResponse = import("@typedefs/api/station").ApiStationResponse; interface Chainable { /** * GET to api/v1/stations to retrieve all stations for current user @@ -64,7 +58,7 @@ declare namespace Cypress { * Optionally check for fail response (statusCode!=200) * By default deleteRecordings is passed as true. * By default stationId is looked up from name in stationIdOrName. - * Optionally: use the raw stationId provided (additionalChecks["useRawSta tionId"]=true) + * Optionally: use the raw stationId provided (additionalChecks["useRawStationId"]=true) * Optionally: check for returned additionalChecks["messages"] * Optionally: check for returned additionalChecks["warnings"] */ diff --git a/integration-tests/cypress/commands/api/station.ts b/integration-tests/cypress/commands/api/station.ts index f2e289741..fe8a91c82 100644 --- a/integration-tests/cypress/commands/api/station.ts +++ b/integration-tests/cypress/commands/api/station.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { ApiStationData } from "../types"; import { ApiStationResponse } from "@typedefs/api/station"; import { getTestName } from "../names"; @@ -22,7 +19,7 @@ Cypress.Commands.add( ( userName: string, expectedStations: ApiStationResponse[], - excludeCheckOn: any = [], + excludeCheckOn: any = ["lastActiveThermalTime", "[].lastActiveThermalTime"], statusCode: number = 200, additionalChecks: any = {} ) => { @@ -80,7 +77,7 @@ Cypress.Commands.add( userName: string, stationIdOrName: string, expectedStation: ApiStationResponse, - excludeCheckOn: any = [], + excludeCheckOn: any = [".lastActiveThermalTime"], statusCode: number = 200, additionalChecks: any = {} ) => { @@ -252,7 +249,7 @@ Cypress.Commands.add( stationIdOrName: string, retirementDate: string = new Date().toISOString(), additionalChecks: any = {} - ): any => { + ) => { let stationId: string; //Get station ID from name (unless we're asked not to) if (additionalChecks["useRawStationId"] === true) { diff --git a/integration-tests/cypress/commands/api/track.d.ts b/integration-tests/cypress/commands/api/track.d.ts index 77e773673..787ced0b5 100644 --- a/integration-tests/cypress/commands/api/track.d.ts +++ b/integration-tests/cypress/commands/api/track.d.ts @@ -1,10 +1,4 @@ -/// - declare namespace Cypress { - type ApiTrackDataRequest = import("@typedefs/api/track").ApiTrackDataRequest; - type ApiTrackResponse = import("@typedefs/api/track").ApiTrackResponse; - type ApiTrackTagRequest = import("@typedefs/api/trackTag").ApiTrackTagRequest; - interface Chainable { /** * Add a track to a recording. @@ -49,7 +43,7 @@ declare namespace Cypress { /** * Retrieve and check a single track from a recording. * Calls /recording/:id/tracks/:trackId (GET) - * Verfiy that the tracks data matched the expectedtracks + * Verify that the tracks data matched the expectedtracks * Optionally: Exclude checks on specific values by specifying them in excludeChecksOn * Optionally check for a non-200 return statusCode * By default recording ID is looked up by name using recordingNameOrId diff --git a/integration-tests/cypress/commands/api/track.ts b/integration-tests/cypress/commands/api/track.ts index cdbc72594..3eab4d5ae 100644 --- a/integration-tests/cypress/commands/api/track.ts +++ b/integration-tests/cypress/commands/api/track.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { v1ApiPath, getCreds, diff --git a/integration-tests/cypress/commands/api/user.ts b/integration-tests/cypress/commands/api/user.ts index fced3091c..563a92146 100644 --- a/integration-tests/cypress/commands/api/user.ts +++ b/integration-tests/cypress/commands/api/user.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { getTestEmail, getTestName } from "../names"; import { apiPath, diff --git a/integration-tests/cypress/commands/api/visits.d.ts b/integration-tests/cypress/commands/api/visits.d.ts index c98276bcb..60083ba3c 100644 --- a/integration-tests/cypress/commands/api/visits.d.ts +++ b/integration-tests/cypress/commands/api/visits.d.ts @@ -1,8 +1,4 @@ -// load the global Cypress types -/// - declare namespace Cypress { - // type TestComparableVisit = import("../types").TestComparableVisit; interface Chainable { /** * check the visits returned match the listed visits specified. Only the specified information will be checked. diff --git a/integration-tests/cypress/commands/api/visits.ts b/integration-tests/cypress/commands/api/visits.ts index 75f2ba415..6e95f59c5 100644 --- a/integration-tests/cypress/commands/api/visits.ts +++ b/integration-tests/cypress/commands/api/visits.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - import { v1ApiPath, getCreds } from "../server"; import { logTestDescription } from "../descriptions"; import { TestComparableVisit, TestVisitsWhere } from "../types"; diff --git a/integration-tests/cypress/commands/browsegui/general.d.ts b/integration-tests/cypress/commands/browsegui/general.d.ts index bb6cb094b..da02875ac 100644 --- a/integration-tests/cypress/commands/browsegui/general.d.ts +++ b/integration-tests/cypress/commands/browsegui/general.d.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - declare namespace Cypress { interface Chainable { /** diff --git a/integration-tests/cypress/commands/browsegui/groups.d.ts b/integration-tests/cypress/commands/browsegui/groups.d.ts index c3391a8c5..d648a462a 100644 --- a/integration-tests/cypress/commands/browsegui/groups.d.ts +++ b/integration-tests/cypress/commands/browsegui/groups.d.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - declare namespace Cypress { interface Chainable { /** diff --git a/integration-tests/cypress/commands/browsegui/user.d.ts b/integration-tests/cypress/commands/browsegui/user.d.ts index 1f5242604..4059e2de0 100644 --- a/integration-tests/cypress/commands/browsegui/user.d.ts +++ b/integration-tests/cypress/commands/browsegui/user.d.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - declare namespace Cypress { interface Chainable { /** diff --git a/integration-tests/cypress/commands/dataTemplate.ts b/integration-tests/cypress/commands/dataTemplate.ts index 56cdb5ded..2a3a692ab 100644 --- a/integration-tests/cypress/commands/dataTemplate.ts +++ b/integration-tests/cypress/commands/dataTemplate.ts @@ -53,6 +53,22 @@ export const TEMPLATE_AUDIO_RECORDING: ApiRecordingSet = { processingState: RecordingProcessingState.Finished, }; +export const TEMPLATE_AUDIO_RECORDING_TC2: ApiRecordingSet = { + type: RecordingType.Audio, + fileHash: null, + duration: 60, + recordingDateTime: "2024-10-02T01:47:11.101Z", + location: [-43.53345, 172.64745], + cacophonyIndex: [ + { end_s: 20, begin_s: 0, index_percent: 80.8 }, + { end_s: 40, begin_s: 20, index_percent: 77.1 }, + { end_s: 60, begin_s: 40, index_percent: 71.6 }, + ], + additionalMetadata: null, + version: null, + processingState: RecordingProcessingState.Analyse, +}; + export const TEMPLATE_AUDIO_RECORDING_PROCESSING: ApiRecordingForProcessing = { id: NOT_NULL, type: RecordingType.Audio, @@ -129,6 +145,31 @@ export const TEMPLATE_AUDIO_RECORDING_RESPONSE: ApiAudioRecordingResponse = { redacted: false, }; +export const TEMPLATE_AUDIO_RECORDING_RESPONSE_TC2: ApiAudioRecordingResponse = + { + deviceId: 2023, + deviceName: "mattb-s5", + duration: 60, + groupId: 389, + groupName: "mattb-audio", + stationId: NOT_NULL, + stationName: NOT_NULL_STRING, + id: 204771, + location: { + lat: -43.53345, + lng: 172.64745, + }, + version: "1.8.1", + rawMimeType: "", + processing: false, + processingState: RecordingProcessingState.Analyse, + recordingDateTime: "2024-10-02T01:47:11.101Z", + tags: [], + tracks: [], + type: RecordingType.Audio, + redacted: false, + }; + //THERMAL RECORDINGS export const TEMPLATE_AUDIO_TRACK: ApiTrackSet = { @@ -263,11 +304,11 @@ export const TEMPLATE_THERMAL_RECORDING_RESPONSE: ApiThermalRecordingResponse = id: 892972, rawMimeType: "application/x-cptv", processingState: RecordingProcessingState.Finished, - duration: 0.5555555555555556, + duration: 0.4444444444444444, recordingDateTime: "2021-07-17T20:13:17.248Z", location: { lat: -45.29115, lng: 169.30845 }, type: RecordingType.ThermalRaw, - additionalMetadata: { algorithm: 31143, previewSecs: 5, totalFrames: 5 }, + additionalMetadata: { algorithm: 31143, previewSecs: 5, totalFrames: 4 }, //additionalMetadata: { algorithm: 31143, previewSecs: 5, totalFrames: 141 }, groupId: 246, stationId: NOT_NULL, @@ -287,7 +328,7 @@ export const TEMPLATE_THERMAL_RECORDING: ApiRecordingSet = { additionalMetadata: { algorithm: 31143, previewSecs: 5, - totalFrames: 5, + totalFrames: 4, }, metadata: { tracks: [TEMPLATE_TRACK], diff --git a/integration-tests/cypress/commands/fakeCamera/thermalcamera.js b/integration-tests/cypress/commands/fakeCamera/thermalcamera.js deleted file mode 100644 index fe316aa48..000000000 --- a/integration-tests/cypress/commands/fakeCamera/thermalcamera.js +++ /dev/null @@ -1,37 +0,0 @@ -// Not in use right now - hopefully we can get it working again - -const url = require("url"); -const names = require("./names"); - -Cypress.Commands.add("createDevice", (cameraName, groupName) => { - const urlParams = url.format({ - pathname: "create/" + names.getTestName(cameraName), - query: { - "group-name": names.getTestName(groupName), - "api-server": Cypress.config("cacophony-api-server"), - }, - }); - - cy.request(Cypress.config("fakeCameraUrl") + urlParams).then((request) => { - Cypress.config("devices")[cameraName] = { - id: request.body, - groupName: groupName, - }; - }); -}); - -Cypress.Commands.add("cameraEvent", (eventType) => { - const urlParams = url.format({ - pathname: "triggerEvent/" + eventType, - }); - - cy.request("GET", Cypress.config("fakeCameraUrl") + urlParams); -}); - -Cypress.Commands.add("cameraRecording", () => { - const urlParams = url.format({ - pathname: "sendCPTVFrames", - }); - - cy.request("GET", Cypress.config("fakeCameraUrl") + urlParams); -}); diff --git a/integration-tests/cypress/commands/fileUpload.ts b/integration-tests/cypress/commands/fileUpload.ts index 630bddcfb..d5d670f57 100644 --- a/integration-tests/cypress/commands/fileUpload.ts +++ b/integration-tests/cypress/commands/fileUpload.ts @@ -1,8 +1,4 @@ -// load the global Cypress types -/// - import { getCreds } from "./server"; -import { Interception } from "cypress/types/net-stubbing"; import { HttpStatusCode, RecordingType } from "@typedefs/api/consts"; import { ApiRecordingSet } from "@commands/types"; import { RecordingId } from "@typedefs/api/common"; @@ -48,7 +44,8 @@ export function uploadFile( fileType: RecordingType | string, data: ApiRecordingSet | Record, waitOn: string, - statusCode: number = 200 + statusCode: number = 200, + fileNameToUse?: string ): Cypress.Chainable< Promise<{ recordingId: RecordingId; @@ -66,7 +63,7 @@ export function uploadFile( const formData = new FormData(); formData.set("data", JSON.stringify(data)); if (!Array.isArray(blob)) { - formData.set("file", blob, fileName as string); //adding a file to the form + formData.set("file", blob, fileNameToUse || (fileName as string)); //adding a file to the form } else { for (const item of blob) { formData.set(item.key, item.fileBlob, item.filename); diff --git a/integration-tests/cypress/commands/names.ts b/integration-tests/cypress/commands/names.ts index f7ab9fa6e..2d08afb96 100644 --- a/integration-tests/cypress/commands/names.ts +++ b/integration-tests/cypress/commands/names.ts @@ -1,4 +1,3 @@ -// @ts-ignore import { randomBytes } from "crypto"; //suffix to add to ids making them unique within each test run diff --git a/integration-tests/cypress/commands/server.ts b/integration-tests/cypress/commands/server.ts index d1d668366..e1e1ee848 100644 --- a/integration-tests/cypress/commands/server.ts +++ b/integration-tests/cypress/commands/server.ts @@ -1,6 +1,3 @@ -// load the global Cypress types -/// - export const DEFAULT_DATE = new Date(2021, 4, 9, 22); import { logTestDescription } from "./descriptions"; diff --git a/integration-tests/cypress/commands/types.d.ts b/integration-tests/cypress/commands/types.d.ts index bec4b4fa9..05cc4545f 100644 --- a/integration-tests/cypress/commands/types.d.ts +++ b/integration-tests/cypress/commands/types.d.ts @@ -1,7 +1,7 @@ import { ApiAlertCondition } from "@typedefs/api/alerts"; import { RecordingProcessingState, RecordingType } from "@typedefs/api/consts"; import { CacophonyIndex } from "@typedefs/api/recording"; -import { LatLng } from "@typedefs/api/common"; +import { LatLng, TrackId } from "@typedefs/api/common"; // from api/v1/authenticate/token (POST) export interface ApiAuthenticateAccess { @@ -322,9 +322,9 @@ export interface ApiRecordingForProcessing { export interface ApiRecordingSet { type: RecordingType; fileHash?: string; - duration: number; + duration?: number; location?: ApiLocation | number[]; - recordingDateTime: string; + recordingDateTime?: string; relativeToDawn?: number; relativeToDusk?: number; version?: string; @@ -477,16 +477,24 @@ export interface ApiRecordingTrackData { //from api/v1/recordings (post) export interface ApiTrackSet { + id?: TrackId; + tracker_version?: number; + num_frames?: number; positions?: any; start_s: number; end_s: number; + frame_start?: number; + frame_end?: number; minFreq?: number; maxFreq?: number; - predictions: { - model_id: number; - confident_tag?: string; - confidence?: number; - }[]; + predictions: ( + | { + model_id: number; + confident_tag?: string; + confidence?: number; + } + | any + )[]; all_class_confidences?: any; automatic?: boolean; } @@ -515,6 +523,7 @@ export interface TestThermalRecordingInfo { time?: Date | string; duration?: number; model?: string; + type?: RecordingType; tracks?: ApiTrackSet[]; noTracks?: boolean; // by default there will normally be one track, set to true if you don't want tracks minsLater?: number; // minutes that later that the recording is taken diff --git a/integration-tests/cypress/e2e/api/authenticate/authenticate.ts b/integration-tests/cypress/e2e/api/batch-1/authenticate/authenticate.ts similarity index 98% rename from integration-tests/cypress/e2e/api/authenticate/authenticate.ts rename to integration-tests/cypress/e2e/api/batch-1/authenticate/authenticate.ts index f340b1ae7..f4cf8125b 100644 --- a/integration-tests/cypress/e2e/api/authenticate/authenticate.ts +++ b/integration-tests/cypress/e2e/api/batch-1/authenticate/authenticate.ts @@ -1,4 +1,3 @@ -/// import { getTestEmail, getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; import { HttpStatusCode } from "@typedefs/api/consts"; diff --git a/integration-tests/cypress/e2e/api/devices/device_historic_settings.ts b/integration-tests/cypress/e2e/api/batch-1/devices/device_historic_settings.ts similarity index 100% rename from integration-tests/cypress/e2e/api/devices/device_historic_settings.ts rename to integration-tests/cypress/e2e/api/batch-1/devices/device_historic_settings.ts diff --git a/integration-tests/cypress/e2e/api/devices/device_in_group.ts b/integration-tests/cypress/e2e/api/batch-1/devices/device_in_group.ts similarity index 98% rename from integration-tests/cypress/e2e/api/devices/device_in_group.ts rename to integration-tests/cypress/e2e/api/batch-1/devices/device_in_group.ts index 6fd9da2f7..f9546b34b 100644 --- a/integration-tests/cypress/e2e/api/devices/device_in_group.ts +++ b/integration-tests/cypress/e2e/api/batch-1/devices/device_in_group.ts @@ -1,5 +1,3 @@ -/// - import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; import { logTestDescription } from "@commands/descriptions"; diff --git a/integration-tests/cypress/e2e/api/devices/device_mask_regions.ts b/integration-tests/cypress/e2e/api/batch-1/devices/device_mask_regions.ts similarity index 99% rename from integration-tests/cypress/e2e/api/devices/device_mask_regions.ts rename to integration-tests/cypress/e2e/api/batch-1/devices/device_mask_regions.ts index 0b1845141..60f4cb3ca 100644 --- a/integration-tests/cypress/e2e/api/devices/device_mask_regions.ts +++ b/integration-tests/cypress/e2e/api/batch-1/devices/device_mask_regions.ts @@ -1,4 +1,3 @@ -/// import { getCreds, makeAuthorizedRequest, v1ApiPath } from "@commands/server"; import { TestGetLocation } from "@commands/api/station"; import { ApiMaskRegionsData } from "@typedefs/api/device"; diff --git a/integration-tests/cypress/e2e/api/devices/device_register.ts b/integration-tests/cypress/e2e/api/batch-1/devices/device_register.ts similarity index 91% rename from integration-tests/cypress/e2e/api/devices/device_register.ts rename to integration-tests/cypress/e2e/api/batch-1/devices/device_register.ts index 73a2ca447..d2399e5a4 100644 --- a/integration-tests/cypress/e2e/api/devices/device_register.ts +++ b/integration-tests/cypress/e2e/api/batch-1/devices/device_register.ts @@ -1,5 +1,3 @@ -/// - import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; import { DeviceHistoryEntry } from "@commands/types"; @@ -11,6 +9,7 @@ import { DeviceType, HttpStatusCode } from "@typedefs/api/consts"; describe("Device register", () => { const camsGroup = "cams"; const otherCams = "other cams"; + const adminUser = "Anita"; const KEEP_DEVICE_NAME = false; const GENERATE_UNIQUE_NAME = true; @@ -19,9 +18,9 @@ describe("Device register", () => { const LOG = true; before(() => { - cy.testCreateUserGroupAndDevice("Anita", camsGroup, "gotya"); + cy.testCreateUserGroupAndDevice(adminUser, camsGroup, "gotya"); cy.apiDeviceAdd("defaultcam", camsGroup); - cy.apiGroupAdd("Anita", otherCams, true); + cy.apiGroupAdd(adminUser, otherCams, true); }); it("Adding device created valid deviceHistory entry", () => { @@ -37,7 +36,7 @@ describe("Device register", () => { ); expectedHistory.saltId = 1234567; - cy.apiDeviceHistoryCheck("Anita", "aNewDevice", [expectedHistory]); + cy.apiDeviceHistoryCheck(adminUser, "aNewDevice", [expectedHistory]); }); }); @@ -142,7 +141,7 @@ describe("Device register", () => { //Test with Salt Id = device id by default cy.apiDeviceInGroupCheck( - "Anita", + adminUser, "defaultcam", camsGroup, null, @@ -196,8 +195,4 @@ describe("Device register", () => { HttpStatusCode.Forbidden ); }); - - it.skip("Correctly handles missing parameters in register device", () => { - //TODO: write this (helper apiDeviceAdd does not yet support missing params) - }); }); diff --git a/integration-tests/cypress/e2e/api/devices/device_reregister.ts b/integration-tests/cypress/e2e/api/batch-1/devices/device_reregister.ts similarity index 94% rename from integration-tests/cypress/e2e/api/devices/device_reregister.ts rename to integration-tests/cypress/e2e/api/batch-1/devices/device_reregister.ts index 81017e2e5..90d89eb10 100644 --- a/integration-tests/cypress/e2e/api/devices/device_reregister.ts +++ b/integration-tests/cypress/e2e/api/batch-1/devices/device_reregister.ts @@ -1,10 +1,12 @@ -/// - import { NOT_NULL_STRING } from "@commands/constants"; import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; import ApiDeviceResponse = Cypress.ApiDeviceResponse; -import { DeviceType, HttpStatusCode } from "@typedefs/api/consts"; +import { + DeviceType, + HttpStatusCode, + RecordingType, +} from "@typedefs/api/consts"; import { DeviceHistoryEntry } from "@commands/types"; import { TestCreateExpectedHistoryEntry } from "@commands/api/device"; @@ -40,6 +42,13 @@ describe("Device reregister", () => { groupId: getCreds("RR_group1").id, }; }); + cy.log("Add a recording so that old device isn't deleted when renamed"); + cy.apiRecordingAdd( + "RR_cam1", + { type: RecordingType.ThermalRaw }, + "oneframe.cptv", + "raRecording1" + ); //re-register camera & store device details cy.apiDeviceReregister("RR_cam1", "RR_cam1b", "RR_group1").then(() => { @@ -131,6 +140,14 @@ describe("Device reregister", () => { cy.log("create second group"); cy.testCreateUserAndGroup("RR_user2b", "RR_group2b"); + cy.log("Add a recording so that old device isn't deleted when renamed"); + cy.apiRecordingAdd( + "RR_cam2", + { type: RecordingType.ThermalRaw }, + "oneframe.cptv", + "raRecording1" + ); + cy.log("re-register camera to 2nd group & store device details"); cy.apiDeviceReregister("RR_cam2", "RR_cam2", "RR_group2b").then(() => { expectedDevice2b = { @@ -170,6 +187,14 @@ describe("Device reregister", () => { } ); + cy.log("Add a recording so that old device isn't deleted when renamed"); + cy.apiRecordingAdd( + "RR_cam3", + { type: RecordingType.ThermalRaw }, + "oneframe.cptv", + "raRecording1" + ); + cy.log("create second group"); cy.testCreateUserAndGroup("RR_user3b", "RR_group3b"); diff --git a/integration-tests/cypress/e2e/api/devices/devices.ts b/integration-tests/cypress/e2e/api/batch-1/devices/devices.ts similarity index 95% rename from integration-tests/cypress/e2e/api/devices/devices.ts rename to integration-tests/cypress/e2e/api/batch-1/devices/devices.ts index a8679a8be..af88ed6bc 100644 --- a/integration-tests/cypress/e2e/api/devices/devices.ts +++ b/integration-tests/cypress/e2e/api/batch-1/devices/devices.ts @@ -1,8 +1,7 @@ -/// import { getTestName } from "@commands/names"; import { makeAuthorizedRequest, v1ApiPath, getCreds } from "@commands/server"; import ApiDeviceResponse = Cypress.ApiDeviceResponse; -import { DeviceType } from "@typedefs/api/consts"; +import { DeviceType, RecordingType } from "@typedefs/api/consts"; describe("Devices list", () => { const superuser = getCreds("superuser")["email"]; @@ -70,6 +69,12 @@ describe("Devices list", () => { //reregistered device cy.testCreateUserGroupAndDevice(user3, group3, camera3); + cy.log("Add a recording so that old device isn't deleted when renamed"); + cy.apiRecordingAdd( + camera3, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ); cy.apiDeviceReregister(camera3, camera4, group3).then(() => { expectedDevice3AdminView = { id: getCreds(camera3).id, diff --git a/integration-tests/cypress/e2e/api/batch-1/devices/devices_deleted_and_inactive.ts b/integration-tests/cypress/e2e/api/batch-1/devices/devices_deleted_and_inactive.ts new file mode 100644 index 000000000..0511bdedd --- /dev/null +++ b/integration-tests/cypress/e2e/api/batch-1/devices/devices_deleted_and_inactive.ts @@ -0,0 +1,443 @@ +import { HttpStatusCode, RecordingType } from "@typedefs/api/consts"; +import { getCreds } from "@commands/server"; + +// NOTE: When referring to "reassigning" a device, we mean moving a device into a group that already has an *inactive* +// device that shares the same name as the new name for the device we're moving. + +describe("Devices deleted, inactive, and reassigned", () => { + const group = "cameras"; + const group2 = "cameras-2"; + const group3 = "cameras-3"; + const adminUser = "Fredirika"; + const adminUser2 = "Angelica"; + before(() => { + cy.testCreateUserAndGroup(adminUser, group); + cy.testCreateUserAndGroup(adminUser2, group3); + cy.apiGroupAdd(adminUser, group2); + }); + + it("Deleting a device with no recordings removes it completely", () => { + const device = "A001"; + cy.apiDeviceAdd(device, group); + cy.apiDeviceDeleteOrSetInactive(adminUser, device, group); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Forbidden); + }); + + it("Deleting a device with recordings sets the device inactive", () => { + const device = "A001"; + cy.apiDeviceAdd(device, group); + cy.apiRecordingAdd( + device, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording for device", recordingId); + cy.apiDeviceDeleteOrSetInactive(adminUser, device, group); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be inactive.").to + .be.false; + } + ); + }); + }); + + it( + "Recordings uploaded for an inactive device *from the device* should " + + "reactivate the device if the device has not been moved/re-registered", + () => { + cy.log( + "Recordings uploaded for an inactive device *from the device* make it active again" + ); + let originalDeviceId; + const device = "A002"; + cy.apiDeviceAdd(device, group).then((deviceId) => { + cy.log("Created device", deviceId); + originalDeviceId = deviceId; + }); + cy.apiRecordingAdd( + device, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording for device", recordingId); + cy.log("Setting device inactive"); + cy.apiDeviceDeleteOrSetInactive(adminUser, device, group); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be inactive.").to + .be.false; + // Now add another recording for the device + cy.apiRecordingAdd( + device, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording for inactive device", recordingId); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect( + response.body.device.active, + "Device should be active." + ).to.be.true; + } + ); + }); + } + ); + }); + + cy.log( + "Recordings uploaded for an inactive device *from the device* which was moved *don't* make it active again" + ); + const device2 = "A003"; + cy.apiRecordingAdd( + device, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording for device", recordingId); + cy.apiDeviceDeleteOrSetInactive(adminUser, device, group); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be inactive.").to + .be.false; + cy.log("Re-register the device into another group"); + cy.apiDeviceReregisterAuthorized( + device, + device2, + group2, + adminUser + ).then((response) => { + cy.log("Re-registered device", response.body.id); + expect(response.body.id).to.not.equal(originalDeviceId); + // Now add another recording for the device + cy.apiRecordingAdd( + device, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording for original device", recordingId); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect( + response.body.device.active, + "Device should be inactive." + ).to.be.false; + } + ); + }); + }); + } + ); + }); + } + ); + + it("Recordings uploaded for an inactive device on behalf the device (sidekick) should not reactivate the device", () => { + const device = "B001"; + cy.apiDeviceAdd(device, group).then((deviceId) => { + cy.log("Created device", deviceId); + cy.apiRecordingAdd( + device, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording for device", recordingId); + cy.apiDeviceDeleteOrSetInactive(adminUser, device, group); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be inactive.").to + .be.false; + + cy.log("Now upload some recordings on behalf of the device"); + cy.apiRecordingAddOnBehalfUsingDevice( + adminUser, + device, + { type: RecordingType.ThermalRaw }, + "foo", + "oneframe.cptv" + ).then(() => { + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect( + response.body.device.active, + "Device should still be active." + ).to.be.false; + } + ); + }); + } + ); + }); + }); + }); + + it( + "Reassigning a device into the same group should result in the new device " + + "becoming the old device and being set active.", + () => { + let originalDeviceId; + const device = "A004"; + cy.apiDeviceAdd(device, group).then((deviceId) => { + cy.log("Created device", deviceId); + originalDeviceId = deviceId; + }); + cy.apiRecordingAdd( + device, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording for device", recordingId); + cy.log("Setting device inactive"); + cy.apiDeviceDeleteOrSetInactive(adminUser, device, group); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be inactive.").to + .be.false; + cy.log("Re-register the device into the same group"); + cy.apiDeviceReregisterAuthorized( + device, + device, + group, + adminUser + ).then((response) => { + cy.log("Re-registered device", response.body.id); + expect(response.body.id).to.equal(originalDeviceId); + cy.apiDevice(adminUser, device, true, HttpStatusCode.Ok).then( + (response) => { + expect( + response.body.device.active, + "Device should be active." + ).to.be.true; + } + ); + cy.log("Device inherits recordings"); + cy.testCheckDeviceHasRecordings(adminUser, device, 1); + }); + } + ); + }); + } + ); + + it( + "Reassigning a device with recordings into a different group should make it become the the existing device, " + + "be set active, while in the group it's moved from it becomes inactive", + () => { + let originalDeviceId; + const deviceDest = "A005"; + const deviceSource = "A005a"; + cy.apiDeviceAdd(deviceDest, group2).then((deviceId) => { + cy.log("Created device in destination group", deviceId); + originalDeviceId = deviceId; + cy.apiRecordingAdd( + deviceDest, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then(() => { + cy.apiRecordingAdd( + deviceDest, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording to device", recordingId); + cy.log("Setting device inactive"); + cy.apiDeviceDeleteOrSetInactive(adminUser, deviceDest, group2); + cy.apiDevice(adminUser, deviceDest, true, HttpStatusCode.Ok).then( + (response) => { + expect( + response.body.device.active, + "Device should be inactive." + ).to.be.false; + cy.apiDeviceAdd(deviceSource, group).then((deviceId) => { + cy.log("Created device in source group", deviceId); + cy.apiRecordingAdd( + deviceSource, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + // Now move this device from source group to destination + cy.apiDeviceReregisterAuthorized( + deviceSource, + deviceDest, + group2, + adminUser + ).then((response) => { + cy.log("Re-registered device", response.body.id); + expect(response.body.id).to.equal(originalDeviceId); + cy.apiDevice( + adminUser, + deviceDest, + true, + HttpStatusCode.Ok + ).then((response) => { + expect( + response.body.device.active, + "Dest device should now be active." + ).to.be.true; + }); + cy.apiDevice( + adminUser, + deviceSource, + true, + HttpStatusCode.Ok + ).then((response) => { + expect( + response.body.device.active, + "Source device should be inactive." + ).to.be.false; + }); + cy.log("Device inherits recordings"); + cy.testCheckDeviceHasRecordings(adminUser, deviceDest, 2); + }); + }); + }); + } + ); + }); + }); + }); + } + ); + + it("Reassigning a device where there's an active device in the destination group with the same name fails", () => { + const deviceDest = "A008"; + const deviceSource = "A008a"; + cy.apiDeviceAdd(deviceDest, group2).then((deviceId) => { + cy.log("Created device in destination group", deviceId); + cy.apiRecordingAdd( + deviceDest, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording to device", recordingId); + cy.apiDevice(adminUser, deviceDest, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be active.").to + .be.true; + cy.apiDeviceAdd(deviceSource, group).then((deviceId) => { + cy.log("Created device in source group", deviceId); + cy.apiRecordingAdd( + deviceSource, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then(() => { + cy.log( + "Attempt to move device from source group to destination" + ); + cy.apiDeviceReregisterAuthorized( + deviceSource, + deviceDest, + group2, + adminUser, + null, + HttpStatusCode.BadRequest + ); + }); + }); + } + ); + }); + }); + }); + + it( + "Reassigning a device without recordings into a different group should make it become the " + + "the existing device, be set active, while in the group it's moved it is deleted", + () => { + let originalDeviceId; + const deviceDest = "A006"; + const deviceSource = "A006"; + cy.apiDeviceAdd(deviceDest, group2).then((deviceId) => { + cy.log("Created device in destination group", deviceId); + originalDeviceId = deviceId; + cy.apiRecordingAdd( + deviceDest, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording to device", recordingId); + cy.log("Setting device inactive"); + cy.apiDeviceDeleteOrSetInactive(adminUser, deviceDest, group2); + cy.apiDevice(adminUser, deviceDest, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be inactive.") + .to.be.false; + cy.apiDeviceAdd(deviceSource, group).then((deviceId) => { + cy.log("Created device in source group", deviceId); + // Now move this device from source group to destination + cy.apiDeviceReregisterAuthorized( + deviceSource, + deviceDest, + group2, + adminUser + ).then((response) => { + cy.log("Re-registered device", response.body.id); + expect(response.body.id).to.equal(originalDeviceId); + cy.apiDevice( + adminUser, + deviceDest, + true, + HttpStatusCode.Ok + ).then((response) => { + expect( + response.body.device.active, + "Dest device should now be active." + ).to.be.true; + }); + cy.log("Source device with no recordings should be deleted"); + cy.apiDeviceInGroup( + adminUser, + deviceSource, + group, + null, + {}, + HttpStatusCode.Forbidden + ); + cy.log("Device inherits recordings"); + cy.testCheckDeviceHasRecordings(adminUser, deviceDest, 1); + }); + }); + } + ); + }); + }); + } + ); + + it("To reassign a device, the user must be an admin for both the group being moved from and the destination group.", () => { + const deviceDest = "A007"; + const deviceSource = "A007"; + cy.apiDeviceAdd(deviceDest, group3).then((deviceId) => { + cy.log("Created device in destination group", deviceId); + cy.apiRecordingAdd( + deviceDest, + { type: RecordingType.ThermalRaw }, + "oneframe.cptv" + ).then((recordingId) => { + cy.log("Added recording to device", recordingId); + cy.log("Setting device inactive"); + cy.apiDeviceDeleteOrSetInactive(adminUser2, deviceDest, group3); + cy.apiDevice(adminUser2, deviceDest, true, HttpStatusCode.Ok).then( + (response) => { + expect(response.body.device.active, "Device should be inactive.").to + .be.false; + cy.apiDeviceAdd(deviceSource, group).then((deviceId) => { + cy.log("Created device in source group", deviceId); + // Now move this device from source group to destination + cy.apiDeviceReregisterAuthorized( + deviceSource, + deviceDest, + group3, + adminUser, + null, + HttpStatusCode.Forbidden + ); + }); + } + ); + }); + }); + }); +}); diff --git a/integration-tests/cypress/e2e/api/events/errors_query.ts b/integration-tests/cypress/e2e/api/batch-1/events/errors_query.ts similarity index 99% rename from integration-tests/cypress/e2e/api/events/errors_query.ts rename to integration-tests/cypress/e2e/api/batch-1/events/errors_query.ts index 56c89960f..b34dafbc2 100644 --- a/integration-tests/cypress/e2e/api/events/errors_query.ts +++ b/integration-tests/cypress/e2e/api/batch-1/events/errors_query.ts @@ -1,11 +1,9 @@ -/// - -import { getTestName } from "@/commands/names"; +import { getTestName } from "@commands/names"; import { ApiEventErrorSimilar, ApiEventError, ApiEventErrorCategory, -} from "@/commands/types"; +} from "@commands/types"; import { HttpStatusCode } from "@typedefs/api/consts"; // diff --git a/integration-tests/cypress/e2e/api/events/events.ts b/integration-tests/cypress/e2e/api/batch-1/events/events.ts similarity index 99% rename from integration-tests/cypress/e2e/api/events/events.ts rename to integration-tests/cypress/e2e/api/batch-1/events/events.ts index 9f1cfe74e..74f88dc01 100644 --- a/integration-tests/cypress/e2e/api/events/events.ts +++ b/integration-tests/cypress/e2e/api/batch-1/events/events.ts @@ -1,5 +1,3 @@ -/// - import { EventTypes } from "@commands/api/events"; import { getTestName } from "@commands/names"; import { diff --git a/integration-tests/cypress/e2e/api/events/events_on_behalf.ts b/integration-tests/cypress/e2e/api/batch-1/events/events_on_behalf.ts similarity index 99% rename from integration-tests/cypress/e2e/api/events/events_on_behalf.ts rename to integration-tests/cypress/e2e/api/batch-1/events/events_on_behalf.ts index d841fbe22..6a1b16f24 100644 --- a/integration-tests/cypress/e2e/api/events/events_on_behalf.ts +++ b/integration-tests/cypress/e2e/api/batch-1/events/events_on_behalf.ts @@ -1,5 +1,3 @@ -/// - import { EventTypes } from "@commands/api/events"; import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; diff --git a/integration-tests/cypress/e2e/api/events/events_query.ts b/integration-tests/cypress/e2e/api/batch-1/events/events_query.ts similarity index 99% rename from integration-tests/cypress/e2e/api/events/events_query.ts rename to integration-tests/cypress/e2e/api/batch-1/events/events_query.ts index 95fd44dc5..48326f6ce 100644 --- a/integration-tests/cypress/e2e/api/events/events_query.ts +++ b/integration-tests/cypress/e2e/api/batch-1/events/events_query.ts @@ -1,5 +1,3 @@ -/// - import { EventTypes } from "@commands/api/events"; import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; diff --git a/integration-tests/cypress/e2e/api/events/power_events_query.ts b/integration-tests/cypress/e2e/api/batch-1/events/power_events_query.ts similarity index 99% rename from integration-tests/cypress/e2e/api/events/power_events_query.ts rename to integration-tests/cypress/e2e/api/batch-1/events/power_events_query.ts index af7d67f80..d7c7fd482 100644 --- a/integration-tests/cypress/e2e/api/events/power_events_query.ts +++ b/integration-tests/cypress/e2e/api/batch-1/events/power_events_query.ts @@ -1,5 +1,3 @@ -/// -// // This test set verifies correct retrieval of power events // For generation of power events and alerts please see alerts/device_stopped.ts // diff --git a/integration-tests/cypress/e2e/api/groups/groups_add_get.ts b/integration-tests/cypress/e2e/api/batch-1/groups/groups_add_get.ts similarity index 96% rename from integration-tests/cypress/e2e/api/groups/groups_add_get.ts rename to integration-tests/cypress/e2e/api/batch-1/groups/groups_add_get.ts index 9aa04ce3f..f02c7a5e0 100644 --- a/integration-tests/cypress/e2e/api/groups/groups_add_get.ts +++ b/integration-tests/cypress/e2e/api/batch-1/groups/groups_add_get.ts @@ -1,5 +1,3 @@ -/// - import { ApiGroupReturned, ApiGroupUserRelation, @@ -43,7 +41,7 @@ describe("Groups - add, get group", () => { }; expectedGroupAdminUser = { id: getCreds("gaGroupAdmin").id, - username: getTestName("gaGroupAdmin"), + userName: getTestName("gaGroupAdmin"), GroupUsers: { admin: true, createdAt: "", @@ -54,7 +52,7 @@ describe("Groups - add, get group", () => { }; expectedGroupAdminGroupUser = { id: getCreds("gaGroupAdmin").id, - username: getTestName("gaGroupAdmin"), + userName: getTestName("gaGroupAdmin"), admin: true, }; } @@ -78,7 +76,7 @@ describe("Groups - add, get group", () => { ).then(() => { expectedGroupMemberUser = { id: getCreds("gaGroupMember").id, - username: getTestName("gaGroupMember"), + userName: getTestName("gaGroupMember"), GroupUsers: { admin: false, createdAt: "", @@ -89,7 +87,7 @@ describe("Groups - add, get group", () => { }; expectedGroupMemberGroupUser = { id: getCreds("gaGroupMember").id, - username: getTestName("gaGroupMember"), + userName: getTestName("gaGroupMember"), admin: false, }; }); diff --git a/integration-tests/cypress/e2e/api/groups/groups_devices.ts b/integration-tests/cypress/e2e/api/batch-1/groups/groups_devices.ts similarity index 94% rename from integration-tests/cypress/e2e/api/groups/groups_devices.ts rename to integration-tests/cypress/e2e/api/batch-1/groups/groups_devices.ts index b3e846929..7b56796bd 100644 --- a/integration-tests/cypress/e2e/api/groups/groups_devices.ts +++ b/integration-tests/cypress/e2e/api/batch-1/groups/groups_devices.ts @@ -1,11 +1,13 @@ -/// - import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; import { NOT_NULL_STRING } from "@commands/constants"; import ApiDeviceResponse = Cypress.ApiDeviceResponse; -import { DeviceType, HttpStatusCode } from "@typedefs/api/consts"; +import { + DeviceType, + HttpStatusCode, + RecordingType, +} from "@typedefs/api/consts"; describe("Groups - get devices for group", () => { const NOT_ADMIN = false; @@ -128,6 +130,12 @@ describe("Groups - get devices for group", () => { }); cy.log("Reregister the camera, making the old camera inactive"); + cy.apiRecordingAdd( + "gdCam4a", + { type: RecordingType.ThermalRaw }, + "oneframe.cptv", + "raRecording1" + ); cy.apiDeviceReregister("gdCam4a", "gdCam4b", "gdGroup4").then(() => { expectedGroupDevice4b = { id: getCreds("gdCam4b").id, @@ -137,7 +145,7 @@ describe("Groups - get devices for group", () => { groupId: getCreds("gdGroup4").id, active: true, admin: true, - type: DeviceType.Unknown, + type: DeviceType.Thermal, lastConnectionTime: NOT_NULL_STRING, isHealthy: true, }; @@ -149,7 +157,7 @@ describe("Groups - get devices for group", () => { groupId: getCreds("gdGroup4").id, active: true, admin: true, - type: DeviceType.Unknown, + type: DeviceType.Thermal, lastConnectionTime: NOT_NULL_STRING, isHealthy: true, }; diff --git a/integration-tests/cypress/e2e/api/groups/groups_invitations.ts b/integration-tests/cypress/e2e/api/batch-1/groups/groups_invitations.ts similarity index 100% rename from integration-tests/cypress/e2e/api/groups/groups_invitations.ts rename to integration-tests/cypress/e2e/api/batch-1/groups/groups_invitations.ts diff --git a/integration-tests/cypress/e2e/api/groups/groups_users_add_check_remove.ts b/integration-tests/cypress/e2e/api/batch-1/groups/groups_users_add_check_remove.ts similarity index 99% rename from integration-tests/cypress/e2e/api/groups/groups_users_add_check_remove.ts rename to integration-tests/cypress/e2e/api/batch-1/groups/groups_users_add_check_remove.ts index b6fe7b1fd..e9ee5f9e0 100644 --- a/integration-tests/cypress/e2e/api/groups/groups_users_add_check_remove.ts +++ b/integration-tests/cypress/e2e/api/batch-1/groups/groups_users_add_check_remove.ts @@ -1,5 +1,3 @@ -/// - import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; diff --git a/integration-tests/cypress/e2e/api/monitoring/monitor_errors.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitor_errors.ts similarity index 84% rename from integration-tests/cypress/e2e/api/monitoring/monitor_errors.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitor_errors.ts index 87ec30d8e..9fd445d38 100644 --- a/integration-tests/cypress/e2e/api/monitoring/monitor_errors.ts +++ b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitor_errors.ts @@ -1,5 +1,3 @@ -/// - describe("Monitoring : Errors and validation", () => { before(() => {}); diff --git a/integration-tests/cypress/e2e/api/monitoring/monitoring_ai_model.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_ai_model.ts similarity index 97% rename from integration-tests/cypress/e2e/api/monitoring/monitoring_ai_model.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_ai_model.ts index 04b415250..9d6e6ba42 100644 --- a/integration-tests/cypress/e2e/api/monitoring/monitoring_ai_model.ts +++ b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_ai_model.ts @@ -1,5 +1,3 @@ -/// - import { checkRecording } from "@commands/api/recording-tests"; describe("Monitoring : evaluate ai model", () => { diff --git a/integration-tests/cypress/e2e/api/monitoring/monitoring_authorisation.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_authorisation.ts similarity index 97% rename from integration-tests/cypress/e2e/api/monitoring/monitoring_authorisation.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_authorisation.ts index 7ccb617b1..88bcc681b 100644 --- a/integration-tests/cypress/e2e/api/monitoring/monitoring_authorisation.ts +++ b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_authorisation.ts @@ -1,5 +1,3 @@ -/// - import { logTestDescription, NO_LOG_MESSAGE } from "@commands/descriptions"; import { checkRecording } from "@commands/api/recording-tests"; import { StationId } from "@typedefs/api/common"; diff --git a/integration-tests/cypress/e2e/api/monitoring/monitoring_cameras.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_cameras.ts similarity index 100% rename from integration-tests/cypress/e2e/api/monitoring/monitoring_cameras.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_cameras.ts diff --git a/integration-tests/cypress/e2e/api/monitoring/monitoring_filters.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_filters.ts similarity index 100% rename from integration-tests/cypress/e2e/api/monitoring/monitoring_filters.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_filters.ts diff --git a/integration-tests/cypress/e2e/api/monitoring/monitoring_paging.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_paging.ts similarity index 98% rename from integration-tests/cypress/e2e/api/monitoring/monitoring_paging.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_paging.ts index 40ba963d4..63841c942 100644 --- a/integration-tests/cypress/e2e/api/monitoring/monitoring_paging.ts +++ b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_paging.ts @@ -1,5 +1,3 @@ -/// - import { checkRecording } from "@commands/api/recording-tests"; describe("Monitoring : pagings", () => { diff --git a/integration-tests/cypress/e2e/api/monitoring/monitoring_tags.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_tags.ts similarity index 99% rename from integration-tests/cypress/e2e/api/monitoring/monitoring_tags.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_tags.ts index 379842183..6a0bfee80 100644 --- a/integration-tests/cypress/e2e/api/monitoring/monitoring_tags.ts +++ b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_tags.ts @@ -1,5 +1,3 @@ -/// - import { checkRecording } from "@commands/api/recording-tests"; describe("Monitoring : tracks and tags", () => { diff --git a/integration-tests/cypress/e2e/api/monitoring/monitoring_times.ts b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_times.ts similarity index 99% rename from integration-tests/cypress/e2e/api/monitoring/monitoring_times.ts rename to integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_times.ts index f4e6e3638..9c4a15e4a 100644 --- a/integration-tests/cypress/e2e/api/monitoring/monitoring_times.ts +++ b/integration-tests/cypress/e2e/api/batch-1/monitoring/monitoring_times.ts @@ -1,5 +1,3 @@ -/// - import { addSeconds, checkRecording } from "@commands/api/recording-tests"; describe("Monitoring : times and recording groupings", () => { diff --git a/integration-tests/cypress/e2e/api/stations/fix_location-move.ts b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-move.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/fix_location-move.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/fix_location-move.ts index c8acb8823..ecceddbcd 100644 --- a/integration-tests/cypress/e2e/api/stations/fix_location-move.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-move.ts @@ -1,4 +1,3 @@ -/// import { TestGetLocation } from "@commands/api/station"; import { TestCreateExpectedHistoryEntry } from "@commands/api/device"; import { checkRecording } from "@commands/api/recording-tests"; @@ -22,6 +21,7 @@ const templateExpectedStation = { name: NOT_NULL_STRING, id: NOT_NULL, lastThermalRecordingTime: NOT_NULL_STRING, + // lastActiveThermalTime: NOT_NULL_STRING, createdAt: NOT_NULL_STRING, updatedAt: NOT_NULL_STRING, activeAt: NOT_NULL_STRING, diff --git a/integration-tests/cypress/e2e/api/stations/fix_location-move_and_recordings.ts b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-move_and_recordings.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/fix_location-move_and_recordings.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/fix_location-move_and_recordings.ts index 3f4573b1b..283627dbb 100644 --- a/integration-tests/cypress/e2e/api/stations/fix_location-move_and_recordings.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-move_and_recordings.ts @@ -1,4 +1,3 @@ -/// import { TestGetLocation } from "@commands/api/station"; import { ApiStationResponse } from "@typedefs/api/station"; import { diff --git a/integration-tests/cypress/e2e/api/stations/fix_location-reassign.ts b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-reassign.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/fix_location-reassign.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/fix_location-reassign.ts index 7171a9bf7..24b5f5218 100644 --- a/integration-tests/cypress/e2e/api/stations/fix_location-reassign.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-reassign.ts @@ -1,4 +1,3 @@ -/// import { TestGetLocation } from "@commands/api/station"; import { TestCreateExpectedHistoryEntry } from "@commands/api/device"; import { checkRecording } from "@commands/api/recording-tests"; diff --git a/integration-tests/cypress/e2e/api/stations/fix_location-reassign_and_recordings.ts b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-reassign_and_recordings.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/fix_location-reassign_and_recordings.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/fix_location-reassign_and_recordings.ts index 9efeea388..37202323e 100644 --- a/integration-tests/cypress/e2e/api/stations/fix_location-reassign_and_recordings.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/fix_location-reassign_and_recordings.ts @@ -1,4 +1,3 @@ -/// import { TestGetLocation } from "@commands/api/station"; import { ApiStationResponse } from "@typedefs/api/station"; import { diff --git a/integration-tests/cypress/e2e/api/stations/group_station_add.ts b/integration-tests/cypress/e2e/api/batch-1/stations/group_station_add.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/group_station_add.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/group_station_add.ts index 3f92d76f7..f231fbec3 100644 --- a/integration-tests/cypress/e2e/api/stations/group_station_add.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/group_station_add.ts @@ -1,4 +1,3 @@ -/// import { ApiStationResponse } from "@typedefs/api/station"; import { getCreds } from "@commands/server"; import { getTestName } from "@commands/names"; diff --git a/integration-tests/cypress/e2e/api/stations/station_permissions.ts b/integration-tests/cypress/e2e/api/batch-1/stations/station_permissions.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/station_permissions.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/station_permissions.ts index 46a96ef25..bba149cfa 100644 --- a/integration-tests/cypress/e2e/api/stations/station_permissions.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/station_permissions.ts @@ -1,4 +1,3 @@ -/// import { ApiStationResponse } from "@typedefs/api/station"; import { getCreds } from "@commands/server"; import { getTestName } from "@commands/names"; diff --git a/integration-tests/cypress/e2e/api/stations/station_recordings.ts b/integration-tests/cypress/e2e/api/batch-1/stations/station_recordings.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/station_recordings.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/station_recordings.ts index 28ffe4fdd..d8cc8ce12 100644 --- a/integration-tests/cypress/e2e/api/stations/station_recordings.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/station_recordings.ts @@ -1,4 +1,3 @@ -/// import { TestCreateRecordingData } from "@commands/api/recording-tests"; import { TestGetLocation } from "@commands/api/station"; import { TestCreateExpectedHistoryEntry } from "@commands/api/device"; diff --git a/integration-tests/cypress/e2e/api/stations/station_update.ts b/integration-tests/cypress/e2e/api/batch-1/stations/station_update.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/station_update.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/station_update.ts index 5b71a3014..39eac3a9e 100644 --- a/integration-tests/cypress/e2e/api/stations/station_update.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/station_update.ts @@ -1,4 +1,3 @@ -/// import { ApiStationResponse } from "@typedefs/api/station"; import { getCreds } from "@commands/server"; import { getTestName } from "@commands/names"; diff --git a/integration-tests/cypress/e2e/api/stations/station_updates_and_recordings.ts b/integration-tests/cypress/e2e/api/batch-1/stations/station_updates_and_recordings.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/station_updates_and_recordings.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/station_updates_and_recordings.ts index 7832cc3af..468d66c2c 100644 --- a/integration-tests/cypress/e2e/api/stations/station_updates_and_recordings.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/station_updates_and_recordings.ts @@ -1,4 +1,3 @@ -/// import { TestCreateExpectedRecordingData, TestCreateRecordingData, diff --git a/integration-tests/cypress/e2e/api/stations/station_use_cases.ts b/integration-tests/cypress/e2e/api/batch-1/stations/station_use_cases.ts similarity index 99% rename from integration-tests/cypress/e2e/api/stations/station_use_cases.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/station_use_cases.ts index 2c9cc5d16..e8553262c 100644 --- a/integration-tests/cypress/e2e/api/stations/station_use_cases.ts +++ b/integration-tests/cypress/e2e/api/batch-1/stations/station_use_cases.ts @@ -1,4 +1,3 @@ -/// import { TestCreateExpectedRecordingData, TestCreateRecordingData, diff --git a/integration-tests/cypress/e2e/api/stations/test-list.todo.ts b/integration-tests/cypress/e2e/api/batch-1/stations/test-list.todo.ts similarity index 100% rename from integration-tests/cypress/e2e/api/stations/test-list.todo.ts rename to integration-tests/cypress/e2e/api/batch-1/stations/test-list.todo.ts diff --git a/integration-tests/cypress/e2e/api/zz-teardown/teardown.ts b/integration-tests/cypress/e2e/api/batch-1/zz-teardown/teardown.ts similarity index 92% rename from integration-tests/cypress/e2e/api/zz-teardown/teardown.ts rename to integration-tests/cypress/e2e/api/batch-1/zz-teardown/teardown.ts index beb03057b..91d6a161f 100644 --- a/integration-tests/cypress/e2e/api/zz-teardown/teardown.ts +++ b/integration-tests/cypress/e2e/api/batch-1/zz-teardown/teardown.ts @@ -1,4 +1,3 @@ -/// import { testRunOnApi } from "@commands/server"; describe("Teardown test data", () => { diff --git a/integration-tests/cypress/e2e/api/alerts/device_alerts.ts b/integration-tests/cypress/e2e/api/batch-2/alerts/device_alerts.ts similarity index 99% rename from integration-tests/cypress/e2e/api/alerts/device_alerts.ts rename to integration-tests/cypress/e2e/api/batch-2/alerts/device_alerts.ts index 9e3410de7..4ddcc4792 100644 --- a/integration-tests/cypress/e2e/api/alerts/device_alerts.ts +++ b/integration-tests/cypress/e2e/api/batch-2/alerts/device_alerts.ts @@ -1,4 +1,3 @@ -/// import { checkResponse } from "@commands/server"; import { getNewIdentity } from "@commands/names"; import { ApiAlertCondition } from "@typedefs/api/alerts"; diff --git a/integration-tests/cypress/e2e/api/alerts/device_stopped.ts b/integration-tests/cypress/e2e/api/batch-2/alerts/device_stopped.ts similarity index 99% rename from integration-tests/cypress/e2e/api/alerts/device_stopped.ts rename to integration-tests/cypress/e2e/api/batch-2/alerts/device_stopped.ts index b6b808649..ff106d0e3 100644 --- a/integration-tests/cypress/e2e/api/alerts/device_stopped.ts +++ b/integration-tests/cypress/e2e/api/batch-2/alerts/device_stopped.ts @@ -1,4 +1,3 @@ -/// import moment from "moment"; import { EventTypes, testCreateExpectedEvent } from "@commands/api/events"; import { runReportStoppedDevicesScript } from "@commands/api/alerts"; diff --git a/integration-tests/cypress/e2e/api/alerts/station_alerts.ts b/integration-tests/cypress/e2e/api/batch-2/alerts/station_alerts.ts similarity index 99% rename from integration-tests/cypress/e2e/api/alerts/station_alerts.ts rename to integration-tests/cypress/e2e/api/batch-2/alerts/station_alerts.ts index f11e8ebd5..9ae95a75c 100644 --- a/integration-tests/cypress/e2e/api/alerts/station_alerts.ts +++ b/integration-tests/cypress/e2e/api/batch-2/alerts/station_alerts.ts @@ -1,4 +1,3 @@ -/// import { checkResponse } from "@commands/server"; import { getNewIdentity } from "@commands/names"; import { ApiAlertCondition } from "@typedefs/api/alerts"; diff --git a/integration-tests/cypress/e2e/api/recordings/audio_recordings.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/audio_recordings.ts similarity index 97% rename from integration-tests/cypress/e2e/api/recordings/audio_recordings.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/audio_recordings.ts index 7d13d9060..9a0c28567 100644 --- a/integration-tests/cypress/e2e/api/recordings/audio_recordings.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/audio_recordings.ts @@ -1,4 +1,3 @@ -/// import { EXCLUDE_IDS } from "@commands/constants"; import { ApiLocation, ApiRecordingSet } from "@commands/types"; @@ -79,6 +78,39 @@ describe("Recordings - audio recording parameter tests", () => { ); }); + it("Can upload an audio recording - as if from tc2 device", () => { + const recording1 = TestCreateRecordingData(templateRecording); + let expectedRecording1: ApiAudioRecordingResponse; + + cy.log("Add recording as device"); + cy.apiRecordingAdd( + "rarDevice1", + recording1, + "60sec-audio.m4a", + "rarRecording1", + 200, + {}, + "file" // + ).then(() => { + expectedRecording1 = TestCreateExpectedRecordingData( + templateExpectedRecording, + "rarRecording1", + "rarDevice1", + "rarGroup", + null, + recording1 + ); + cy.log("Check recording can be viewed correctly"); + cy.apiRecordingCheck( + "rarGroupAdmin", + "rarRecording1", + expectedRecording1, + EXCLUDE_IDS + ); + cy.apiRecordingDownloadCheck("rarGroupAdmin", "rarRecording1"); + }); + }); + it("Duration handled correctly", () => { cy.log("Uses supplied duration"); const recording2 = TestCreateRecordingData(templateRecording); diff --git a/integration-tests/cypress/e2e/api/recordings/recording_add_get_delete.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_add_get_delete.ts similarity index 91% rename from integration-tests/cypress/e2e/api/recordings/recording_add_get_delete.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_add_get_delete.ts index a198e6ab6..a1e4c918a 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_add_get_delete.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_add_get_delete.ts @@ -1,4 +1,3 @@ -/// import { EXCLUDE_IDS } from "@commands/constants"; import { ApiRecordingSet } from "@commands/types"; @@ -43,7 +42,7 @@ describe("Recordings (thermal): add, get, delete", () => { cy.testCreateUserGroupAndDevice("raGroup2Admin", "raGroup2", "raCamera2"); }); - it.only("Group admin can view and delete device's recordings", () => { + it("Group admin can view and delete device's recordings", () => { const recording1 = TestCreateRecordingData(templateRecording); let expectedRecording1: ApiThermalRecordingResponse; @@ -331,20 +330,88 @@ describe("Recordings (thermal): add, get, delete", () => { const recording1 = TestCreateRecordingData(templateRecording); let expectedRecording1: ApiThermalRecordingResponse; - cy.log("Rename/reregister device"); - cy.apiDeviceReregister("raCamera1", "raCamera1-renamed", "raGroup").then( - () => { + cy.log( + "Add a recording to device to be re-registered so that the old device is set inactive, not deleted" + ); + cy.apiRecordingAddOnBehalfUsingDevice( + "raGroupAdmin", + "raCamera1", + recording1, + "raRecording1" + ).then(() => { + cy.log("Rename/reregister device"); + cy.apiDeviceReregister("raCamera1", "raCamera1-renamed", "raGroup").then( + () => { + cy.log("Add recording as group member"); + cy.apiRecordingAddOnBehalfUsingDevice( + "raGroupAdmin", + "raCamera1", + recording1, + "raRecording1" + ).then(() => { + expectedRecording1 = TestCreateExpectedRecordingData( + templateExpectedRecording, + "raRecording1", + "raCamera1", + "raGroup", + null, + recording1 + ); + cy.log("Check recording can be viewed correctly"); + cy.apiRecordingCheck( + "raGroupMember", + "raRecording1", + expectedRecording1, + EXCLUDE_IDS + ); + }); + + cy.log("Delete recording"); + cy.apiRecordingDelete("raGroupMember", "raRecording1"); + + cy.log("Check recording no longer exists"); + cy.apiRecordingCheck( + "raGroupMember", + "raRecording1", + undefined, + [], + HttpStatusCode.Forbidden + ); + } + ); + }); + }); + + it("Group member can add recordings by device on behalf - for inactive device", () => { + // NOTE: This test requires the previous test to also pass. + const recording1 = TestCreateRecordingData(templateRecording); + let expectedRecording1: ApiThermalRecordingResponse; + cy.log( + "Add a recording to device to be re-registered so that the old device is set inactive, not deleted" + ); + cy.apiRecordingAddOnBehalfUsingDevice( + "raGroupAdmin", + "raCamera1-renamed", + recording1, + "raRecording1" + ).then(() => { + cy.log("Rename/reregister device"); + cy.apiDeviceReregister( + "raCamera1-renamed", + "raCamera1-renamed2", + "raGroup" + ).then(() => { cy.log("Add recording as group member"); cy.apiRecordingAddOnBehalfUsingDevice( - "raGroupAdmin", - "raCamera1", + "raGroupMember", + "raCamera1-renamed", recording1, "raRecording1" ).then(() => { expectedRecording1 = TestCreateExpectedRecordingData( templateExpectedRecording, "raRecording1", - "raCamera1", + "raCamera1-renamed", "raGroup", null, recording1 @@ -369,55 +436,7 @@ describe("Recordings (thermal): add, get, delete", () => { [], HttpStatusCode.Forbidden ); - } - ); - }); - - it("Group member can add recordings by device on behalf - for inactive device", () => { - // NOTE: This test requires the previous test to also pass. - const recording1 = TestCreateRecordingData(templateRecording); - let expectedRecording1: ApiThermalRecordingResponse; - cy.log("Rename/reregister device"); - cy.apiDeviceReregister( - "raCamera1-renamed", - "raCamera1-renamed2", - "raGroup" - ).then(() => { - cy.log("Add recording as group member"); - cy.apiRecordingAddOnBehalfUsingDevice( - "raGroupMember", - "raCamera1-renamed", - recording1, - "raRecording1" - ).then(() => { - expectedRecording1 = TestCreateExpectedRecordingData( - templateExpectedRecording, - "raRecording1", - "raCamera1-renamed", - "raGroup", - null, - recording1 - ); - cy.log("Check recording can be viewed correctly"); - cy.apiRecordingCheck( - "raGroupMember", - "raRecording1", - expectedRecording1, - EXCLUDE_IDS - ); }); - - cy.log("Delete recording"); - cy.apiRecordingDelete("raGroupMember", "raRecording1"); - - cy.log("Check recording no longer exists"); - cy.apiRecordingCheck( - "raGroupMember", - "raRecording1", - undefined, - [], - HttpStatusCode.Forbidden - ); }); }); diff --git a/integration-tests/cypress/e2e/api/recordings/recording_bulk_delete_undelete.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_bulk_delete_undelete.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_bulk_delete_undelete.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_bulk_delete_undelete.ts index ab888ac19..ceafa18aa 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_bulk_delete_undelete.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_bulk_delete_undelete.ts @@ -1,5 +1,4 @@ import { TEMPLATE_AUDIO_RECORDING } from "@commands/dataTemplate"; -/// import { EXCLUDE_IDS_ARRAY } from "@commands/constants"; import { diff --git a/integration-tests/cypress/e2e/api/recordings/recording_filtering.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_filtering.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_filtering.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_filtering.ts index ac3c1f38f..be6602806 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_filtering.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_filtering.ts @@ -1,5 +1,3 @@ -/// - import { ApiRecordingSet, ApiTrackSet, diff --git a/integration-tests/cypress/e2e/api/recordings/recording_needs_tag.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_needs_tag.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_needs_tag.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_needs_tag.ts index 49752c2cb..3561dadc8 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_needs_tag.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_needs_tag.ts @@ -1,5 +1,3 @@ -/// - import { ApiRecordingNeedsTagReturned, ApiRecordingSet } from "@commands/types"; import { getCreds } from "@commands/server"; diff --git a/integration-tests/cypress/e2e/api/recordings/recording_parameters.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_parameters.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_parameters.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_parameters.ts index 89e36b8a8..2d0692471 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_parameters.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_parameters.ts @@ -1,4 +1,3 @@ -/// import { EXCLUDE_IDS } from "@commands/constants"; import { @@ -259,7 +258,7 @@ describe("Recordings - parameter tests", () => { null, recording1 ); - expectedRecording1.duration = 0.555555555555556; + expectedRecording1.duration = 0.4444444444444444; cy.apiRecordingCheck( "rpaGroupAdmin", "rpaRecording35", @@ -987,7 +986,7 @@ describe("Recordings - parameter tests", () => { ); expectedRecording4.additionalMetadata = { previewSecs: 5, - totalFrames: 5, + totalFrames: 4, }; cy.apiRecordingCheck( "rpaGroupAdmin", diff --git a/integration-tests/cypress/e2e/api/recordings/recording_processing.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_processing.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_processing.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_processing.ts index a9594b363..9a312a253 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_processing.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_processing.ts @@ -1,4 +1,3 @@ -/// import { NOT_NULL_STRING, EXCLUDE_IDS } from "@commands/constants"; import { TEMPLATE_AUDIO_RECORDING_RESPONSE, @@ -1133,7 +1132,7 @@ describe("Recordings - processing tests", () => { newField: "newValue", newField2: "newValue2", algorithm: 99999, - totalFrames: 5, + totalFrames: 4, previewSecs: null, } as any; diff --git a/integration-tests/cypress/e2e/api/recordings/recording_query.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_query.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_query.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_query.ts index 9b7099e39..6b3823b3a 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_query.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_query.ts @@ -1,4 +1,3 @@ -/// import { EXCLUDE_IDS_ARRAY } from "@commands/constants"; import { TEMPLATE_AUDIO_RECORDING_RESPONSE, diff --git a/integration-tests/cypress/e2e/api/recordings/recording_query_v2.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_query_v2.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_query_v2.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_query_v2.ts index 5524ec046..4a196c8bf 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_query_v2.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_query_v2.ts @@ -1,4 +1,3 @@ -/// import { EXCLUDE_IDS_ARRAY, NOT_NULL } from "@commands/constants"; import { TEMPLATE_AUDIO_RECORDING_RESPONSE, diff --git a/integration-tests/cypress/e2e/api/recordings/recording_report.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_report.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_report.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_report.ts index 249b6aae3..a08d39b1f 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_report.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_report.ts @@ -1,5 +1,3 @@ -/// - import { HttpStatusCode, RecordingProcessingState, diff --git a/integration-tests/cypress/e2e/api/recordings/recording_reprocess.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_reprocess.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_reprocess.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_reprocess.ts index 92c849b75..9c3512bc1 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_reprocess.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_reprocess.ts @@ -1,4 +1,3 @@ -/// import { NOT_NULL_STRING, EXCLUDE_IDS } from "@commands/constants"; import { getCreds } from "@commands/server"; diff --git a/integration-tests/cypress/e2e/api/recordings/recording_softdelete_undelete.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_softdelete_undelete.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_softdelete_undelete.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_softdelete_undelete.ts index 626aec01e..17403850f 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_softdelete_undelete.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_softdelete_undelete.ts @@ -1,4 +1,3 @@ -/// import { EXCLUDE_IDS_ARRAY } from "@commands/constants"; import { diff --git a/integration-tests/cypress/e2e/api/recordings/recording_tag.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_tag.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_tag.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_tag.ts index 1e6c8842f..4ec4b1488 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_tag.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_tag.ts @@ -1,4 +1,3 @@ -/// import { NOT_NULL_STRING } from "@commands/constants"; import { ApiRecordingSet } from "@commands/types"; diff --git a/integration-tests/cypress/e2e/api/batch-2/recordings/recording_tc2_audio.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_tc2_audio.ts new file mode 100644 index 000000000..aefdc0eb3 --- /dev/null +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_tc2_audio.ts @@ -0,0 +1,61 @@ +import { EXCLUDE_IDS } from "@commands/constants"; +import { ApiRecordingSet } from "@commands/types"; + +import { + TestCreateExpectedRecordingData, + TestCreateRecordingData, +} from "@commands/api/recording-tests"; +import { ApiAudioRecordingResponse } from "@typedefs/api/recording"; +import { + TEMPLATE_AUDIO_RECORDING_RESPONSE_TC2, + TEMPLATE_AUDIO_RECORDING_TC2, +} from "@commands/dataTemplate"; +import { RecordingType } from "@typedefs/api/consts"; + +describe("Recordings - audio recording parameter tests", () => { + const templateExpectedRecording: ApiAudioRecordingResponse = JSON.parse( + JSON.stringify(TEMPLATE_AUDIO_RECORDING_RESPONSE_TC2) + ); + const templateRecording: ApiRecordingSet = JSON.parse( + JSON.stringify(TEMPLATE_AUDIO_RECORDING_TC2) + ); + + const adminUser = "tc2AudioGroupAdmin"; + const group = "tc2AudioGroup"; + const device = "tc2AudioDevice"; + + before(() => { + //Create group1 with Admin, Member and 2 devices + cy.testCreateUserGroupAndDevice(adminUser, group, device); + }); + + it("Can upload an audio recording and use embedded metadata", () => { + const recording1 = TestCreateRecordingData(templateRecording); + let expectedRecording1: ApiAudioRecordingResponse; + recording1.duration = 20.000912; + recording1.location = [-43.601032, 172.71317]; + cy.log("Add recording as device"); + cy.apiRecordingAdd( + device, + { type: RecordingType.Audio }, + "embedded-metadata-tc2.aac", + "rarRecording1" + ).then(() => { + expectedRecording1 = TestCreateExpectedRecordingData( + templateExpectedRecording, + "rarRecording1", + device, + group, + null, + recording1 + ); + cy.log("Check recording can be viewed correctly"); + cy.apiRecordingCheck( + adminUser, + "rarRecording1", + expectedRecording1, + EXCLUDE_IDS + ); + }); + }); +}); diff --git a/integration-tests/cypress/e2e/api/recordings/recording_thumbnail.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_thumbnail.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_thumbnail.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_thumbnail.ts index 12fcfb235..84bea6cf2 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_thumbnail.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_thumbnail.ts @@ -1,5 +1,3 @@ -/// - import { ApiRecordingSet, ApiRecordingForProcessing } from "@commands/types"; import { diff --git a/integration-tests/cypress/e2e/api/recordings/recording_update.ts b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_update.ts similarity index 99% rename from integration-tests/cypress/e2e/api/recordings/recording_update.ts rename to integration-tests/cypress/e2e/api/batch-2/recordings/recording_update.ts index 82b7aa348..e621cd3e4 100644 --- a/integration-tests/cypress/e2e/api/recordings/recording_update.ts +++ b/integration-tests/cypress/e2e/api/batch-2/recordings/recording_update.ts @@ -1,5 +1,3 @@ -/// - import { ApiRecordingSet } from "@commands/types"; import { diff --git a/integration-tests/cypress/e2e/api/tracks/track.ts b/integration-tests/cypress/e2e/api/batch-2/tracks/track.ts similarity index 99% rename from integration-tests/cypress/e2e/api/tracks/track.ts rename to integration-tests/cypress/e2e/api/batch-2/tracks/track.ts index f007d1eec..58a66dc4d 100644 --- a/integration-tests/cypress/e2e/api/tracks/track.ts +++ b/integration-tests/cypress/e2e/api/batch-2/tracks/track.ts @@ -1,4 +1,3 @@ -/// import { NOT_NULL_STRING } from "@commands/constants"; import { ApiRecordingSet } from "@commands/types"; diff --git a/integration-tests/cypress/e2e/api/tracks/track_tag_add.ts b/integration-tests/cypress/e2e/api/batch-2/tracks/track_tag_add.ts similarity index 99% rename from integration-tests/cypress/e2e/api/tracks/track_tag_add.ts rename to integration-tests/cypress/e2e/api/batch-2/tracks/track_tag_add.ts index c911c91c9..4f919a83b 100644 --- a/integration-tests/cypress/e2e/api/tracks/track_tag_add.ts +++ b/integration-tests/cypress/e2e/api/batch-2/tracks/track_tag_add.ts @@ -1,4 +1,3 @@ -/// import { NOT_NULL_STRING, NOT_NULL } from "@commands/constants"; import { ApiRecordingSet } from "@commands/types"; diff --git a/integration-tests/cypress/e2e/api/tracks/track_tag_replace.ts b/integration-tests/cypress/e2e/api/batch-2/tracks/track_tag_replace.ts similarity index 99% rename from integration-tests/cypress/e2e/api/tracks/track_tag_replace.ts rename to integration-tests/cypress/e2e/api/batch-2/tracks/track_tag_replace.ts index 4bd656f8b..ace1aafdd 100644 --- a/integration-tests/cypress/e2e/api/tracks/track_tag_replace.ts +++ b/integration-tests/cypress/e2e/api/batch-2/tracks/track_tag_replace.ts @@ -1,4 +1,3 @@ -/// import { NOT_NULL_STRING, NOT_NULL } from "@commands/constants"; import { ApiRecordingSet } from "@commands/types"; diff --git a/integration-tests/cypress/e2e/api/transactional-emails/transactional_emails.ts b/integration-tests/cypress/e2e/api/batch-2/transactional-emails/transactional_emails.ts similarity index 100% rename from integration-tests/cypress/e2e/api/transactional-emails/transactional_emails.ts rename to integration-tests/cypress/e2e/api/batch-2/transactional-emails/transactional_emails.ts diff --git a/integration-tests/cypress/e2e/api/users-admin/admin_global_user.ts b/integration-tests/cypress/e2e/api/batch-2/users-admin/admin_global_user.ts similarity index 99% rename from integration-tests/cypress/e2e/api/users-admin/admin_global_user.ts rename to integration-tests/cypress/e2e/api/batch-2/users-admin/admin_global_user.ts index af758b6b9..9620c600e 100644 --- a/integration-tests/cypress/e2e/api/users-admin/admin_global_user.ts +++ b/integration-tests/cypress/e2e/api/batch-2/users-admin/admin_global_user.ts @@ -1,5 +1,3 @@ -/// - import { TestCreateExpectedUser } from "@commands/api/user"; import { getTestEmail, getTestName } from "@commands/names"; diff --git a/integration-tests/cypress/e2e/api/users-admin/end_user_agreement.ts b/integration-tests/cypress/e2e/api/batch-2/users-admin/end_user_agreement.ts similarity index 83% rename from integration-tests/cypress/e2e/api/users-admin/end_user_agreement.ts rename to integration-tests/cypress/e2e/api/batch-2/users-admin/end_user_agreement.ts index a0d804639..8d9b7e1b5 100644 --- a/integration-tests/cypress/e2e/api/users-admin/end_user_agreement.ts +++ b/integration-tests/cypress/e2e/api/batch-2/users-admin/end_user_agreement.ts @@ -1,4 +1,3 @@ -/// import { LATEST_END_USER_AGREEMENT } from "@commands/constants"; describe("User: end user agreement", () => { diff --git a/integration-tests/cypress/e2e/api/users-admin/user_add_get.ts b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_add_get.ts similarity index 99% rename from integration-tests/cypress/e2e/api/users-admin/user_add_get.ts rename to integration-tests/cypress/e2e/api/batch-2/users-admin/user_add_get.ts index c221ec81f..d9c28d6d3 100644 --- a/integration-tests/cypress/e2e/api/users-admin/user_add_get.ts +++ b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_add_get.ts @@ -1,4 +1,3 @@ -/// import { LATEST_END_USER_AGREEMENT } from "@commands/constants"; import { TestCreateExpectedUser } from "@commands/api/user"; diff --git a/integration-tests/cypress/e2e/api/users-admin/user_list.ts b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_list.ts similarity index 97% rename from integration-tests/cypress/e2e/api/users-admin/user_list.ts rename to integration-tests/cypress/e2e/api/batch-2/users-admin/user_list.ts index d04471540..3dfe2963c 100644 --- a/integration-tests/cypress/e2e/api/users-admin/user_list.ts +++ b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_list.ts @@ -1,5 +1,3 @@ -/// - import { getTestName } from "@commands/names"; import { getCreds } from "@commands/server"; diff --git a/integration-tests/cypress/e2e/api/users-admin/user_password_reset.ts b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_password_reset.ts similarity index 97% rename from integration-tests/cypress/e2e/api/users-admin/user_password_reset.ts rename to integration-tests/cypress/e2e/api/batch-2/users-admin/user_password_reset.ts index bdfa5eed9..7da9600dc 100644 --- a/integration-tests/cypress/e2e/api/users-admin/user_password_reset.ts +++ b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_password_reset.ts @@ -1,5 +1,3 @@ -/// - import { getTestEmail, getTestName } from "@commands/names"; import { HttpStatusCode } from "@typedefs/api/consts"; import { diff --git a/integration-tests/cypress/e2e/api/users-admin/user_update.ts b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_update.ts similarity index 99% rename from integration-tests/cypress/e2e/api/users-admin/user_update.ts rename to integration-tests/cypress/e2e/api/batch-2/users-admin/user_update.ts index 9e9840a27..4fbf4967a 100644 --- a/integration-tests/cypress/e2e/api/users-admin/user_update.ts +++ b/integration-tests/cypress/e2e/api/batch-2/users-admin/user_update.ts @@ -1,5 +1,3 @@ -/// - import { TestCreateExpectedUser } from "@commands/api/user"; import { getTestEmail, getTestName } from "@commands/names"; diff --git a/integration-tests/cypress/e2e/api/batch-2/zz-teardown/teardown.ts b/integration-tests/cypress/e2e/api/batch-2/zz-teardown/teardown.ts new file mode 100644 index 000000000..91d6a161f --- /dev/null +++ b/integration-tests/cypress/e2e/api/batch-2/zz-teardown/teardown.ts @@ -0,0 +1,19 @@ +import { testRunOnApi } from "@commands/server"; + +describe("Teardown test data", () => { + before(() => {}); + + it.skip("Remove test data from database", () => { + if (Cypress.env("running_in_a_dev_environment") == true) { + // do not error in dev as we don't want a failed teardown to fail a build in travis + testRunOnApi( + "sudo -u postgres psql -d cacophonytest -f /app/api/scripts/deleteTestData.sql", + { failOnNonZeroExit: false } + ); + } else { + testRunOnApi( + "psql -U user10 -d cacodb -f /srv/cacophony/api/scripts/deleteTestData.sql" + ); + } + }); +}); diff --git a/integration-tests/cypress/e2e/browse-next/signup/new-user-signup.ts b/integration-tests/cypress/e2e/browse-next/signup/new-user-signup.ts index 8d471c789..cfd81d4c9 100644 --- a/integration-tests/cypress/e2e/browse-next/signup/new-user-signup.ts +++ b/integration-tests/cypress/e2e/browse-next/signup/new-user-signup.ts @@ -90,7 +90,7 @@ describe("New users can sign up and confirm their email address", () => { startMailServerStub(); }); - it.only("Existing user (with projects) is able to request to join an existing project from main view", () => { + it("Existing user (with projects) is able to request to join an existing project from main view", () => { cy.log("User 1 creates a project"); const user1 = uniqueName("Bob"); const password = uniqueName("pass"); diff --git a/integration-tests/cypress/fixtures/embedded-metadata-tc2.aac b/integration-tests/cypress/fixtures/embedded-metadata-tc2.aac new file mode 100644 index 000000000..e6d4c6d8e Binary files /dev/null and b/integration-tests/cypress/fixtures/embedded-metadata-tc2.aac differ diff --git a/integration-tests/cypress/performance/monitoring_performance.ts b/integration-tests/cypress/performance/monitoring_performance.ts deleted file mode 100644 index 05c8df117..000000000 --- a/integration-tests/cypress/performance/monitoring_performance.ts +++ /dev/null @@ -1,116 +0,0 @@ -/// - -describe("Monitoring : times and recording groupings", () => { - const Dexter = "Dexter"; - const group = "Monitoring_visits"; - const max_page_length = 100; - - before(() => { - cy.testCreateUserAndGroup(Dexter, group); - }); - - it("can handle maximum number of visits per page", () => { - const camera = "visits-per-page"; - const visits = []; - // add 1000 recordings - cy.apiDeviceAdd(camera, group); - cy.apiRecordingAdd(camera, { time: "20:55", duration: 10 }); - for (let i = 0; i < max_page_length * 10 - 1; i++) { - cy.apiRecordingAdd(camera, { minsLater: 11 }); - } - - for (let i = 0; i < max_page_length; i++) { - visits.push({ recordings: 1 }); - } - - const filter = { "page-size": max_page_length, page: 1 }; - // check first page - const t1 = performance.now(); - cy.checkMonitoringWithFilter(Dexter, camera, filter, visits); - const t2 = performance.now(); - cy.log(`Page 1 load duration: ${t2 - t1} ms`); - // check last page - const filter10 = { "page-size": max_page_length, page: 10 }; - cy.checkMonitoringWithFilter(Dexter, camera, filter10, visits); - const t3 = performance.now(); - cy.log(`Page 10 load duration: ${t3 - t2} ms`); - // check nothing beyond last page - const filter11 = { "page-size": max_page_length, page: 11 }; - cy.checkMonitoringWithFilter(Dexter, camera, filter11, []); - const t4 = performance.now(); - cy.log(`Page 11 load duration: ${t4 - t3} ms`); - }); - - it("applies max page length by default", () => { - const camera = "default_page_size"; - const visits = []; - // add 1 page plus 1 worth of recordings - cy.apiDeviceAdd(camera, group); - cy.apiRecordingAdd(camera, { time: "20:55", duration: 10 }); - for (let i = 0; i < max_page_length; i++) { - cy.apiRecordingAdd(camera, { minsLater: 11 }); - } - - // expected visits on 1st page is array of max_page_length of visits - for (let i = 0; i < max_page_length; i++) { - visits.push({ recordings: 1 }); - } - - const visits2 = [{ recordings: 1 }]; - - //check first page - const filter = { "page-size": max_page_length, page: 1 }; - cy.checkMonitoringWithFilter(Dexter, camera, filter, visits); - //check second page - const filter2 = { "page-size": max_page_length, page: 2 }; - cy.checkMonitoringWithFilter(Dexter, camera, filter2, visits2); - }); - - it("can handle large number of recordings per visit", () => { - const camera = "recordings-per-visit"; - // add 1000 recordings - cy.apiDeviceAdd(camera, group); - cy.apiRecordingAdd(camera, { time: "20:55", duration: 10 }); - for (let i = 0; i < 999; i++) { - cy.apiRecordingAdd(camera, { minsLater: 9 }); - } - - const visits = [{ recordings: 1000 }]; - - const filter = { page: 1 }; - const t1 = performance.now(); - cy.checkMonitoringWithFilter(Dexter, camera, filter, visits); - const t2 = performance.now(); - cy.log(`Page 1 load duration: ${t2 - t1} ms`); - }); - - it("can handle large number of pages", () => { - const camera = "pages"; - - // add 1000 recordings - cy.apiDeviceAdd(camera, group); - cy.apiRecordingAdd(camera, { time: "20:55", duration: 10 }); - for (let i = 0; i < 999; i++) { - cy.apiRecordingAdd(camera, { minsLater: 11 }); - } - - const visits = [{ recordings: 1 }]; - - //check first page - const filter1 = { "page-size": 1, page: 1 }; - const t1 = performance.now(); - cy.checkMonitoringWithFilter(Dexter, camera, filter1, visits); - const t2 = performance.now(); - cy.log(`Page 1 load duration: ${t2 - t1} ms`); - - //check last page - const filter1000 = { "page-size": 1, page: 1000 }; - cy.checkMonitoringWithFilter(Dexter, camera, filter1000, visits); - const t3 = performance.now(); - cy.log(`Page 1000 load duration: ${t3 - t2} ms`); - - //check nothing beyond last page - const filter1001 = { "page-size": 1, page: 1001 }; - cy.checkMonitoringWithFilter(Dexter, camera, filter1001, []); - }); -}); diff --git a/integration-tests/cypress/support/component.ts b/integration-tests/cypress/support/component.ts index bbe524cda..79cf34774 100644 --- a/integration-tests/cypress/support/component.ts +++ b/integration-tests/cypress/support/component.ts @@ -13,6 +13,8 @@ // https://on.cypress.io/configuration // *********************************************************** +/// + // Import commands.js using ES2015 syntax: import "./commands"; diff --git a/integration-tests/cypress/support/index.d.ts b/integration-tests/cypress/support/index.d.ts index af20b03ea..7764f81e4 100644 --- a/integration-tests/cypress/support/index.d.ts +++ b/integration-tests/cypress/support/index.d.ts @@ -17,3 +17,73 @@ /// /// /// + +// Reexport types in Cypress namespace +declare namespace Cypress { + type ApiStationData = import("@commands/types").ApiStationData; + type ApiStationResponse = import("@typedefs/api/station").ApiStationResponse; + type StationId = import("@typedefs/api/common").StationId; + + type ApiTrackDataRequest = import("@typedefs/api/track").ApiTrackDataRequest; + type ApiTrackResponse = import("@typedefs/api/track").ApiTrackResponse; + type ApiTrackTagRequest = import("@typedefs/api/trackTag").ApiTrackTagRequest; + + type TestThermalRecordingInfoAlias = + import("@commands/types").TestThermalRecordingInfo; + type RecordingIdAlias = import("@typedefs/api/common").RecordingId; + type ApiRecordingTagRequest = + import("@typedefs/api/tag").ApiRecordingTagRequest; + + type ApiRecordingSet = import("@commands/types").ApiRecordingSet; + type ApiRecordingReturned = import("@commands/types").ApiRecordingReturned; + type ApiRecordingColumns = import("@commands/types").ApiRecordingColumns; + type ApiRecordingNeedsTagReturned = + import("@commands/types").ApiRecordingNeedsTagReturned; + type ApiRecordingDataMetadata = + import("@commands/types").ApiRecordingDataMetadata; + type Interception = import("cypress/types/net-stubbing").Interception; + type ApiRecordingResponse = + import("@typedefs/api/recording").ApiRecordingResponse; + type TestThermalRecordingInfo = + import("@commands/types").TestThermalRecordingInfo; + type RecordingId = number; + + type ApiAudioRecordingResponse = + import("@typedefs/api/recording").ApiAudioRecordingResponse; + type ApiThermalRecordingResponse = + import("@typedefs/api/recording").ApiThermalRecordingResponse; + + type TestVisitSearchParams = import("@commands/types").TestVisitSearchParams; + type TestComparableVisit = import("@commands/types").TestComparableVisit; + type ApiGroupReturned = import("@commands/types").ApiGroupReturned; + type ApiDeviceIdAndName = import("@commands/types").ApiDeviceIdAndName; + type ApiGroupsDevice = import("@commands/types").ApiGroupsDevice; + type ApiStationDataAlias = import("@commands/types").ApiStationData; + type ApiStationDataReturned = + import("@commands/types").ApiStationDataReturned; + type ApiDeviceResponseAlias = + import("@typedefs/api/device").ApiDeviceResponse; + type ApiGroupUserRelationshipResponse = + import("@typedefs/api/group").ApiGroupUserResponse; + + type TestComparablePowerEvent = + import("@commands/types").TestComparablePowerEvent; + type ApiEventDetail = import("@commands/types").ApiEventDetail; + type ApiEventReturned = import("@commands/types").ApiEventReturned; + type ApiEventErrorCategory = import("@commands/types").ApiEventErrorCategory; + type ApiPowerEventReturned = import("@commands//types").ApiPowerEventReturned; + + type ApiDeviceResponse = import("@typedefs/api/device").ApiDeviceResponse; + type LatLng = import("@typedefs/api/common").LatLng; + type ApiGroupsUserRelationshipResponse = + import("@typedefs/api/group").ApiGroupUserResponse; + type DeviceType = import("@typedefs/api/consts").DeviceType; + type DeviceHistoryEntry = import("@commands/types").DeviceHistoryEntry; + type DeviceId = import("@typedefs/api/common").DeviceId; + type ApiMaskRegionsData = import("@typedefs/api/device").ApiMaskRegionsData; + type ApiAuthenticateAccess = import("@commands//types").ApiAuthenticateAccess; + + type ApiAlertCondition = import("@typedefs/api/alerts").ApiAlertCondition; + type AlertId = import("@typedefs/api/common").AlertId; + type ApiAlertResponse = import("@typedefs/api/alerts").ApiAlertResponse; +} diff --git a/integration-tests/package.json b/integration-tests/package.json index d7d4046dd..60efc57c2 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -5,10 +5,10 @@ "main": "index.js", "scripts": { "dev": "node ./node_modules/.bin/cypress open", - "performance": "node ./node_modules/.bin/cypress open --config-file cypress-performance.json", "browse": "node ./node_modules/.bin/cypress open --config-file cypress-browse.config.ts", - "release": "node ./node_modules/.bin/cypress run $CYPRESS_RECORD", - "performance:release": "node ./node_modules/.bin/cypress run $CYPRESS_RECORD --config-file cypress-performance.json", + "release": "node ./node_modules/.bin/cypress run", + "ci-batch-1": "node ./node_modules/.bin/cypress run --config-file cypress-api-batch-1.config.ts", + "ci-batch-2": "node ./node_modules/.bin/cypress run --config-file cypress-api-batch-2.config.ts", "lint": "eslint ./ --ext .ts && (npx prettier --check './**/*.ts' || (echo \"Please run 'npm run lint:fix' to clean up formatting of files.\"; false))", "lint:fix": "eslint ./ --fix --ext .ts && npx prettier --write './**/*.ts'" }, diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index 5895090c5..601893bb4 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -6,6 +6,7 @@ "moduleResolution": "node", "allowSyntheticDefaultImports": true, "baseUrl": ".", + "typeRoots": ["./cypress/support", "./cypress/commands"], "paths": { "@typedefs/*": ["../types/*"], "@commands/*": ["./cypress/commands/*"], @@ -13,8 +14,12 @@ "@/*": ["./cypress/*"], } }, + "references": [ + { "path": "./support/" } + ], "include": [ "**/*.ts", + "**/*.d.ts", "../types/**/*.ts" ] } diff --git a/types/api/recording.d.ts b/types/api/recording.d.ts index 014dea625..c76f592ef 100644 --- a/types/api/recording.d.ts +++ b/types/api/recording.d.ts @@ -112,7 +112,7 @@ export interface ApiAudioRecordingResponse extends ApiRecordingResponse { cacophonyIndex?: CacophonyIndex[]; type: RecordingType.Audio; fileMimeType?: string; - additionalMetadata?: ApiAudioRecordingMetadataResponse; + additionalMetadata?: ApiAudioRecordingMetadataResponse | any; } export interface ApiRecordingProcessingJob {