From 4a96d908e05304f792b34d1db6e5ccb5cdfb2af4 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Wed, 12 May 2021 12:45:54 -0700 Subject: [PATCH 01/32] Use Puppeteer to run tests in browser env --- tests/integration/serverConnections.integration.test.ts | 7 +++++++ tests/smoke/rest.smoke.test.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/tests/integration/serverConnections.integration.test.ts b/tests/integration/serverConnections.integration.test.ts index afcbdf6e..8250dfab 100644 --- a/tests/integration/serverConnections.integration.test.ts +++ b/tests/integration/serverConnections.integration.test.ts @@ -1,3 +1,5 @@ +const puppeteer = require('puppeteer'); +let browser: any; const fetch = require('node-fetch'); const stacks = require('../secrets/auth.json').stacks; @@ -72,6 +74,11 @@ describe('Mixer connections', () => { let nonadminDupSpaceName: string; let hifiCommunicator: HiFiCommunicator; beforeAll(async () => { + browser = await puppeteer.launch({ args: ['--use-fake-ui-for-media-stream'] }); + const page = await browser.newPage(); + page.setContent(''); + global.window = page; + global.navigator = window.navigator; try { let adminTokenNoSpace = await generateJWT(tokenTypes.ADMIN_ID_APP2); let returnMessage = await fetch(`${stackURL}/api/v1/spaces/?token=${adminTokenNoSpace}`); diff --git a/tests/smoke/rest.smoke.test.ts b/tests/smoke/rest.smoke.test.ts index f4d14f06..04d621fe 100644 --- a/tests/smoke/rest.smoke.test.ts +++ b/tests/smoke/rest.smoke.test.ts @@ -1,3 +1,5 @@ +const puppeteer = require('puppeteer'); +let browser: any; const fetch = require('node-fetch'); const stacks = require('../secrets/auth.json').stacks; @@ -35,6 +37,11 @@ describe('HiFi API REST Calls', () => { let appID = stackData.apps.APP_1.id; beforeAll(async () => { + browser = await puppeteer.launch({ args: ['--use-fake-ui-for-media-stream'] }); + const page = await browser.newPage(); + page.setContent(''); + global.window = page; + global.navigator = window.navigator; adminTokenNoSpace = await generateJWT(tokenTypes.ADMIN_ID_APP1); nonadminTokenNoSpace = await generateJWT(tokenTypes.NONADMIN_ID_APP1); }); From 1f274ea31db1a85b4cfe95578e79dfd933b4f3f0 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Wed, 12 May 2021 12:45:54 -0700 Subject: [PATCH 02/32] Use Puppeteer to run tests in browser env --- tests/integration/serverConnections.integration.test.ts | 7 +++++++ tests/smoke/rest.smoke.test.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/tests/integration/serverConnections.integration.test.ts b/tests/integration/serverConnections.integration.test.ts index afcbdf6e..8250dfab 100644 --- a/tests/integration/serverConnections.integration.test.ts +++ b/tests/integration/serverConnections.integration.test.ts @@ -1,3 +1,5 @@ +const puppeteer = require('puppeteer'); +let browser: any; const fetch = require('node-fetch'); const stacks = require('../secrets/auth.json').stacks; @@ -72,6 +74,11 @@ describe('Mixer connections', () => { let nonadminDupSpaceName: string; let hifiCommunicator: HiFiCommunicator; beforeAll(async () => { + browser = await puppeteer.launch({ args: ['--use-fake-ui-for-media-stream'] }); + const page = await browser.newPage(); + page.setContent(''); + global.window = page; + global.navigator = window.navigator; try { let adminTokenNoSpace = await generateJWT(tokenTypes.ADMIN_ID_APP2); let returnMessage = await fetch(`${stackURL}/api/v1/spaces/?token=${adminTokenNoSpace}`); diff --git a/tests/smoke/rest.smoke.test.ts b/tests/smoke/rest.smoke.test.ts index f4d14f06..04d621fe 100644 --- a/tests/smoke/rest.smoke.test.ts +++ b/tests/smoke/rest.smoke.test.ts @@ -1,3 +1,5 @@ +const puppeteer = require('puppeteer'); +let browser: any; const fetch = require('node-fetch'); const stacks = require('../secrets/auth.json').stacks; @@ -35,6 +37,11 @@ describe('HiFi API REST Calls', () => { let appID = stackData.apps.APP_1.id; beforeAll(async () => { + browser = await puppeteer.launch({ args: ['--use-fake-ui-for-media-stream'] }); + const page = await browser.newPage(); + page.setContent(''); + global.window = page; + global.navigator = window.navigator; adminTokenNoSpace = await generateJWT(tokenTypes.ADMIN_ID_APP1); nonadminTokenNoSpace = await generateJWT(tokenTypes.NONADMIN_ID_APP1); }); From 2dedf43c34a4e3538476e42f20e533fa47210798 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Wed, 12 May 2021 13:08:56 -0700 Subject: [PATCH 03/32] Fix yml error --- .github/workflows/run-unit-tests.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 0e817945..3c32b216 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -7,14 +7,15 @@ on: description: "Host name, i.e. 'api', `api-hobby-latest`, or 'api-pro'" required: true default: api-staging-latest + push: branches: - main - - gha - push: - pull_request: + - release + + pull_request: branches: - main - - gha + - release jobs: From a49f229b4ae6709cd02aa1d28ed8b0c6ee12588e Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Mon, 17 May 2021 10:43:39 -0700 Subject: [PATCH 04/32] Mute tests in progress --- .../serverConnections.integration.test.ts | 7 - tests/smoke/rest.smoke.test.ts | 1706 +++++++++-------- tests/testUtilities/TestUser.ts | 15 +- 3 files changed, 947 insertions(+), 781 deletions(-) diff --git a/tests/integration/serverConnections.integration.test.ts b/tests/integration/serverConnections.integration.test.ts index 8250dfab..afcbdf6e 100644 --- a/tests/integration/serverConnections.integration.test.ts +++ b/tests/integration/serverConnections.integration.test.ts @@ -1,5 +1,3 @@ -const puppeteer = require('puppeteer'); -let browser: any; const fetch = require('node-fetch'); const stacks = require('../secrets/auth.json').stacks; @@ -74,11 +72,6 @@ describe('Mixer connections', () => { let nonadminDupSpaceName: string; let hifiCommunicator: HiFiCommunicator; beforeAll(async () => { - browser = await puppeteer.launch({ args: ['--use-fake-ui-for-media-stream'] }); - const page = await browser.newPage(); - page.setContent(''); - global.window = page; - global.navigator = window.navigator; try { let adminTokenNoSpace = await generateJWT(tokenTypes.ADMIN_ID_APP2); let returnMessage = await fetch(`${stackURL}/api/v1/spaces/?token=${adminTokenNoSpace}`); diff --git a/tests/smoke/rest.smoke.test.ts b/tests/smoke/rest.smoke.test.ts index 04d621fe..22dd4ff6 100644 --- a/tests/smoke/rest.smoke.test.ts +++ b/tests/smoke/rest.smoke.test.ts @@ -1,7 +1,7 @@ -const puppeteer = require('puppeteer'); -let browser: any; const fetch = require('node-fetch'); const stacks = require('../secrets/auth.json').stacks; +const { MediaStream } = require('wrtc'); +const RTCAudioSourceSineWave = require('../testUtilities/rtcAudioSourceSineWave'); import { tokenTypes, generateJWT, generateUUID, sleep, ZoneData, AttenuationData, setStackData } from '../testUtilities/testUtils'; import { TestUser } from '../testUtilities/TestUser'; @@ -30,23 +30,18 @@ describe('HiFi API REST Calls', () => { console.log("_______________USING HOBBY AUTH FILE_______________________"); } else { stackData = stacks[stackname]; - console.log(`_______________USING ${ stackname } AUTH FILE_______________________`); + console.log(`_______________USING ${stackname} AUTH FILE_______________________`); } setStackData(stackData); let appID = stackData.apps.APP_1.id; beforeAll(async () => { - browser = await puppeteer.launch({ args: ['--use-fake-ui-for-media-stream'] }); - const page = await browser.newPage(); - page.setContent(''); - global.window = page; - global.navigator = window.navigator; adminTokenNoSpace = await generateJWT(tokenTypes.ADMIN_ID_APP1); nonadminTokenNoSpace = await generateJWT(tokenTypes.NONADMIN_ID_APP1); }); - describe('App spaces', () => { + describe.skip('App spaces', () => { let spaceID: string; let adminToken: string; let nonAdminToken: string; @@ -340,7 +335,109 @@ describe('HiFi API REST Calls', () => { }); }); - describe('Kicking users', () => { + // describe.skip('Kicking users', () => { + // const numberTestUsers = 4; + // let testUsers: Array = []; + // let spaceID: string; + // let adminToken: string; + // let nonAdminToken: string; + + // beforeAll(async () => { + // jest.setTimeout(35000); // these tests need longer to complete + // try { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // spaceID = returnMessageJSON['space-id']; + // adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + // nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); + // } catch (e) { + // console.error("Failed to create a space before tests for kicking."); + // } + // }); + + // afterAll(async () => { + // jest.setTimeout(5000); // restore to default + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + // method: 'DELETE' + // }); + // }); + + // beforeEach(async () => { + // testUsers = []; + // for (let i = 0; i < numberTestUsers; i++) { + // let tokenData = tokenTypes.NONADMIN_ID_APP1 + // tokenData['user_id'] = generateUUID(); + // testUsers.push(new TestUser(tokenData['user_id'])); + // let token = await generateJWT(tokenData, spaceID); + // await testUsers[i].communicator.connectToHiFiAudioAPIServer(token, stackURL); + // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + // } + // }); + + // afterEach(async () => { + // // disconnect communicators to avoid using too many mixers + // for (let i = 0; i < numberTestUsers; i++) { + // await testUsers[i].communicator.disconnectFromHiFiAudioAPIServer(); + // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Disconnected); + // } + // }); + + // describe('Admin CAN kick users', () => { + // test(`Kick one user`, async () => { + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { + // method: 'DELETE' + // }); + // await sleep(30000); + // for (let i = 0; i < numberTestUsers; i++) { + // if (i === 0) expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); + // else expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + // } + // }); + + // test(`Kick all users`, async () => { + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { + // method: 'DELETE' + // }); + // await sleep(30000); + // for (let i = 0; i < numberTestUsers; i++) { + // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); + // } + // }); + // }); + + // describe('Nonadmin CANNOT kick users', () => { + // test(`Kick one user`, async () => { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { + // method: 'DELETE' + // }); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // await sleep(30000); + // expect(returnMessageJSON.code).toBe(401); + // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); + // for (let i = 0; i < numberTestUsers; i++) { + // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + // } + // }); + + // test(`Kick all users`, async () => { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonAdminToken}`, { + // method: 'DELETE' + // }); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // await sleep(30000); + // expect(returnMessageJSON.code).toBe(401); + // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); + // for (let i = 0; i < numberTestUsers; i++) { + // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + // } + // }); + // }); + // }); + + describe.only('Muting users', () => { const numberTestUsers = 4; let testUsers: Array = []; let spaceID: string; @@ -350,811 +447,876 @@ describe('HiFi API REST Calls', () => { beforeAll(async () => { jest.setTimeout(35000); // these tests need longer to complete try { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - let returnMessageJSON: any = {}; - returnMessageJSON = await returnMessage.json(); - spaceID = returnMessageJSON['space-id']; - adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); - nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // spaceID = returnMessageJSON['space-id']; + spaceID = '4a34ad54-d5ae-42e7-b52b-9fa67a8b1847'; + // adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + adminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfaWQiOiJjMzBjZWQ2Yi0xODU1LTQ0OTYtYjRjNS02MzE3ZTZkNThmOTEiLCJ1c2VyX2lkIjoiYSIsImFkbWluIjp0cnVlLCJkZXZlbG9wZXJfaWQiOiJhYzUwOWQxMC03N2U0LTQxYWUtYWIyMy02MGYzOWFiYTgwYTYiLCJzdGFjayI6ImF1ZGlvbmV0LW1peGVyLWFwaS1zdGFnaW5nLTE5In0.tP2yhh20l4NL0tYfyAcOLGkxHO3QSGjeIyephozCAsw'; + // nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); + nonAdminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfaWQiOiJjMzBjZWQ2Yi0xODU1LTQ0OTYtYjRjNS02MzE3ZTZkNThmOTEiLCJ1c2VyX2lkIjoiYiIsInNwYWNlX2lkIjoiNGEzNGFkNTQtZDVhZS00MmU3LWI1MmItOWZhNjdhOGIxODQ3Iiwic3RhY2siOiJhdWRpb25ldC1taXhlci1hcGktc3RhZ2luZy0xOSJ9.VMNCPH-8c96sqvPe6QEhRLi2PSdLNwCIyDL7rhqUMU0'; } catch (e) { - console.error("Failed to create a space before tests for kicking."); + console.error("Failed to create a space before tests for muting."); } }); afterAll(async () => { - jest.setTimeout(5000); // restore to default - await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { - method: 'DELETE' - }); + // jest.setTimeout(5000); // restore to default + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + // method: 'DELETE' + // }); }); beforeEach(async () => { testUsers = []; + let notesHz = [110, 130.81, 164.81, 196]; for (let i = 0; i < numberTestUsers; i++) { - let tokenData = tokenTypes.NONADMIN_ID_APP1 + // let tokenData = tokenTypes.NONADMIN_ID_APP1; + let tokenData = { + "admin": false, + "signed": true, + "user_id": "qateamNonAdmin", + "app_id": 'c30ced6b-1855-4496-b4c5-6317e6d58f91', + "app_secret": '881ea557-1ab5-4a7b-8d08-d59e13fb26aa' + }; tokenData['user_id'] = generateUUID(); - testUsers.push(new TestUser(tokenData['user_id'])); + testUsers.push(new TestUser(tokenData['user_id'], i + 1 * 3)); let token = await generateJWT(tokenData, spaceID); + console.log("__________________USER_______________", tokenData['user_id'], "_____________", token); await testUsers[i].communicator.connectToHiFiAudioAPIServer(token, stackURL); expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + + let source = new RTCAudioSourceSineWave({ frequency: notesHz[i] }); + let track = source.createTrack(); + let inputAudioMediaStream = new MediaStream([track]); + testUsers[i].communicator.setInputAudioMediaStream(inputAudioMediaStream); } }); afterEach(async () => { // disconnect communicators to avoid using too many mixers for (let i = 0; i < numberTestUsers; i++) { + console.log("____________DISCONNECT__________________"); await testUsers[i].communicator.disconnectFromHiFiAudioAPIServer(); expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Disconnected); } }); - describe('Admin CAN kick users', () => { - test(`Kick one user`, async () => { - await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { - method: 'DELETE' - }); - await sleep(30000); - for (let i = 0; i < numberTestUsers; i++) { - if (i === 0) expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); - else expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - } - }); + describe('Admin CAN mute/unmute users', () => { + test.only(`Mute one user`, async () => { + await sleep(10000); + console.log("__________________MUTE_______________", `${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); - test(`Kick all users`, async () => { - await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { - method: 'DELETE' - }); - await sleep(30000); - for (let i = 0; i < numberTestUsers; i++) { - expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); - } - }); - }); - - describe('Nonadmin CANNOT kick users', () => { - test(`Kick one user`, async () => { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { - method: 'DELETE' - }); - let returnMessageJSON: any = {}; - returnMessageJSON = await returnMessage.json(); - await sleep(30000); - expect(returnMessageJSON.code).toBe(401); - expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); - for (let i = 0; i < numberTestUsers; i++) { - expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - } - }); - - test(`Kick all users`, async () => { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonAdminToken}`, { - method: 'DELETE' - }); - let returnMessageJSON: any = {}; - returnMessageJSON = await returnMessage.json(); - await sleep(30000); - expect(returnMessageJSON.code).toBe(401); - expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); - for (let i = 0; i < numberTestUsers; i++) { - expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - } - }); - }); - }); - - describe('Wrong admin tokens', () => { - describe(`CANNOT read/alter App A by using a valid admin token for App B`, () => { - let spaceID: string; - let adminTokenApp1: string; - let adminTokenApp2: string; - beforeAll(async () => { - try { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - let returnMessageJSON: any = {}; - returnMessageJSON = await returnMessage.json(); - spaceID = returnMessageJSON['space-id']; - adminTokenApp1 = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); - adminTokenApp2 = await generateJWT(tokenTypes.ADMIN_ID_APP2); - } catch (err) { - console.error("Failed to create spaces before tests for wrong admin."); - } - }); - - afterAll(async () => { - await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminTokenApp1}`, { - method: 'DELETE' - }); - }); - - test(`Read settings for a space`, async () => { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/app-id/?token=${adminTokenApp2}`); - let returnMessageJSON: any = {}; - returnMessageJSON = await returnMessage.json(); - expect(returnMessageJSON.code).toBe(422); - expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/space\/app mismatch/) }); - }); - }); - }); - - describe('Zones and attenuations', () => { - let spaceID: string; - let adminToken: string; - let nonAdminToken: string; - let zone1Data: ZoneData; - let zone2Data: ZoneData; - let zone3Data: ZoneData; - let zone4Data: ZoneData; - - beforeAll(async () => { - jest.setTimeout(20000); // these tests need longer to complete - // Create a space for testing - try { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - let returnMessageJSON: any = {}; - returnMessageJSON = await returnMessage.json(); - spaceID = returnMessageJSON['space-id']; - adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); - nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); - } catch (e) { - console.error("Failed to create a space before tests for zones and attenuations."); - } - - zone1Data = { - "x-min": -5, - "x-max": 5, - "y-min": 0, - "y-max": 10, - "z-min": -5, - "z-max": 5, - "name": generateUUID() - }; - zone2Data = { - "x-min": 5, - "x-max": 15, - "y-min": 0, - "y-max": 10, - "z-min": -5, - "z-max": 5, - "name": generateUUID() - }; - zone3Data = { - "x-min": 15, - "x-max": 25, - "y-min": 0, - "y-max": 10, - "z-min": -5, - "z-max": 5, - "name": generateUUID() - }; - zone4Data = { - "x-min": 25, - "x-max": 35, - "y-min": 0, - "y-max": 10, - "z-min": -5, - "z-max": 5, - "name": generateUUID() - }; - }); - - afterAll(async () => { - jest.setTimeout(5000); // restore to default - await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { - method: 'DELETE' - }); - }); - - beforeEach(async () => { - try { - await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - method: 'DELETE' - }); - } catch (err) { - console.error("Failed to delete all zones before test. Please manually remove them and then rerun the test."); - } - }); - - test(`Admin CAN access, edit, create, and delete zones and attenuations`, async () => { - // Create multiple zones via space `settings/zones` POST request - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([zone1Data, zone2Data]) - }); - - // Response will be the zone datas plus a new zone ID for each zone. Add the IDs to our data and the response should match - let responseJSON: any = {}; - responseJSON = await returnMessage.json(); - expect(responseJSON[0].id).toBeDefined(); - expect(responseJSON[1].id).toBeDefined(); - if (responseJSON[0].name === zone1Data.name) { - zone1Data['id'] = responseJSON[0].id; - zone2Data['id'] = responseJSON[1].id; - } else { - zone1Data['id'] = responseJSON[1].id; - zone2Data['id'] = responseJSON[0].id; - } - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data].map(a => a.id).sort()); - - // Create one zone via space `settings/zones/create` POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([zone3Data]) - }); - responseJSON = await returnMessage.json(); - expect(responseJSON[0].id).toBeDefined(); - zone3Data['id'] = responseJSON[0].id; - expect(responseJSON).toEqual([zone3Data]); - - // Create one zone via space settings/zones/create GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${adminToken}&x-min=${zone4Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.id).toBeDefined(); - zone4Data['id'] = responseJSON.id; - expect(responseJSON).toEqual(zone4Data); - - // Get the list of zones and make sure it is accurate - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data, zone3Data, zone4Data].map(a => a.id).sort()); - - - // Get a zone's settings via GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual(zone1Data); - - // Change a zone's settings via GET request - zone1Data['x-min'] = -6; - zone1Data['x-max'] = 6; - zone1Data['y-min'] = 10; - zone1Data['y-max'] = 20; - zone1Data['z-min'] = -6; - zone1Data['z-max'] = -6; - zone1Data['name'] = generateUUID(); - - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual(zone1Data); - - // Get a zone's settings via POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: '{}' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual(zone1Data); - - // Change a zone's settings via POST request - let zoneID = zone1Data.id; - zone1Data = { - "x-min": -7, - "x-max": 7, - "y-min": 0, - "y-max": 10, - "z-min": -7, - "z-max": 7, - "name": generateUUID() - }; - - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(zone1Data) - }); - responseJSON = await returnMessage.json(); - zone1Data.id = zoneID; - expect(responseJSON).toEqual(zone1Data); - - // Create multiple attenuations via space `settings/attenuations` POST request - let attenuation1Data: AttenuationData; - let attenuation2Data: AttenuationData; - let attenuation3Data: AttenuationData; - let attenuation4Data: AttenuationData; - attenuation1Data = { - "attenuation": 0.5, - "listener-zone-id": zone1Data.id, - "source-zone-id": zone2Data.id, - "za-offset": -5 - }; - attenuation2Data = { - "attenuation": 0.5, - "listener-zone-id": zone1Data.id, - "source-zone-id": zone2Data.id, - "za-offset": -5 - }; - attenuation3Data = { - "attenuation": 0.5, - "listener-zone-id": zone1Data.id, - "source-zone-id": zone3Data.id, - "za-offset": -5 - }; - attenuation4Data = { - "attenuation": 0.5, - "listener-zone-id": zone1Data.id, - "source-zone-id": zone4Data.id, - "za-offset": -5 - }; - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([attenuation1Data, attenuation2Data]) - }); - - // Response will be the attenuation datas plus a new attenuation ID for each attenuation. Add the IDs to our data and the response should match - responseJSON = await returnMessage.json(); - expect(responseJSON[0]['id']).toBeDefined(); - expect(responseJSON[1]['id']).toBeDefined(); - attenuation1Data['id'] = responseJSON[0]['id']; - attenuation2Data['id'] = responseJSON[1]['id']; - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data].map(a => a.id).sort()); - - // Create one attenuation via space `settings/attenuations/create` POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([attenuation3Data]) - }); - responseJSON = await returnMessage.json(); - expect(responseJSON[0]['id']).toBeDefined(); - attenuation3Data['id'] = responseJSON[0]['id']; - expect(responseJSON).toEqual([attenuation3Data]); - - // Create one attenuation via space settings/attenuations/create GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${adminToken}&attenuation=${attenuation4Data["attenuation"]}&source-zone-id=${attenuation4Data["source-zone-id"]}&listener-zone-id=${attenuation4Data["listener-zone-id"]}&za-offset=${attenuation4Data["za-offset"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON['id']).toBeDefined(); - attenuation4Data['id'] = responseJSON['id']; - expect(responseJSON).toEqual(attenuation4Data); - - // Get the list of attenuations and make sure it is accurate - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data, attenuation3Data, attenuation4Data].map(a => a.id).sort()); - - // Get a zone attenuation's settings via GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual(attenuation1Data); - - // Change a zone attenuation's settings via GET request - attenuation1Data['attenuation'] = -6; - attenuation1Data['listener-zone-id'] = zone2Data.id; - attenuation1Data['source-zone-id'] = zone3Data.id; - attenuation1Data['za-offset'] = 20; - - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual(attenuation1Data); - - // Get a zone attenuation's settings via POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: '{}' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual(attenuation1Data); - - // Change a zone attenuation's settings via POST request - let attenuationID = attenuation1Data.id; - attenuation1Data = { - "attenuation": 0.8, - "listener-zone-id": zone3Data.id, - "source-zone-id": zone4Data.id, - "za-offset": -7 - } - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${adminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(attenuation1Data) - }); - responseJSON = await returnMessage.json(); - attenuation1Data.id = attenuationID; - expect(responseJSON).toEqual(attenuation1Data); - - // Delete one zone attenuation - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { - method: 'DELETE' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.id).toBe(attenuation1Data.id); - - // Delete all zone attenuations - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { - method: 'DELETE' - }); - responseJSON = await returnMessage.json(); - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual([]); - - // Delete one zone - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { - method: 'DELETE' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.id).toBe(zone1Data.id); - - // Delete all zones - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - method: 'DELETE' - }); - - // check - responseJSON = await returnMessage.json(); - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON).toEqual([]); - }); - - test(`Nonadmin CANNOT access, edit, create, and delete zones and attenuations`, async () => { - // reset zone 1 and 2 as they are the only ones we will use for nonadmin testing - zone1Data = { - "x-min": -5, - "x-max": 5, - "y-min": 0, - "y-max": 10, - "z-min": -5, - "z-max": 5, - "name": generateUUID() - }; - zone2Data = { - "x-min": 5, - "x-max": 15, - "y-min": 0, - "y-max": 10, - "z-min": -5, - "z-max": 5, - "name": generateUUID() - }; - - // Try to create multiple zones via space `settings/zones` POST request - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([zone1Data, zone2Data]) - }); - let responseJSON: any = {}; - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to create one zone via space `settings/zones/create` POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([zone1Data]) - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to create one zone via space settings/zones/create GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to get the list of zones - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to get a zone's settings via GET request - // Create 2 zones to test against (need 2 for attenualtions) - try { - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminTokenNoSpace}`, { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify([zone1Data, zone2Data]) + body: JSON.stringify({ mute: true }) }); - responseJSON = await returnMessage.json(); - if (responseJSON[0].name === zone1Data.name) { - zone1Data['id'] = responseJSON[0].id; - zone2Data['id'] = responseJSON[1].id; - } else { - zone1Data['id'] = responseJSON[1].id; - zone2Data['id'] = responseJSON[0].id; - } - } catch (e) { - console.error("Failed to create a zone before tests for nonadmins to edit zones."); - } - - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to change a zone's settings via GET request - zone1Data['x-min'] = -6; - zone1Data['x-max'] = 6; - zone1Data['y-min'] = 10; - zone1Data['y-max'] = 20; - zone1Data['z-min'] = -6; - zone1Data['z-max'] = -6; - zone1Data['name'] = generateUUID(); - - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to get a zone's settings via POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: '{}' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to change a zone's settings via POST request - let zoneID = zone1Data.id; - zone1Data = { - "x-min": -7, - "x-max": 7, - "y-min": 0, - "y-max": 10, - "z-min": -7, - "z-max": 7, - "name": generateUUID() - }; - - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(zone1Data) - }); - responseJSON = await returnMessage.json(); - zone1Data.id = zoneID; - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to create multiple attenuations via space `settings/attenuations` POST request - let attenuation1Data: AttenuationData; - let attenuation2Data: AttenuationData; - attenuation1Data = { - "attenuation": 0.5, - "listener-zone-id": zone1Data.id, - "source-zone-id": zone2Data.id, - "za-offset": -5 - }; - attenuation2Data = { - "attenuation": 0.5, - "listener-zone-id": zone2Data.id, - "source-zone-id": zone1Data.id, - "za-offset": -5 - }; - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([attenuation1Data, attenuation2Data]) - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to create one attenuation via space `settings/attenuations/create` POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify([attenuation2Data]) - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - // Try to create one attenuation via space settings/attenuations/create GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&source-zone-id=${attenuation2Data["source-zone-id"]}&listener-zone-id=${attenuation2Data["listener-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + let responseJSON: any = {}; + responseJSON = await returnMessage.json(); + console.log("__________mute response___________", responseJSON); - // Try to get the list of attenuations - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + await sleep(10000); + console.log("__________________UNMUTE_______________", `${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); - // Try to get a zone attenuation's settings via GET request - // Create 2 attenuations to test against - try { - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminTokenNoSpace}`, { + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify([attenuation1Data, attenuation2Data]) - }); - responseJSON = await returnMessage.json(); - attenuation1Data['id'] = responseJSON[0]['id']; - attenuation1Data['id'] = responseJSON[1]['id']; - } catch (e) { - console.error("Failed to create an attenuation before tests for nonadmins to edit attenuations."); - } - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to change a zone attenuation's settings via GET request - attenuation1Data['attenuation'] = -6; - attenuation1Data['listener-zone-id'] = zone2Data.id; - attenuation1Data['source-zone-id'] = zone3Data.id; - attenuation1Data['za-offset'] = 20; - - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to get a zone attenuation's settings via POST request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: '{}' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to change a zone attenuation's settings via POST request - let attenuationID = attenuation1Data.id; - attenuation1Data = { - "attenuation": 0.8, - "listener-zone-id": zone3Data.id, - "source-zone-id": zone4Data.id, - "za-offset": -7 - } - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${nonAdminToken}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(attenuation1Data) - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - attenuation1Data.id = attenuationID; - - // Try to delete one zone attenuation - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { - method: 'DELETE' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to delete all zone attenuations - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { - method: 'DELETE' - }); - responseJSON = await returnMessage.json(); - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to delete one zone - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { - method: 'DELETE' - }); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Try to delete all zones - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { - method: 'DELETE' - }); - responseJSON = await returnMessage.json(); - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); - responseJSON = await returnMessage.json(); - expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // Clean up - try { - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - method: 'DELETE' + body: JSON.stringify({ mute: false }) }); - } catch (e) { - console.error("Failed to clean up zones after testing."); - } - }); - }); - describe('User data access', () => { - let spaceID: string; - let adminToken: string; - let adminID: string; - let adminVisitIDHash: string; - let nonadminToken: string; - let nonadminID: string; - let nonadminVisitIDHash: string; - let testUserAdmin: TestUser; - let testUserNonadmin: TestUser; - - beforeAll(async () => { - jest.setTimeout(15000); // these tests need longer to complete - try { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - let returnMessageJSON: any = {}; - returnMessageJSON = await returnMessage.json(); - spaceID = returnMessageJSON['space-id']; - - // connect an admin test user to the space - let tokenData = tokenTypes.ADMIN_ID_APP1; - tokenData['user_id'] = generateUUID(); - testUserAdmin = new TestUser(tokenData['user_id']); - adminToken = await generateJWT(tokenData, spaceID); - await testUserAdmin.communicator.connectToHiFiAudioAPIServer(adminToken, stackURL) - .then(data => { - adminID = data.audionetInitResponse.user_id; - adminVisitIDHash = data.audionetInitResponse.visit_id_hash; - }); - expect(testUserAdmin.connectionState).toBe(HiFiConnectionStates.Connected); - - // connect a nonadmin test user to the space - tokenData = tokenTypes.NONADMIN_ID_APP1; - tokenData['user_id'] = generateUUID(); - testUserNonadmin = new TestUser(tokenData['user_id']); - nonadminToken = await generateJWT(tokenData, spaceID); - await testUserNonadmin.communicator.connectToHiFiAudioAPIServer(nonadminToken, stackURL) - .then(data => { - nonadminID = data.audionetInitResponse.user_id; - nonadminVisitIDHash = data.audionetInitResponse.visit_id_hash; - }); - expect(testUserNonadmin.connectionState).toBe(HiFiConnectionStates.Connected); - } catch (err) { - console.error("Could not set up a space and connect users to test user data access."); - } - }); - - afterAll(async () => { - await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { - method: 'DELETE' + responseJSON = await returnMessage.json(); + console.log("__________mute response___________", responseJSON); + await sleep(10000); + // for (let i = 0; i < numberTestUsers; i++) { + // if (i === 0) expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); + // else expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + // } + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify({ mute: false }) + // }) + // .then((data: any) => { + // console.log("__________________DATA_______________", data); + // }); }); - jest.setTimeout(5000); // restore to default - }); - test('Admin CAN access list of users', async () => { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); - let returnMessageJSON = await returnMessage.json(); - expect(returnMessageJSON.length).toBe(2); - let returnedAdmin = returnMessageJSON.filter((obj: { user_id: string; }) => { - return obj['user_id'] === adminID; - }) - expect(returnedAdmin).toBeDefined(); - - let returnedNonadmin = returnMessageJSON.filter((obj: { user_id: string; }) => { - return obj['user_id'] === nonadminID; - }) - expect(returnedNonadmin).toBeDefined(); + // test(`Mute all users`, async () => { + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify({ mute: true }) + // }); + // await sleep(30000); + // // for (let i = 0; i < numberTestUsers; i++) { + // // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); + // // } + // }); }); - test('Nonadmin CANNOT access list of users', async () => { - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonadminToken}`); - let returnMessageJSON = await returnMessage.json(); - expect(returnMessageJSON.errors.description).toBe(`token isn't an admin token`); - }); + // describe('Nonadmin CANNOT mute users', () => { + // test(`Mute one user`, async () => { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify({ mute: true }) + // }); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // await sleep(30000); + // expect(returnMessageJSON.code).toBe(401); + // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); + // for (let i = 0; i < numberTestUsers; i++) { + // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + // } + // }); + + // test(`Mute all users`, async () => { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify({ mute: true }) + // }); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // await sleep(30000); + // expect(returnMessageJSON.code).toBe(401); + // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); + // for (let i = 0; i < numberTestUsers; i++) { + // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + // } + // }); + // }); }); + + // describe.skip('Wrong admin tokens', () => { + // describe(`CANNOT read/alter App A by using a valid admin token for App B`, () => { + // let spaceID: string; + // let adminTokenApp1: string; + // let adminTokenApp2: string; + // beforeAll(async () => { + // try { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // spaceID = returnMessageJSON['space-id']; + // adminTokenApp1 = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + // adminTokenApp2 = await generateJWT(tokenTypes.ADMIN_ID_APP2); + // } catch (err) { + // console.error("Failed to create spaces before tests for wrong admin."); + // } + // }); + + // afterAll(async () => { + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminTokenApp1}`, { + // method: 'DELETE' + // }); + // }); + + // test(`Read settings for a space`, async () => { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/app-id/?token=${adminTokenApp2}`); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // expect(returnMessageJSON.code).toBe(422); + // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/space\/app mismatch/) }); + // }); + // }); + // }); + + // describe.skip('Zones and attenuations', () => { + // let spaceID: string; + // let adminToken: string; + // let nonAdminToken: string; + // let zone1Data: ZoneData; + // let zone2Data: ZoneData; + // let zone3Data: ZoneData; + // let zone4Data: ZoneData; + + // beforeAll(async () => { + // jest.setTimeout(20000); // these tests need longer to complete + // // Create a space for testing + // try { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // spaceID = returnMessageJSON['space-id']; + // adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + // nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); + // } catch (e) { + // console.error("Failed to create a space before tests for zones and attenuations."); + // } + + // zone1Data = { + // "x-min": -5, + // "x-max": 5, + // "y-min": 0, + // "y-max": 10, + // "z-min": -5, + // "z-max": 5, + // "name": generateUUID() + // }; + // zone2Data = { + // "x-min": 5, + // "x-max": 15, + // "y-min": 0, + // "y-max": 10, + // "z-min": -5, + // "z-max": 5, + // "name": generateUUID() + // }; + // zone3Data = { + // "x-min": 15, + // "x-max": 25, + // "y-min": 0, + // "y-max": 10, + // "z-min": -5, + // "z-max": 5, + // "name": generateUUID() + // }; + // zone4Data = { + // "x-min": 25, + // "x-max": 35, + // "y-min": 0, + // "y-max": 10, + // "z-min": -5, + // "z-max": 5, + // "name": generateUUID() + // }; + // }); + + // afterAll(async () => { + // jest.setTimeout(5000); // restore to default + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + // method: 'DELETE' + // }); + // }); + + // beforeEach(async () => { + // try { + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + // method: 'DELETE' + // }); + // } catch (err) { + // console.error("Failed to delete all zones before test. Please manually remove them and then rerun the test."); + // } + // }); + + // test(`Admin CAN access, edit, create, and delete zones and attenuations`, async () => { + // // Create multiple zones via space `settings/zones` POST request + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([zone1Data, zone2Data]) + // }); + + // // Response will be the zone datas plus a new zone ID for each zone. Add the IDs to our data and the response should match + // let responseJSON: any = {}; + // responseJSON = await returnMessage.json(); + // expect(responseJSON[0].id).toBeDefined(); + // expect(responseJSON[1].id).toBeDefined(); + // if (responseJSON[0].name === zone1Data.name) { + // zone1Data['id'] = responseJSON[0].id; + // zone2Data['id'] = responseJSON[1].id; + // } else { + // zone1Data['id'] = responseJSON[1].id; + // zone2Data['id'] = responseJSON[0].id; + // } + // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data].map(a => a.id).sort()); + + // // Create one zone via space `settings/zones/create` POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([zone3Data]) + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON[0].id).toBeDefined(); + // zone3Data['id'] = responseJSON[0].id; + // expect(responseJSON).toEqual([zone3Data]); + + // // Create one zone via space settings/zones/create GET request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${adminToken}&x-min=${zone4Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.id).toBeDefined(); + // zone4Data['id'] = responseJSON.id; + // expect(responseJSON).toEqual(zone4Data); + + // // Get the list of zones and make sure it is accurate + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data, zone3Data, zone4Data].map(a => a.id).sort()); + + + // // Get a zone's settings via GET request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual(zone1Data); + + // // Change a zone's settings via GET request + // zone1Data['x-min'] = -6; + // zone1Data['x-max'] = 6; + // zone1Data['y-min'] = 10; + // zone1Data['y-max'] = 20; + // zone1Data['z-min'] = -6; + // zone1Data['z-max'] = -6; + // zone1Data['name'] = generateUUID(); + + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual(zone1Data); + + // // Get a zone's settings via POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: '{}' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual(zone1Data); + + // // Change a zone's settings via POST request + // let zoneID = zone1Data.id; + // zone1Data = { + // "x-min": -7, + // "x-max": 7, + // "y-min": 0, + // "y-max": 10, + // "z-min": -7, + // "z-max": 7, + // "name": generateUUID() + // }; + + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify(zone1Data) + // }); + // responseJSON = await returnMessage.json(); + // zone1Data.id = zoneID; + // expect(responseJSON).toEqual(zone1Data); + + // // Create multiple attenuations via space `settings/attenuations` POST request + // let attenuation1Data: AttenuationData; + // let attenuation2Data: AttenuationData; + // let attenuation3Data: AttenuationData; + // let attenuation4Data: AttenuationData; + // attenuation1Data = { + // "attenuation": 0.5, + // "listener-zone-id": zone1Data.id, + // "source-zone-id": zone2Data.id, + // "za-offset": -5 + // }; + // attenuation2Data = { + // "attenuation": 0.5, + // "listener-zone-id": zone1Data.id, + // "source-zone-id": zone2Data.id, + // "za-offset": -5 + // }; + // attenuation3Data = { + // "attenuation": 0.5, + // "listener-zone-id": zone1Data.id, + // "source-zone-id": zone3Data.id, + // "za-offset": -5 + // }; + // attenuation4Data = { + // "attenuation": 0.5, + // "listener-zone-id": zone1Data.id, + // "source-zone-id": zone4Data.id, + // "za-offset": -5 + // }; + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([attenuation1Data, attenuation2Data]) + // }); + + // // Response will be the attenuation datas plus a new attenuation ID for each attenuation. Add the IDs to our data and the response should match + // responseJSON = await returnMessage.json(); + // expect(responseJSON[0]['id']).toBeDefined(); + // expect(responseJSON[1]['id']).toBeDefined(); + // attenuation1Data['id'] = responseJSON[0]['id']; + // attenuation2Data['id'] = responseJSON[1]['id']; + // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data].map(a => a.id).sort()); + + // // Create one attenuation via space `settings/attenuations/create` POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([attenuation3Data]) + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON[0]['id']).toBeDefined(); + // attenuation3Data['id'] = responseJSON[0]['id']; + // expect(responseJSON).toEqual([attenuation3Data]); + + // // Create one attenuation via space settings/attenuations/create GET request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${adminToken}&attenuation=${attenuation4Data["attenuation"]}&source-zone-id=${attenuation4Data["source-zone-id"]}&listener-zone-id=${attenuation4Data["listener-zone-id"]}&za-offset=${attenuation4Data["za-offset"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON['id']).toBeDefined(); + // attenuation4Data['id'] = responseJSON['id']; + // expect(responseJSON).toEqual(attenuation4Data); + + // // Get the list of attenuations and make sure it is accurate + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data, attenuation3Data, attenuation4Data].map(a => a.id).sort()); + + // // Get a zone attenuation's settings via GET request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual(attenuation1Data); + + // // Change a zone attenuation's settings via GET request + // attenuation1Data['attenuation'] = -6; + // attenuation1Data['listener-zone-id'] = zone2Data.id; + // attenuation1Data['source-zone-id'] = zone3Data.id; + // attenuation1Data['za-offset'] = 20; + + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual(attenuation1Data); + + // // Get a zone attenuation's settings via POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: '{}' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual(attenuation1Data); + + // // Change a zone attenuation's settings via POST request + // let attenuationID = attenuation1Data.id; + // attenuation1Data = { + // "attenuation": 0.8, + // "listener-zone-id": zone3Data.id, + // "source-zone-id": zone4Data.id, + // "za-offset": -7 + // } + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${adminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify(attenuation1Data) + // }); + // responseJSON = await returnMessage.json(); + // attenuation1Data.id = attenuationID; + // expect(responseJSON).toEqual(attenuation1Data); + + // // Delete one zone attenuation + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { + // method: 'DELETE' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.id).toBe(attenuation1Data.id); + + // // Delete all zone attenuations + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { + // method: 'DELETE' + // }); + // responseJSON = await returnMessage.json(); + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual([]); + + // // Delete one zone + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { + // method: 'DELETE' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.id).toBe(zone1Data.id); + + // // Delete all zones + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + // method: 'DELETE' + // }); + + // // check + // responseJSON = await returnMessage.json(); + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON).toEqual([]); + // }); + + // test(`Nonadmin CANNOT access, edit, create, and delete zones and attenuations`, async () => { + // // reset zone 1 and 2 as they are the only ones we will use for nonadmin testing + // zone1Data = { + // "x-min": -5, + // "x-max": 5, + // "y-min": 0, + // "y-max": 10, + // "z-min": -5, + // "z-max": 5, + // "name": generateUUID() + // }; + // zone2Data = { + // "x-min": 5, + // "x-max": 15, + // "y-min": 0, + // "y-max": 10, + // "z-min": -5, + // "z-max": 5, + // "name": generateUUID() + // }; + + // // Try to create multiple zones via space `settings/zones` POST request + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([zone1Data, zone2Data]) + // }); + // let responseJSON: any = {}; + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to create one zone via space `settings/zones/create` POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([zone1Data]) + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to create one zone via space settings/zones/create GET request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to get the list of zones + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to get a zone's settings via GET request + // // Create 2 zones to test against (need 2 for attenualtions) + // try { + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminTokenNoSpace}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([zone1Data, zone2Data]) + // }); + // responseJSON = await returnMessage.json(); + // if (responseJSON[0].name === zone1Data.name) { + // zone1Data['id'] = responseJSON[0].id; + // zone2Data['id'] = responseJSON[1].id; + // } else { + // zone1Data['id'] = responseJSON[1].id; + // zone2Data['id'] = responseJSON[0].id; + // } + // } catch (e) { + // console.error("Failed to create a zone before tests for nonadmins to edit zones."); + // } + + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to change a zone's settings via GET request + // zone1Data['x-min'] = -6; + // zone1Data['x-max'] = 6; + // zone1Data['y-min'] = 10; + // zone1Data['y-max'] = 20; + // zone1Data['z-min'] = -6; + // zone1Data['z-max'] = -6; + // zone1Data['name'] = generateUUID(); + + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to get a zone's settings via POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: '{}' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to change a zone's settings via POST request + // let zoneID = zone1Data.id; + // zone1Data = { + // "x-min": -7, + // "x-max": 7, + // "y-min": 0, + // "y-max": 10, + // "z-min": -7, + // "z-max": 7, + // "name": generateUUID() + // }; + + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify(zone1Data) + // }); + // responseJSON = await returnMessage.json(); + // zone1Data.id = zoneID; + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to create multiple attenuations via space `settings/attenuations` POST request + // let attenuation1Data: AttenuationData; + // let attenuation2Data: AttenuationData; + // attenuation1Data = { + // "attenuation": 0.5, + // "listener-zone-id": zone1Data.id, + // "source-zone-id": zone2Data.id, + // "za-offset": -5 + // }; + // attenuation2Data = { + // "attenuation": 0.5, + // "listener-zone-id": zone2Data.id, + // "source-zone-id": zone1Data.id, + // "za-offset": -5 + // }; + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([attenuation1Data, attenuation2Data]) + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to create one attenuation via space `settings/attenuations/create` POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([attenuation2Data]) + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to create one attenuation via space settings/attenuations/create GET request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&source-zone-id=${attenuation2Data["source-zone-id"]}&listener-zone-id=${attenuation2Data["listener-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to get the list of attenuations + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to get a zone attenuation's settings via GET request + // // Create 2 attenuations to test against + // try { + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminTokenNoSpace}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify([attenuation1Data, attenuation2Data]) + // }); + // responseJSON = await returnMessage.json(); + // attenuation1Data['id'] = responseJSON[0]['id']; + // attenuation1Data['id'] = responseJSON[1]['id']; + // } catch (e) { + // console.error("Failed to create an attenuation before tests for nonadmins to edit attenuations."); + // } + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to change a zone attenuation's settings via GET request + // attenuation1Data['attenuation'] = -6; + // attenuation1Data['listener-zone-id'] = zone2Data.id; + // attenuation1Data['source-zone-id'] = zone3Data.id; + // attenuation1Data['za-offset'] = 20; + + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to get a zone attenuation's settings via POST request + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: '{}' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to change a zone attenuation's settings via POST request + // let attenuationID = attenuation1Data.id; + // attenuation1Data = { + // "attenuation": 0.8, + // "listener-zone-id": zone3Data.id, + // "source-zone-id": zone4Data.id, + // "za-offset": -7 + // } + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${nonAdminToken}`, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // }, + // body: JSON.stringify(attenuation1Data) + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + // attenuation1Data.id = attenuationID; + + // // Try to delete one zone attenuation + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { + // method: 'DELETE' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to delete all zone attenuations + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { + // method: 'DELETE' + // }); + // responseJSON = await returnMessage.json(); + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to delete one zone + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { + // method: 'DELETE' + // }); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Try to delete all zones + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { + // method: 'DELETE' + // }); + // responseJSON = await returnMessage.json(); + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); + // responseJSON = await returnMessage.json(); + // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // // Clean up + // try { + // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + // method: 'DELETE' + // }); + // } catch (e) { + // console.error("Failed to clean up zones after testing."); + // } + // }); + // }); + + // describe.skip('User data access', () => { + // let spaceID: string; + // let adminToken: string; + // let adminID: string; + // let adminVisitIDHash: string; + // let nonadminToken: string; + // let nonadminID: string; + // let nonadminVisitIDHash: string; + // let testUserAdmin: TestUser; + // let testUserNonadmin: TestUser; + + // beforeAll(async () => { + // jest.setTimeout(15000); // these tests need longer to complete + // try { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + // let returnMessageJSON: any = {}; + // returnMessageJSON = await returnMessage.json(); + // spaceID = returnMessageJSON['space-id']; + + // // connect an admin test user to the space + // let tokenData = tokenTypes.ADMIN_ID_APP1; + // tokenData['user_id'] = generateUUID(); + // testUserAdmin = new TestUser(tokenData['user_id']); + // adminToken = await generateJWT(tokenData, spaceID); + // await testUserAdmin.communicator.connectToHiFiAudioAPIServer(adminToken, stackURL) + // .then(data => { + // adminID = data.audionetInitResponse.user_id; + // adminVisitIDHash = data.audionetInitResponse.visit_id_hash; + // }); + // expect(testUserAdmin.connectionState).toBe(HiFiConnectionStates.Connected); + + // // connect a nonadmin test user to the space + // tokenData = tokenTypes.NONADMIN_ID_APP1; + // tokenData['user_id'] = generateUUID(); + // testUserNonadmin = new TestUser(tokenData['user_id']); + // nonadminToken = await generateJWT(tokenData, spaceID); + // await testUserNonadmin.communicator.connectToHiFiAudioAPIServer(nonadminToken, stackURL) + // .then(data => { + // nonadminID = data.audionetInitResponse.user_id; + // nonadminVisitIDHash = data.audionetInitResponse.visit_id_hash; + // }); + // expect(testUserNonadmin.connectionState).toBe(HiFiConnectionStates.Connected); + // } catch (err) { + // console.error("Could not set up a space and connect users to test user data access."); + // } + // }); + + // afterAll(async () => { + // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + // method: 'DELETE' + // }); + // jest.setTimeout(5000); // restore to default + // }); + + // test('Admin CAN access list of users', async () => { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); + // let returnMessageJSON = await returnMessage.json(); + // expect(returnMessageJSON.length).toBe(2); + // let returnedAdmin = returnMessageJSON.filter((obj: { user_id: string; }) => { + // return obj['user_id'] === adminID; + // }) + // expect(returnedAdmin).toBeDefined(); + + // let returnedNonadmin = returnMessageJSON.filter((obj: { user_id: string; }) => { + // return obj['user_id'] === nonadminID; + // }) + // expect(returnedNonadmin).toBeDefined(); + // }); + + // test('Nonadmin CANNOT access list of users', async () => { + // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonadminToken}`); + // let returnMessageJSON = await returnMessage.json(); + // expect(returnMessageJSON.errors.description).toBe(`token isn't an admin token`); + // }); + // }); }); \ No newline at end of file diff --git a/tests/testUtilities/TestUser.ts b/tests/testUtilities/TestUser.ts index cce2b09a..604c1483 100644 --- a/tests/testUtilities/TestUser.ts +++ b/tests/testUtilities/TestUser.ts @@ -1,3 +1,4 @@ +import { HiFiAudioAPIData, Point3D } from "../../src/classes/HiFiAudioAPIData"; import { HiFiCommunicator, HiFiConnectionStates } from "../../src/classes/HiFiCommunicator"; export class TestUser { @@ -5,13 +6,23 @@ export class TestUser { connectionState: HiFiConnectionStates; communicator: HiFiCommunicator; - constructor(name: string) { + constructor(name: string, xPosition: number) { this.name = name; this.connectionState = HiFiConnectionStates.Disconnected; - this.communicator = new HiFiCommunicator({ onConnectionStateChanged: this.onConnectionStateChanged.bind(this) }); + let initialAudioData = new HiFiAudioAPIData({ position: new Point3D({ x: xPosition }) }); + this.communicator = new HiFiCommunicator({ + initialHiFiAudioAPIData: initialAudioData, + onConnectionStateChanged: this.onConnectionStateChanged.bind(this), + onMuteChanged: this.onMuteChanged.bind(this), + }); } onConnectionStateChanged(connectionState: HiFiConnectionStates) { + console.log("______________________ connectionState __________", connectionState); this.connectionState = connectionState; } + + onMuteChanged(data:any) { + console.log("______________________ User mute change: ", this.name, "__________State:", data); + } } \ No newline at end of file From 242f96247759b1b81d50ed19e8cc11aad88721e8 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Mon, 17 May 2021 15:25:55 -0700 Subject: [PATCH 05/32] Make it so users don't need to install wget --- src/classes/HiFiMixerSession.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/classes/HiFiMixerSession.ts b/src/classes/HiFiMixerSession.ts index 9ec42e6d..70bfd19f 100644 --- a/src/classes/HiFiMixerSession.ts +++ b/src/classes/HiFiMixerSession.ts @@ -485,10 +485,8 @@ export class HiFiMixerSession { if (instructionName === "mute") { let shouldBeMuted: boolean; if (instructionArguments.length >= 1) { - if (instructionArguments[0] === 1) { - shouldBeMuted = true; - } else if (instructionArguments[0] === 0) { - shouldBeMuted = false; + if (typeof(instructionArguments[0]) === "boolean") { + shouldBeMuted = instructionArguments[0]; } } if (shouldBeMuted !== undefined) { From fde327ee49152cf4ce0b347c245575575621d735 Mon Sep 17 00:00:00 2001 From: hifibuild Date: Tue, 18 May 2021 01:58:48 +0000 Subject: [PATCH 06/32] Bump package version to 1.1.2-0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f24824c8..5422c0b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.1", + "version": "1.1.2-0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index f744ee61..0d93c890 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.1", + "version": "1.1.2-0", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 966d13923c69c485a6fc83cdfd1aa8384bd58b71 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Tue, 18 May 2021 13:07:40 -0700 Subject: [PATCH 07/32] Mute tests...sssh --- tests/smoke/clientAudio.smoke.test.ts | 4 +- tests/smoke/rest.smoke.test.ts | 1908 +++++++++++++------------ tests/testUtilities/TestUser.ts | 18 +- 3 files changed, 1001 insertions(+), 929 deletions(-) diff --git a/tests/smoke/clientAudio.smoke.test.ts b/tests/smoke/clientAudio.smoke.test.ts index c331f304..8d7fe09a 100644 --- a/tests/smoke/clientAudio.smoke.test.ts +++ b/tests/smoke/clientAudio.smoke.test.ts @@ -128,13 +128,13 @@ describe('Audio', () => { expect(usersDataArray[usersDataArray.findIndex((userData: UserData) => userData.hashedVisitID === user1HashedVisitID)].volumeDecibels).toBe(0); // user 1 can mute, user 2 stops hearing user 1 - user1.setInputAudioMuted(true); + await user1.setInputAudioMuted(true); await sleep(1000); // what is the right value here? expect(usersDataArray[usersDataArray.findIndex((userData: UserData) => userData.hashedVisitID === user1HashedVisitID)].volumeDecibels).toBeLessThan(-60); // user 1 can unmute, user 2 can hear user 1 again - user1.setInputAudioMuted(false); + await user1.setInputAudioMuted(false); await sleep(1000); expect(usersDataArray[usersDataArray.findIndex((userData: UserData) => userData.hashedVisitID === user1HashedVisitID)].volumeDecibels).toBe(0); diff --git a/tests/smoke/rest.smoke.test.ts b/tests/smoke/rest.smoke.test.ts index 22dd4ff6..140f1854 100644 --- a/tests/smoke/rest.smoke.test.ts +++ b/tests/smoke/rest.smoke.test.ts @@ -41,7 +41,7 @@ describe('HiFi API REST Calls', () => { nonadminTokenNoSpace = await generateJWT(tokenTypes.NONADMIN_ID_APP1); }); - describe.skip('App spaces', () => { + describe('App spaces', () => { let spaceID: string; let adminToken: string; let nonAdminToken: string; @@ -335,109 +335,7 @@ describe('HiFi API REST Calls', () => { }); }); - // describe.skip('Kicking users', () => { - // const numberTestUsers = 4; - // let testUsers: Array = []; - // let spaceID: string; - // let adminToken: string; - // let nonAdminToken: string; - - // beforeAll(async () => { - // jest.setTimeout(35000); // these tests need longer to complete - // try { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // spaceID = returnMessageJSON['space-id']; - // adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); - // nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); - // } catch (e) { - // console.error("Failed to create a space before tests for kicking."); - // } - // }); - - // afterAll(async () => { - // jest.setTimeout(5000); // restore to default - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { - // method: 'DELETE' - // }); - // }); - - // beforeEach(async () => { - // testUsers = []; - // for (let i = 0; i < numberTestUsers; i++) { - // let tokenData = tokenTypes.NONADMIN_ID_APP1 - // tokenData['user_id'] = generateUUID(); - // testUsers.push(new TestUser(tokenData['user_id'])); - // let token = await generateJWT(tokenData, spaceID); - // await testUsers[i].communicator.connectToHiFiAudioAPIServer(token, stackURL); - // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - // } - // }); - - // afterEach(async () => { - // // disconnect communicators to avoid using too many mixers - // for (let i = 0; i < numberTestUsers; i++) { - // await testUsers[i].communicator.disconnectFromHiFiAudioAPIServer(); - // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Disconnected); - // } - // }); - - // describe('Admin CAN kick users', () => { - // test(`Kick one user`, async () => { - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { - // method: 'DELETE' - // }); - // await sleep(30000); - // for (let i = 0; i < numberTestUsers; i++) { - // if (i === 0) expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); - // else expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - // } - // }); - - // test(`Kick all users`, async () => { - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { - // method: 'DELETE' - // }); - // await sleep(30000); - // for (let i = 0; i < numberTestUsers; i++) { - // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); - // } - // }); - // }); - - // describe('Nonadmin CANNOT kick users', () => { - // test(`Kick one user`, async () => { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { - // method: 'DELETE' - // }); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // await sleep(30000); - // expect(returnMessageJSON.code).toBe(401); - // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); - // for (let i = 0; i < numberTestUsers; i++) { - // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - // } - // }); - - // test(`Kick all users`, async () => { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonAdminToken}`, { - // method: 'DELETE' - // }); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // await sleep(30000); - // expect(returnMessageJSON.code).toBe(401); - // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); - // for (let i = 0; i < numberTestUsers; i++) { - // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - // } - // }); - // }); - // }); - - describe.only('Muting users', () => { + describe('Kicking users', () => { const numberTestUsers = 4; let testUsers: Array = []; let spaceID: string; @@ -447,47 +345,137 @@ describe('HiFi API REST Calls', () => { beforeAll(async () => { jest.setTimeout(35000); // these tests need longer to complete try { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // spaceID = returnMessageJSON['space-id']; - spaceID = '4a34ad54-d5ae-42e7-b52b-9fa67a8b1847'; - // adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); - adminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfaWQiOiJjMzBjZWQ2Yi0xODU1LTQ0OTYtYjRjNS02MzE3ZTZkNThmOTEiLCJ1c2VyX2lkIjoiYSIsImFkbWluIjp0cnVlLCJkZXZlbG9wZXJfaWQiOiJhYzUwOWQxMC03N2U0LTQxYWUtYWIyMy02MGYzOWFiYTgwYTYiLCJzdGFjayI6ImF1ZGlvbmV0LW1peGVyLWFwaS1zdGFnaW5nLTE5In0.tP2yhh20l4NL0tYfyAcOLGkxHO3QSGjeIyephozCAsw'; - // nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); - nonAdminToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfaWQiOiJjMzBjZWQ2Yi0xODU1LTQ0OTYtYjRjNS02MzE3ZTZkNThmOTEiLCJ1c2VyX2lkIjoiYiIsInNwYWNlX2lkIjoiNGEzNGFkNTQtZDVhZS00MmU3LWI1MmItOWZhNjdhOGIxODQ3Iiwic3RhY2siOiJhdWRpb25ldC1taXhlci1hcGktc3RhZ2luZy0xOSJ9.VMNCPH-8c96sqvPe6QEhRLi2PSdLNwCIyDL7rhqUMU0'; + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + spaceID = returnMessageJSON['space-id']; + adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); + } catch (e) { + console.error("Failed to create a space before tests for kicking."); + } + }); + + afterAll(async () => { + jest.setTimeout(5000); // restore to default + await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + method: 'DELETE' + }); + }); + + beforeEach(async () => { + testUsers = []; + for (let i = 0; i < numberTestUsers; i++) { + let tokenData = tokenTypes.NONADMIN_ID_APP1 + tokenData['user_id'] = generateUUID(); + testUsers.push(new TestUser(tokenData['user_id'])); + let token = await generateJWT(tokenData, spaceID); + await testUsers[i].communicator.connectToHiFiAudioAPIServer(token, stackURL); + expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + } + }); + + afterEach(async () => { + // disconnect communicators to avoid using too many mixers + for (let i = 0; i < numberTestUsers; i++) { + await testUsers[i].communicator.disconnectFromHiFiAudioAPIServer(); + expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Disconnected); + } + }); + + describe('Admin CAN kick users', () => { + test(`Kick one user`, async () => { + await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { + method: 'DELETE' + }); + await sleep(30000); + for (let i = 0; i < numberTestUsers; i++) { + if (i === 0) expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); + else expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + } + }); + + test(`Kick all users`, async () => { + await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { + method: 'DELETE' + }); + await sleep(30000); + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); + } + }); + }); + + describe('Nonadmin CANNOT kick users', () => { + test(`Kick one user`, async () => { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { + method: 'DELETE' + }); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + await sleep(30000); + expect(returnMessageJSON.code).toBe(401); + expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + } + }); + + test(`Kick all users`, async () => { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonAdminToken}`, { + method: 'DELETE' + }); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + await sleep(30000); + expect(returnMessageJSON.code).toBe(401); + expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); + } + }); + }); + }); + + describe('Muting users', () => { + const numberTestUsers = 4; + let testUsers: Array = []; + let spaceID: string; + let adminToken: string; + let nonAdminToken: string; + + beforeAll(async () => { + jest.setTimeout(30000); // these tests need longer to complete + try { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + spaceID = returnMessageJSON['space-id']; + adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); } catch (e) { console.error("Failed to create a space before tests for muting."); } }); afterAll(async () => { - // jest.setTimeout(5000); // restore to default - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { - // method: 'DELETE' - // }); + jest.setTimeout(5000); // restore to default + await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + method: 'DELETE' + }); }); beforeEach(async () => { testUsers = []; - let notesHz = [110, 130.81, 164.81, 196]; for (let i = 0; i < numberTestUsers; i++) { - // let tokenData = tokenTypes.NONADMIN_ID_APP1; - let tokenData = { - "admin": false, - "signed": true, - "user_id": "qateamNonAdmin", - "app_id": 'c30ced6b-1855-4496-b4c5-6317e6d58f91', - "app_secret": '881ea557-1ab5-4a7b-8d08-d59e13fb26aa' - }; + let tokenData = tokenTypes.NONADMIN_ID_APP1; tokenData['user_id'] = generateUUID(); - testUsers.push(new TestUser(tokenData['user_id'], i + 1 * 3)); + testUsers.push(new TestUser(tokenData['user_id'])); let token = await generateJWT(tokenData, spaceID); - console.log("__________________USER_______________", tokenData['user_id'], "_____________", token); await testUsers[i].communicator.connectToHiFiAudioAPIServer(token, stackURL); expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - let source = new RTCAudioSourceSineWave({ frequency: notesHz[i] }); + let source = new RTCAudioSourceSineWave({ frequency: 330 }); let track = source.createTrack(); let inputAudioMediaStream = new MediaStream([track]); testUsers[i].communicator.setInputAudioMediaStream(inputAudioMediaStream); @@ -497,32 +485,111 @@ describe('HiFi API REST Calls', () => { afterEach(async () => { // disconnect communicators to avoid using too many mixers for (let i = 0; i < numberTestUsers; i++) { - console.log("____________DISCONNECT__________________"); await testUsers[i].communicator.disconnectFromHiFiAudioAPIServer(); expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Disconnected); } }); describe('Admin CAN mute/unmute users', () => { - test.only(`Mute one user`, async () => { - await sleep(10000); - console.log("__________________MUTE_______________", `${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); - - let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { + test(`Mute one user`, async () => { + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + + // Admin mutes one user + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mute: true }) }); - let responseJSON: any = {}; responseJSON = await returnMessage.json(); - console.log("__________mute response___________", responseJSON); + expect(responseJSON.status).toBe("ok"); + + await sleep(2000); + // User is muted, other users are not muted + for (let i = 0; i < numberTestUsers; i++) { + if (i === 0) expect(testUsers[i].muteState).toBe("MUTED_FIXED"); + else expect(testUsers[i].muteState).toBe("UNMUTED"); + } + + // Muted user tries to unmute + returnMessage = await testUsers[0].communicator.setInputAudioMuted(false); + expect(returnMessage).toBe(false); + + // User is still muted and fixed, other users are not muted + for (let i = 0; i < numberTestUsers; i++) { + if (i === 0) expect(testUsers[i].muteState).toBe("MUTED_FIXED"); + else expect(testUsers[i].muteState).toBe("UNMUTED"); + } + + // Admin unmutes muted user + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ mute: false }) + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.status).toBe("ok"); + + await sleep(2000); + // User is still muted, but not fixed, other users are not muted + for (let i = 0; i < numberTestUsers; i++) { + if (i === 0) expect(testUsers[i].muteState).toBe("MUTED_NOT_FIXED"); + else expect(testUsers[i].muteState).toBe("UNMUTED"); + } + + // Muted user tries to unmute + returnMessage = await testUsers[0].communicator.setInputAudioMuted(false); + expect(returnMessage).toBe(true); + + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + }); - await sleep(10000); - console.log("__________________UNMUTE_______________", `${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); + test(`Mute all users`, async () => { + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + // Admin mutes all users + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ mute: true }) + }); + let responseJSON: any = {}; + responseJSON = await returnMessage.json(); + expect(responseJSON.status).toBe("ok"); + + await sleep(2000); + // All users are muted, fixed + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("MUTED_FIXED"); + } + + // Users try to unmute + for (let i = 0; i < numberTestUsers; i++) { + returnMessage = await testUsers[0].communicator.setInputAudioMuted(false); + expect(returnMessage).toBe(false); + } + + // Users are still muted and fixed + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("MUTED_FIXED"); + } + + // Admin unmutes all users returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { method: 'POST', headers: { @@ -530,793 +597,794 @@ describe('HiFi API REST Calls', () => { }, body: JSON.stringify({ mute: false }) }); + responseJSON = await returnMessage.json(); + expect(responseJSON.status).toBe("ok"); + + await sleep(2000); + // Users are still muted, but not fixed + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("MUTED_NOT_FIXED"); + } + + // Users try to unmute + for (let i = 0; i < numberTestUsers; i++) { + returnMessage = await testUsers[i].communicator.setInputAudioMuted(false); + expect(returnMessage).toBe(true); + } + + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + }); + }); + + describe('Nonadmin CANNOT mute/unmute users', () => { + test(`Mute one user`, async () => { + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + + // Nonadmin tries to mute one user + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ mute: true }) + }); + let responseJSON: any = {}; + responseJSON = await returnMessage.json(); + expect(responseJSON.status).toBe("Unauthorized"); + + await sleep(2000); + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + }); + + test(`Mute all users`, async () => { + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + // Nonadmin tries to mute all users + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ mute: true }) + }); + let responseJSON: any = {}; responseJSON = await returnMessage.json(); - console.log("__________mute response___________", responseJSON); - await sleep(10000); - // for (let i = 0; i < numberTestUsers; i++) { - // if (i === 0) expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); - // else expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - // } - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify({ mute: false }) - // }) - // .then((data: any) => { - // console.log("__________________DATA_______________", data); - // }); + expect(responseJSON.status).toBe("Unauthorized"); + + await sleep(2000); + // All users are unmuted + for (let i = 0; i < numberTestUsers; i++) { + expect(testUsers[i].muteState).toBe("UNMUTED"); + } + }); + }); + }); + + describe('Wrong admin tokens', () => { + describe(`CANNOT read/alter App A by using a valid admin token for App B`, () => { + let spaceID: string; + let adminTokenApp1: string; + let adminTokenApp2: string; + beforeAll(async () => { + try { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + spaceID = returnMessageJSON['space-id']; + adminTokenApp1 = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + adminTokenApp2 = await generateJWT(tokenTypes.ADMIN_ID_APP2); + } catch (err) { + console.error("Failed to create spaces before tests for wrong admin."); + } + }); + + afterAll(async () => { + await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminTokenApp1}`, { + method: 'DELETE' + }); + }); + + test(`Read settings for a space`, async () => { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/app-id/?token=${adminTokenApp2}`); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + expect(returnMessageJSON.code).toBe(422); + expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/space\/app mismatch/) }); + }); + }); + }); + + describe('Zones and attenuations', () => { + let spaceID: string; + let adminToken: string; + let nonAdminToken: string; + let zone1Data: ZoneData; + let zone2Data: ZoneData; + let zone3Data: ZoneData; + let zone4Data: ZoneData; + + beforeAll(async () => { + jest.setTimeout(20000); // these tests need longer to complete + // Create a space for testing + try { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + spaceID = returnMessageJSON['space-id']; + adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); + nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); + } catch (e) { + console.error("Failed to create a space before tests for zones and attenuations."); + } + + zone1Data = { + "x-min": -5, + "x-max": 5, + "y-min": 0, + "y-max": 10, + "z-min": -5, + "z-max": 5, + "name": generateUUID() + }; + zone2Data = { + "x-min": 5, + "x-max": 15, + "y-min": 0, + "y-max": 10, + "z-min": -5, + "z-max": 5, + "name": generateUUID() + }; + zone3Data = { + "x-min": 15, + "x-max": 25, + "y-min": 0, + "y-max": 10, + "z-min": -5, + "z-max": 5, + "name": generateUUID() + }; + zone4Data = { + "x-min": 25, + "x-max": 35, + "y-min": 0, + "y-max": 10, + "z-min": -5, + "z-max": 5, + "name": generateUUID() + }; + }); + + afterAll(async () => { + jest.setTimeout(5000); // restore to default + await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + method: 'DELETE' + }); + }); + + beforeEach(async () => { + try { + await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + method: 'DELETE' + }); + } catch (err) { + console.error("Failed to delete all zones before test. Please manually remove them and then rerun the test."); + } + }); + + test(`Admin CAN access, edit, create, and delete zones and attenuations`, async () => { + // Create multiple zones via space `settings/zones` POST request + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([zone1Data, zone2Data]) + }); + + // Response will be the zone datas plus a new zone ID for each zone. Add the IDs to our data and the response should match + let responseJSON: any = {}; + responseJSON = await returnMessage.json(); + expect(responseJSON[0].id).toBeDefined(); + expect(responseJSON[1].id).toBeDefined(); + if (responseJSON[0].name === zone1Data.name) { + zone1Data['id'] = responseJSON[0].id; + zone2Data['id'] = responseJSON[1].id; + } else { + zone1Data['id'] = responseJSON[1].id; + zone2Data['id'] = responseJSON[0].id; + } + expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data].map(a => a.id).sort()); + + // Create one zone via space `settings/zones/create` POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([zone3Data]) + }); + responseJSON = await returnMessage.json(); + expect(responseJSON[0].id).toBeDefined(); + zone3Data['id'] = responseJSON[0].id; + expect(responseJSON).toEqual([zone3Data]); + + // Create one zone via space settings/zones/create GET request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${adminToken}&x-min=${zone4Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.id).toBeDefined(); + zone4Data['id'] = responseJSON.id; + expect(responseJSON).toEqual(zone4Data); + + // Get the list of zones and make sure it is accurate + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data, zone3Data, zone4Data].map(a => a.id).sort()); + + + // Get a zone's settings via GET request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual(zone1Data); + + // Change a zone's settings via GET request + zone1Data['x-min'] = -6; + zone1Data['x-max'] = 6; + zone1Data['y-min'] = 10; + zone1Data['y-max'] = 20; + zone1Data['z-min'] = -6; + zone1Data['z-max'] = -6; + zone1Data['name'] = generateUUID(); + + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual(zone1Data); + + // Get a zone's settings via POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: '{}' + }); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual(zone1Data); + + // Change a zone's settings via POST request + let zoneID = zone1Data.id; + zone1Data = { + "x-min": -7, + "x-max": 7, + "y-min": 0, + "y-max": 10, + "z-min": -7, + "z-max": 7, + "name": generateUUID() + }; + + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(zone1Data) + }); + responseJSON = await returnMessage.json(); + zone1Data.id = zoneID; + expect(responseJSON).toEqual(zone1Data); + + // Create multiple attenuations via space `settings/attenuations` POST request + let attenuation1Data: AttenuationData; + let attenuation2Data: AttenuationData; + let attenuation3Data: AttenuationData; + let attenuation4Data: AttenuationData; + attenuation1Data = { + "attenuation": 0.5, + "listener-zone-id": zone1Data.id, + "source-zone-id": zone2Data.id, + "za-offset": -5 + }; + attenuation2Data = { + "attenuation": 0.5, + "listener-zone-id": zone1Data.id, + "source-zone-id": zone2Data.id, + "za-offset": -5 + }; + attenuation3Data = { + "attenuation": 0.5, + "listener-zone-id": zone1Data.id, + "source-zone-id": zone3Data.id, + "za-offset": -5 + }; + attenuation4Data = { + "attenuation": 0.5, + "listener-zone-id": zone1Data.id, + "source-zone-id": zone4Data.id, + "za-offset": -5 + }; + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([attenuation1Data, attenuation2Data]) + }); + + // Response will be the attenuation datas plus a new attenuation ID for each attenuation. Add the IDs to our data and the response should match + responseJSON = await returnMessage.json(); + expect(responseJSON[0]['id']).toBeDefined(); + expect(responseJSON[1]['id']).toBeDefined(); + attenuation1Data['id'] = responseJSON[0]['id']; + attenuation2Data['id'] = responseJSON[1]['id']; + expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data].map(a => a.id).sort()); + + // Create one attenuation via space `settings/attenuations/create` POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([attenuation3Data]) + }); + responseJSON = await returnMessage.json(); + expect(responseJSON[0]['id']).toBeDefined(); + attenuation3Data['id'] = responseJSON[0]['id']; + expect(responseJSON).toEqual([attenuation3Data]); + + // Create one attenuation via space settings/attenuations/create GET request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${adminToken}&attenuation=${attenuation4Data["attenuation"]}&source-zone-id=${attenuation4Data["source-zone-id"]}&listener-zone-id=${attenuation4Data["listener-zone-id"]}&za-offset=${attenuation4Data["za-offset"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON['id']).toBeDefined(); + attenuation4Data['id'] = responseJSON['id']; + expect(responseJSON).toEqual(attenuation4Data); + + // Get the list of attenuations and make sure it is accurate + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data, attenuation3Data, attenuation4Data].map(a => a.id).sort()); + + // Get a zone attenuation's settings via GET request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual(attenuation1Data); + + // Change a zone attenuation's settings via GET request + attenuation1Data['attenuation'] = -6; + attenuation1Data['listener-zone-id'] = zone2Data.id; + attenuation1Data['source-zone-id'] = zone3Data.id; + attenuation1Data['za-offset'] = 20; + + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual(attenuation1Data); + + // Get a zone attenuation's settings via POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: '{}' + }); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual(attenuation1Data); + + // Change a zone attenuation's settings via POST request + let attenuationID = attenuation1Data.id; + attenuation1Data = { + "attenuation": 0.8, + "listener-zone-id": zone3Data.id, + "source-zone-id": zone4Data.id, + "za-offset": -7 + } + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${adminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(attenuation1Data) + }); + responseJSON = await returnMessage.json(); + attenuation1Data.id = attenuationID; + expect(responseJSON).toEqual(attenuation1Data); + + // Delete one zone attenuation + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { + method: 'DELETE' + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.id).toBe(attenuation1Data.id); + + // Delete all zone attenuations + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { + method: 'DELETE' + }); + responseJSON = await returnMessage.json(); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual([]); + + // Delete one zone + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { + method: 'DELETE' }); + responseJSON = await returnMessage.json(); + expect(responseJSON.id).toBe(zone1Data.id); - // test(`Mute all users`, async () => { - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify({ mute: true }) - // }); - // await sleep(30000); - // // for (let i = 0; i < numberTestUsers; i++) { - // // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Failed); - // // } - // }); + // Delete all zones + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + method: 'DELETE' + }); + + // check + responseJSON = await returnMessage.json(); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON).toEqual([]); }); - // describe('Nonadmin CANNOT mute users', () => { - // test(`Mute one user`, async () => { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users/${testUsers[0].name}?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify({ mute: true }) - // }); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // await sleep(30000); - // expect(returnMessageJSON.code).toBe(401); - // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); - // for (let i = 0; i < numberTestUsers; i++) { - // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - // } - // }); - - // test(`Mute all users`, async () => { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify({ mute: true }) - // }); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // await sleep(30000); - // expect(returnMessageJSON.code).toBe(401); - // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/token isn't an admin token/) }); - // for (let i = 0; i < numberTestUsers; i++) { - // expect(testUsers[i].connectionState).toBe(HiFiConnectionStates.Connected); - // } - // }); - // }); + test(`Nonadmin CANNOT access, edit, create, and delete zones and attenuations`, async () => { + // reset zone 1 and 2 as they are the only ones we will use for nonadmin testing + zone1Data = { + "x-min": -5, + "x-max": 5, + "y-min": 0, + "y-max": 10, + "z-min": -5, + "z-max": 5, + "name": generateUUID() + }; + zone2Data = { + "x-min": 5, + "x-max": 15, + "y-min": 0, + "y-max": 10, + "z-min": -5, + "z-max": 5, + "name": generateUUID() + }; + + // Try to create multiple zones via space `settings/zones` POST request + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([zone1Data, zone2Data]) + }); + let responseJSON: any = {}; + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to create one zone via space `settings/zones/create` POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([zone1Data]) + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to create one zone via space settings/zones/create GET request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to get the list of zones + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to get a zone's settings via GET request + // Create 2 zones to test against (need 2 for attenualtions) + try { + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminTokenNoSpace}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([zone1Data, zone2Data]) + }); + responseJSON = await returnMessage.json(); + if (responseJSON[0].name === zone1Data.name) { + zone1Data['id'] = responseJSON[0].id; + zone2Data['id'] = responseJSON[1].id; + } else { + zone1Data['id'] = responseJSON[1].id; + zone2Data['id'] = responseJSON[0].id; + } + } catch (e) { + console.error("Failed to create a zone before tests for nonadmins to edit zones."); + } + + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to change a zone's settings via GET request + zone1Data['x-min'] = -6; + zone1Data['x-max'] = 6; + zone1Data['y-min'] = 10; + zone1Data['y-max'] = 20; + zone1Data['z-min'] = -6; + zone1Data['z-max'] = -6; + zone1Data['name'] = generateUUID(); + + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to get a zone's settings via POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: '{}' + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to change a zone's settings via POST request + let zoneID = zone1Data.id; + zone1Data = { + "x-min": -7, + "x-max": 7, + "y-min": 0, + "y-max": 10, + "z-min": -7, + "z-max": 7, + "name": generateUUID() + }; + + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(zone1Data) + }); + responseJSON = await returnMessage.json(); + zone1Data.id = zoneID; + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to create multiple attenuations via space `settings/attenuations` POST request + let attenuation1Data: AttenuationData; + let attenuation2Data: AttenuationData; + attenuation1Data = { + "attenuation": 0.5, + "listener-zone-id": zone1Data.id, + "source-zone-id": zone2Data.id, + "za-offset": -5 + }; + attenuation2Data = { + "attenuation": 0.5, + "listener-zone-id": zone2Data.id, + "source-zone-id": zone1Data.id, + "za-offset": -5 + }; + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([attenuation1Data, attenuation2Data]) + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to create one attenuation via space `settings/attenuations/create` POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([attenuation2Data]) + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to create one attenuation via space settings/attenuations/create GET request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&source-zone-id=${attenuation2Data["source-zone-id"]}&listener-zone-id=${attenuation2Data["listener-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to get the list of attenuations + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to get a zone attenuation's settings via GET request + // Create 2 attenuations to test against + try { + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminTokenNoSpace}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify([attenuation1Data, attenuation2Data]) + }); + responseJSON = await returnMessage.json(); + attenuation1Data['id'] = responseJSON[0]['id']; + attenuation1Data['id'] = responseJSON[1]['id']; + } catch (e) { + console.error("Failed to create an attenuation before tests for nonadmins to edit attenuations."); + } + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to change a zone attenuation's settings via GET request + attenuation1Data['attenuation'] = -6; + attenuation1Data['listener-zone-id'] = zone2Data.id; + attenuation1Data['source-zone-id'] = zone3Data.id; + attenuation1Data['za-offset'] = 20; + + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to get a zone attenuation's settings via POST request + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: '{}' + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to change a zone attenuation's settings via POST request + let attenuationID = attenuation1Data.id; + attenuation1Data = { + "attenuation": 0.8, + "listener-zone-id": zone3Data.id, + "source-zone-id": zone4Data.id, + "za-offset": -7 + } + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${nonAdminToken}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(attenuation1Data) + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + attenuation1Data.id = attenuationID; + + // Try to delete one zone attenuation + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { + method: 'DELETE' + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to delete all zone attenuations + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { + method: 'DELETE' + }); + responseJSON = await returnMessage.json(); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to delete one zone + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { + method: 'DELETE' + }); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Try to delete all zones + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { + method: 'DELETE' + }); + responseJSON = await returnMessage.json(); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); + responseJSON = await returnMessage.json(); + expect(responseJSON.errors.description).toBe(`token isn't an admin token`); + + // Clean up + try { + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { + method: 'DELETE' + }); + } catch (e) { + console.error("Failed to clean up zones after testing."); + } + }); }); - // describe.skip('Wrong admin tokens', () => { - // describe(`CANNOT read/alter App A by using a valid admin token for App B`, () => { - // let spaceID: string; - // let adminTokenApp1: string; - // let adminTokenApp2: string; - // beforeAll(async () => { - // try { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // spaceID = returnMessageJSON['space-id']; - // adminTokenApp1 = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); - // adminTokenApp2 = await generateJWT(tokenTypes.ADMIN_ID_APP2); - // } catch (err) { - // console.error("Failed to create spaces before tests for wrong admin."); - // } - // }); - - // afterAll(async () => { - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminTokenApp1}`, { - // method: 'DELETE' - // }); - // }); - - // test(`Read settings for a space`, async () => { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/app-id/?token=${adminTokenApp2}`); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // expect(returnMessageJSON.code).toBe(422); - // expect(returnMessageJSON.errors).toMatchObject({ description: expect.stringMatching(/space\/app mismatch/) }); - // }); - // }); - // }); - - // describe.skip('Zones and attenuations', () => { - // let spaceID: string; - // let adminToken: string; - // let nonAdminToken: string; - // let zone1Data: ZoneData; - // let zone2Data: ZoneData; - // let zone3Data: ZoneData; - // let zone4Data: ZoneData; - - // beforeAll(async () => { - // jest.setTimeout(20000); // these tests need longer to complete - // // Create a space for testing - // try { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // spaceID = returnMessageJSON['space-id']; - // adminToken = await generateJWT(tokenTypes.ADMIN_ID_APP1, spaceID); - // nonAdminToken = await generateJWT(tokenTypes.NONADMIN_ID_APP1, spaceID); - // } catch (e) { - // console.error("Failed to create a space before tests for zones and attenuations."); - // } - - // zone1Data = { - // "x-min": -5, - // "x-max": 5, - // "y-min": 0, - // "y-max": 10, - // "z-min": -5, - // "z-max": 5, - // "name": generateUUID() - // }; - // zone2Data = { - // "x-min": 5, - // "x-max": 15, - // "y-min": 0, - // "y-max": 10, - // "z-min": -5, - // "z-max": 5, - // "name": generateUUID() - // }; - // zone3Data = { - // "x-min": 15, - // "x-max": 25, - // "y-min": 0, - // "y-max": 10, - // "z-min": -5, - // "z-max": 5, - // "name": generateUUID() - // }; - // zone4Data = { - // "x-min": 25, - // "x-max": 35, - // "y-min": 0, - // "y-max": 10, - // "z-min": -5, - // "z-max": 5, - // "name": generateUUID() - // }; - // }); - - // afterAll(async () => { - // jest.setTimeout(5000); // restore to default - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { - // method: 'DELETE' - // }); - // }); - - // beforeEach(async () => { - // try { - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - // method: 'DELETE' - // }); - // } catch (err) { - // console.error("Failed to delete all zones before test. Please manually remove them and then rerun the test."); - // } - // }); - - // test(`Admin CAN access, edit, create, and delete zones and attenuations`, async () => { - // // Create multiple zones via space `settings/zones` POST request - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([zone1Data, zone2Data]) - // }); - - // // Response will be the zone datas plus a new zone ID for each zone. Add the IDs to our data and the response should match - // let responseJSON: any = {}; - // responseJSON = await returnMessage.json(); - // expect(responseJSON[0].id).toBeDefined(); - // expect(responseJSON[1].id).toBeDefined(); - // if (responseJSON[0].name === zone1Data.name) { - // zone1Data['id'] = responseJSON[0].id; - // zone2Data['id'] = responseJSON[1].id; - // } else { - // zone1Data['id'] = responseJSON[1].id; - // zone2Data['id'] = responseJSON[0].id; - // } - // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data].map(a => a.id).sort()); - - // // Create one zone via space `settings/zones/create` POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([zone3Data]) - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON[0].id).toBeDefined(); - // zone3Data['id'] = responseJSON[0].id; - // expect(responseJSON).toEqual([zone3Data]); - - // // Create one zone via space settings/zones/create GET request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${adminToken}&x-min=${zone4Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.id).toBeDefined(); - // zone4Data['id'] = responseJSON.id; - // expect(responseJSON).toEqual(zone4Data); - - // // Get the list of zones and make sure it is accurate - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data, zone3Data, zone4Data].map(a => a.id).sort()); - - - // // Get a zone's settings via GET request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual(zone1Data); - - // // Change a zone's settings via GET request - // zone1Data['x-min'] = -6; - // zone1Data['x-max'] = 6; - // zone1Data['y-min'] = 10; - // zone1Data['y-max'] = 20; - // zone1Data['z-min'] = -6; - // zone1Data['z-max'] = -6; - // zone1Data['name'] = generateUUID(); - - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual(zone1Data); - - // // Get a zone's settings via POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: '{}' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual(zone1Data); - - // // Change a zone's settings via POST request - // let zoneID = zone1Data.id; - // zone1Data = { - // "x-min": -7, - // "x-max": 7, - // "y-min": 0, - // "y-max": 10, - // "z-min": -7, - // "z-max": 7, - // "name": generateUUID() - // }; - - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify(zone1Data) - // }); - // responseJSON = await returnMessage.json(); - // zone1Data.id = zoneID; - // expect(responseJSON).toEqual(zone1Data); - - // // Create multiple attenuations via space `settings/attenuations` POST request - // let attenuation1Data: AttenuationData; - // let attenuation2Data: AttenuationData; - // let attenuation3Data: AttenuationData; - // let attenuation4Data: AttenuationData; - // attenuation1Data = { - // "attenuation": 0.5, - // "listener-zone-id": zone1Data.id, - // "source-zone-id": zone2Data.id, - // "za-offset": -5 - // }; - // attenuation2Data = { - // "attenuation": 0.5, - // "listener-zone-id": zone1Data.id, - // "source-zone-id": zone2Data.id, - // "za-offset": -5 - // }; - // attenuation3Data = { - // "attenuation": 0.5, - // "listener-zone-id": zone1Data.id, - // "source-zone-id": zone3Data.id, - // "za-offset": -5 - // }; - // attenuation4Data = { - // "attenuation": 0.5, - // "listener-zone-id": zone1Data.id, - // "source-zone-id": zone4Data.id, - // "za-offset": -5 - // }; - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([attenuation1Data, attenuation2Data]) - // }); - - // // Response will be the attenuation datas plus a new attenuation ID for each attenuation. Add the IDs to our data and the response should match - // responseJSON = await returnMessage.json(); - // expect(responseJSON[0]['id']).toBeDefined(); - // expect(responseJSON[1]['id']).toBeDefined(); - // attenuation1Data['id'] = responseJSON[0]['id']; - // attenuation2Data['id'] = responseJSON[1]['id']; - // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data].map(a => a.id).sort()); - - // // Create one attenuation via space `settings/attenuations/create` POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([attenuation3Data]) - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON[0]['id']).toBeDefined(); - // attenuation3Data['id'] = responseJSON[0]['id']; - // expect(responseJSON).toEqual([attenuation3Data]); - - // // Create one attenuation via space settings/attenuations/create GET request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${adminToken}&attenuation=${attenuation4Data["attenuation"]}&source-zone-id=${attenuation4Data["source-zone-id"]}&listener-zone-id=${attenuation4Data["listener-zone-id"]}&za-offset=${attenuation4Data["za-offset"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON['id']).toBeDefined(); - // attenuation4Data['id'] = responseJSON['id']; - // expect(responseJSON).toEqual(attenuation4Data); - - // // Get the list of attenuations and make sure it is accurate - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data, attenuation3Data, attenuation4Data].map(a => a.id).sort()); - - // // Get a zone attenuation's settings via GET request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual(attenuation1Data); - - // // Change a zone attenuation's settings via GET request - // attenuation1Data['attenuation'] = -6; - // attenuation1Data['listener-zone-id'] = zone2Data.id; - // attenuation1Data['source-zone-id'] = zone3Data.id; - // attenuation1Data['za-offset'] = 20; - - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual(attenuation1Data); - - // // Get a zone attenuation's settings via POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: '{}' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual(attenuation1Data); - - // // Change a zone attenuation's settings via POST request - // let attenuationID = attenuation1Data.id; - // attenuation1Data = { - // "attenuation": 0.8, - // "listener-zone-id": zone3Data.id, - // "source-zone-id": zone4Data.id, - // "za-offset": -7 - // } - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${adminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify(attenuation1Data) - // }); - // responseJSON = await returnMessage.json(); - // attenuation1Data.id = attenuationID; - // expect(responseJSON).toEqual(attenuation1Data); - - // // Delete one zone attenuation - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`, { - // method: 'DELETE' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.id).toBe(attenuation1Data.id); - - // // Delete all zone attenuations - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { - // method: 'DELETE' - // }); - // responseJSON = await returnMessage.json(); - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual([]); - - // // Delete one zone - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${adminToken}`, { - // method: 'DELETE' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.id).toBe(zone1Data.id); - - // // Delete all zones - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - // method: 'DELETE' - // }); - - // // check - // responseJSON = await returnMessage.json(); - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON).toEqual([]); - // }); - - // test(`Nonadmin CANNOT access, edit, create, and delete zones and attenuations`, async () => { - // // reset zone 1 and 2 as they are the only ones we will use for nonadmin testing - // zone1Data = { - // "x-min": -5, - // "x-max": 5, - // "y-min": 0, - // "y-max": 10, - // "z-min": -5, - // "z-max": 5, - // "name": generateUUID() - // }; - // zone2Data = { - // "x-min": 5, - // "x-max": 15, - // "y-min": 0, - // "y-max": 10, - // "z-min": -5, - // "z-max": 5, - // "name": generateUUID() - // }; - - // // Try to create multiple zones via space `settings/zones` POST request - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([zone1Data, zone2Data]) - // }); - // let responseJSON: any = {}; - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to create one zone via space `settings/zones/create` POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([zone1Data]) - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to create one zone via space settings/zones/create GET request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/create?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone4Data["x-max"]}&y-min=${zone4Data["y-min"]}&y-max=${zone4Data["y-max"]}&z-min=${zone4Data["z-min"]}&z-max=${zone4Data["z-max"]}&name=${zone4Data["name"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to get the list of zones - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to get a zone's settings via GET request - // // Create 2 zones to test against (need 2 for attenualtions) - // try { - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminTokenNoSpace}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([zone1Data, zone2Data]) - // }); - // responseJSON = await returnMessage.json(); - // if (responseJSON[0].name === zone1Data.name) { - // zone1Data['id'] = responseJSON[0].id; - // zone2Data['id'] = responseJSON[1].id; - // } else { - // zone1Data['id'] = responseJSON[1].id; - // zone2Data['id'] = responseJSON[0].id; - // } - // } catch (e) { - // console.error("Failed to create a zone before tests for nonadmins to edit zones."); - // } - - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to change a zone's settings via GET request - // zone1Data['x-min'] = -6; - // zone1Data['x-max'] = 6; - // zone1Data['y-min'] = 10; - // zone1Data['y-max'] = 20; - // zone1Data['z-min'] = -6; - // zone1Data['z-max'] = -6; - // zone1Data['name'] = generateUUID(); - - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}&x-min=${zone1Data["x-min"]}&x-max=${zone1Data["x-max"]}&y-min=${zone1Data["y-min"]}&y-max=${zone1Data["y-max"]}&z-min=${zone1Data["z-min"]}&z-max=${zone1Data["z-max"]}&name=${zone1Data["name"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to get a zone's settings via POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: '{}' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to change a zone's settings via POST request - // let zoneID = zone1Data.id; - // zone1Data = { - // "x-min": -7, - // "x-max": 7, - // "y-min": 0, - // "y-max": 10, - // "z-min": -7, - // "z-max": 7, - // "name": generateUUID() - // }; - - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zoneID}?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify(zone1Data) - // }); - // responseJSON = await returnMessage.json(); - // zone1Data.id = zoneID; - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to create multiple attenuations via space `settings/attenuations` POST request - // let attenuation1Data: AttenuationData; - // let attenuation2Data: AttenuationData; - // attenuation1Data = { - // "attenuation": 0.5, - // "listener-zone-id": zone1Data.id, - // "source-zone-id": zone2Data.id, - // "za-offset": -5 - // }; - // attenuation2Data = { - // "attenuation": 0.5, - // "listener-zone-id": zone2Data.id, - // "source-zone-id": zone1Data.id, - // "za-offset": -5 - // }; - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([attenuation1Data, attenuation2Data]) - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to create one attenuation via space `settings/attenuations/create` POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([attenuation2Data]) - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to create one attenuation via space settings/attenuations/create GET request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&source-zone-id=${attenuation2Data["source-zone-id"]}&listener-zone-id=${attenuation2Data["listener-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to get the list of attenuations - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to get a zone attenuation's settings via GET request - // // Create 2 attenuations to test against - // try { - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminTokenNoSpace}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify([attenuation1Data, attenuation2Data]) - // }); - // responseJSON = await returnMessage.json(); - // attenuation1Data['id'] = responseJSON[0]['id']; - // attenuation1Data['id'] = responseJSON[1]['id']; - // } catch (e) { - // console.error("Failed to create an attenuation before tests for nonadmins to edit attenuations."); - // } - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to change a zone attenuation's settings via GET request - // attenuation1Data['attenuation'] = -6; - // attenuation1Data['listener-zone-id'] = zone2Data.id; - // attenuation1Data['source-zone-id'] = zone3Data.id; - // attenuation1Data['za-offset'] = 20; - - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to get a zone attenuation's settings via POST request - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: '{}' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to change a zone attenuation's settings via POST request - // let attenuationID = attenuation1Data.id; - // attenuation1Data = { - // "attenuation": 0.8, - // "listener-zone-id": zone3Data.id, - // "source-zone-id": zone4Data.id, - // "za-offset": -7 - // } - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuationID}?token=${nonAdminToken}`, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify(attenuation1Data) - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - // attenuation1Data.id = attenuationID; - - // // Try to delete one zone attenuation - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}`, { - // method: 'DELETE' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to delete all zone attenuations - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`, { - // method: 'DELETE' - // }); - // responseJSON = await returnMessage.json(); - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${nonAdminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to delete one zone - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones/${zone1Data.id}?token=${nonAdminToken}`, { - // method: 'DELETE' - // }); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Try to delete all zones - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`, { - // method: 'DELETE' - // }); - // responseJSON = await returnMessage.json(); - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${nonAdminToken}`); - // responseJSON = await returnMessage.json(); - // expect(responseJSON.errors.description).toBe(`token isn't an admin token`); - - // // Clean up - // try { - // returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { - // method: 'DELETE' - // }); - // } catch (e) { - // console.error("Failed to clean up zones after testing."); - // } - // }); - // }); - - // describe.skip('User data access', () => { - // let spaceID: string; - // let adminToken: string; - // let adminID: string; - // let adminVisitIDHash: string; - // let nonadminToken: string; - // let nonadminID: string; - // let nonadminVisitIDHash: string; - // let testUserAdmin: TestUser; - // let testUserNonadmin: TestUser; - - // beforeAll(async () => { - // jest.setTimeout(15000); // these tests need longer to complete - // try { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); - // let returnMessageJSON: any = {}; - // returnMessageJSON = await returnMessage.json(); - // spaceID = returnMessageJSON['space-id']; - - // // connect an admin test user to the space - // let tokenData = tokenTypes.ADMIN_ID_APP1; - // tokenData['user_id'] = generateUUID(); - // testUserAdmin = new TestUser(tokenData['user_id']); - // adminToken = await generateJWT(tokenData, spaceID); - // await testUserAdmin.communicator.connectToHiFiAudioAPIServer(adminToken, stackURL) - // .then(data => { - // adminID = data.audionetInitResponse.user_id; - // adminVisitIDHash = data.audionetInitResponse.visit_id_hash; - // }); - // expect(testUserAdmin.connectionState).toBe(HiFiConnectionStates.Connected); - - // // connect a nonadmin test user to the space - // tokenData = tokenTypes.NONADMIN_ID_APP1; - // tokenData['user_id'] = generateUUID(); - // testUserNonadmin = new TestUser(tokenData['user_id']); - // nonadminToken = await generateJWT(tokenData, spaceID); - // await testUserNonadmin.communicator.connectToHiFiAudioAPIServer(nonadminToken, stackURL) - // .then(data => { - // nonadminID = data.audionetInitResponse.user_id; - // nonadminVisitIDHash = data.audionetInitResponse.visit_id_hash; - // }); - // expect(testUserNonadmin.connectionState).toBe(HiFiConnectionStates.Connected); - // } catch (err) { - // console.error("Could not set up a space and connect users to test user data access."); - // } - // }); - - // afterAll(async () => { - // await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { - // method: 'DELETE' - // }); - // jest.setTimeout(5000); // restore to default - // }); - - // test('Admin CAN access list of users', async () => { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); - // let returnMessageJSON = await returnMessage.json(); - // expect(returnMessageJSON.length).toBe(2); - // let returnedAdmin = returnMessageJSON.filter((obj: { user_id: string; }) => { - // return obj['user_id'] === adminID; - // }) - // expect(returnedAdmin).toBeDefined(); - - // let returnedNonadmin = returnMessageJSON.filter((obj: { user_id: string; }) => { - // return obj['user_id'] === nonadminID; - // }) - // expect(returnedNonadmin).toBeDefined(); - // }); - - // test('Nonadmin CANNOT access list of users', async () => { - // let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonadminToken}`); - // let returnMessageJSON = await returnMessage.json(); - // expect(returnMessageJSON.errors.description).toBe(`token isn't an admin token`); - // }); - // }); + describe('User data access', () => { + let spaceID: string; + let adminToken: string; + let adminID: string; + let adminVisitIDHash: string; + let nonadminToken: string; + let nonadminID: string; + let nonadminVisitIDHash: string; + let testUserAdmin: TestUser; + let testUserNonadmin: TestUser; + + beforeAll(async () => { + jest.setTimeout(15000); // these tests need longer to complete + try { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/create?token=${adminTokenNoSpace}`); + let returnMessageJSON: any = {}; + returnMessageJSON = await returnMessage.json(); + spaceID = returnMessageJSON['space-id']; + + // connect an admin test user to the space + let tokenData = tokenTypes.ADMIN_ID_APP1; + tokenData['user_id'] = generateUUID(); + testUserAdmin = new TestUser(tokenData['user_id']); + adminToken = await generateJWT(tokenData, spaceID); + await testUserAdmin.communicator.connectToHiFiAudioAPIServer(adminToken, stackURL) + .then(data => { + adminID = data.audionetInitResponse.user_id; + adminVisitIDHash = data.audionetInitResponse.visit_id_hash; + }); + expect(testUserAdmin.connectionState).toBe(HiFiConnectionStates.Connected); + + // connect a nonadmin test user to the space + tokenData = tokenTypes.NONADMIN_ID_APP1; + tokenData['user_id'] = generateUUID(); + testUserNonadmin = new TestUser(tokenData['user_id']); + nonadminToken = await generateJWT(tokenData, spaceID); + await testUserNonadmin.communicator.connectToHiFiAudioAPIServer(nonadminToken, stackURL) + .then(data => { + nonadminID = data.audionetInitResponse.user_id; + nonadminVisitIDHash = data.audionetInitResponse.visit_id_hash; + }); + expect(testUserNonadmin.connectionState).toBe(HiFiConnectionStates.Connected); + } catch (err) { + console.error("Could not set up a space and connect users to test user data access."); + } + }); + + afterAll(async () => { + await fetch(`${stackURL}/api/v1/spaces/${spaceID}?token=${adminToken}`, { + method: 'DELETE' + }); + jest.setTimeout(5000); // restore to default + }); + + test('Admin CAN access list of users', async () => { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${adminToken}`); + let returnMessageJSON = await returnMessage.json(); + expect(returnMessageJSON.length).toBe(2); + let returnedAdmin = returnMessageJSON.filter((obj: { user_id: string; }) => { + return obj['user_id'] === adminID; + }) + expect(returnedAdmin).toBeDefined(); + + let returnedNonadmin = returnMessageJSON.filter((obj: { user_id: string; }) => { + return obj['user_id'] === nonadminID; + }) + expect(returnedNonadmin).toBeDefined(); + }); + + test('Nonadmin CANNOT access list of users', async () => { + let returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/users?token=${nonadminToken}`); + let returnMessageJSON = await returnMessage.json(); + expect(returnMessageJSON.errors.description).toBe(`token isn't an admin token`); + }); + }); }); \ No newline at end of file diff --git a/tests/testUtilities/TestUser.ts b/tests/testUtilities/TestUser.ts index 604c1483..98264a05 100644 --- a/tests/testUtilities/TestUser.ts +++ b/tests/testUtilities/TestUser.ts @@ -1,28 +1,32 @@ -import { HiFiAudioAPIData, Point3D } from "../../src/classes/HiFiAudioAPIData"; import { HiFiCommunicator, HiFiConnectionStates } from "../../src/classes/HiFiCommunicator"; +export type MuteState = "MUTED_FIXED" | "MUTED_NOT_FIXED" | "UNMUTED"; + export class TestUser { name: string; connectionState: HiFiConnectionStates; + muteState: MuteState; communicator: HiFiCommunicator; - constructor(name: string, xPosition: number) { + constructor(name: string) { this.name = name; this.connectionState = HiFiConnectionStates.Disconnected; - let initialAudioData = new HiFiAudioAPIData({ position: new Point3D({ x: xPosition }) }); + this.muteState = "UNMUTED"; this.communicator = new HiFiCommunicator({ - initialHiFiAudioAPIData: initialAudioData, onConnectionStateChanged: this.onConnectionStateChanged.bind(this), onMuteChanged: this.onMuteChanged.bind(this), }); } onConnectionStateChanged(connectionState: HiFiConnectionStates) { - console.log("______________________ connectionState __________", connectionState); this.connectionState = connectionState; } - onMuteChanged(data:any) { - console.log("______________________ User mute change: ", this.name, "__________State:", data); + onMuteChanged(data: any) { + if (data.currentInputAudioMutedValue === true) { + this.muteState = data.adminPreventsInputAudioUnmuting ? "MUTED_FIXED" : "MUTED_NOT_FIXED"; + } else { + this.muteState = "UNMUTED"; + } } } \ No newline at end of file From c7c3672810d1f04cdc376113014645e8beb0675f Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Tue, 18 May 2021 16:58:26 -0700 Subject: [PATCH 08/32] Change yaml file to use 'pull_request_target' --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 3c32b216..643e4611 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -12,7 +12,7 @@ on: - main - release - pull_request: + pull_request_target: branches: - main - release From 3b26118eb84b02de4889896be4faa92c66a324f8 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Wed, 19 May 2021 13:16:18 -0700 Subject: [PATCH 09/32] Updated attenuation tests --- tests/smoke/rest.smoke.test.ts | 17 +++++++++++++---- tests/testUtilities/testUtils.ts | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/smoke/rest.smoke.test.ts b/tests/smoke/rest.smoke.test.ts index f4d14f06..f6861e0c 100644 --- a/tests/smoke/rest.smoke.test.ts +++ b/tests/smoke/rest.smoke.test.ts @@ -656,24 +656,28 @@ describe('HiFi API REST Calls', () => { let attenuation4Data: AttenuationData; attenuation1Data = { "attenuation": 0.5, + "frequency-rolloff": 12, "listener-zone-id": zone1Data.id, "source-zone-id": zone2Data.id, "za-offset": -5 }; attenuation2Data = { "attenuation": 0.5, + "frequency-rolloff": 12, "listener-zone-id": zone1Data.id, "source-zone-id": zone2Data.id, "za-offset": -5 }; attenuation3Data = { "attenuation": 0.5, + "frequency-rolloff": 12, "listener-zone-id": zone1Data.id, "source-zone-id": zone3Data.id, "za-offset": -5 }; attenuation4Data = { "attenuation": 0.5, + "frequency-rolloff": 12, "listener-zone-id": zone1Data.id, "source-zone-id": zone4Data.id, "za-offset": -5 @@ -708,7 +712,7 @@ describe('HiFi API REST Calls', () => { expect(responseJSON).toEqual([attenuation3Data]); // Create one attenuation via space settings/attenuations/create GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${adminToken}&attenuation=${attenuation4Data["attenuation"]}&source-zone-id=${attenuation4Data["source-zone-id"]}&listener-zone-id=${attenuation4Data["listener-zone-id"]}&za-offset=${attenuation4Data["za-offset"]}`); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${adminToken}&attenuation=${attenuation4Data["attenuation"]}&frequency-rolloff=${attenuation4Data["frequency-rolloff"]}&source-zone-id=${attenuation4Data["source-zone-id"]}&listener-zone-id=${attenuation4Data["listener-zone-id"]}&za-offset=${attenuation4Data["za-offset"]}`); responseJSON = await returnMessage.json(); expect(responseJSON['id']).toBeDefined(); attenuation4Data['id'] = responseJSON['id']; @@ -726,11 +730,12 @@ describe('HiFi API REST Calls', () => { // Change a zone attenuation's settings via GET request attenuation1Data['attenuation'] = -6; + attenuation1Data['frequency-rolloff'] = 2; attenuation1Data['listener-zone-id'] = zone2Data.id; attenuation1Data['source-zone-id'] = zone3Data.id; attenuation1Data['za-offset'] = 20; - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}&attenuation=${attenuation1Data["attenuation"]}&frequency-rolloff=${attenuation1Data["frequency-rolloff"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); responseJSON = await returnMessage.json(); expect(responseJSON).toEqual(attenuation1Data); @@ -749,6 +754,7 @@ describe('HiFi API REST Calls', () => { let attenuationID = attenuation1Data.id; attenuation1Data = { "attenuation": 0.8, + "frequency-rolloff": 2, "listener-zone-id": zone3Data.id, "source-zone-id": zone4Data.id, "za-offset": -7 @@ -931,12 +937,14 @@ describe('HiFi API REST Calls', () => { let attenuation2Data: AttenuationData; attenuation1Data = { "attenuation": 0.5, + "frequency-rolloff": 12, "listener-zone-id": zone1Data.id, "source-zone-id": zone2Data.id, "za-offset": -5 }; attenuation2Data = { "attenuation": 0.5, + "frequency-rolloff": 12, "listener-zone-id": zone2Data.id, "source-zone-id": zone1Data.id, "za-offset": -5 @@ -963,7 +971,7 @@ describe('HiFi API REST Calls', () => { expect(responseJSON.errors.description).toBe(`token isn't an admin token`); // Try to create one attenuation via space settings/attenuations/create GET request - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&source-zone-id=${attenuation2Data["source-zone-id"]}&listener-zone-id=${attenuation2Data["listener-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/create?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&frequency-rolloff=${attenuation1Data["frequency-rolloff"]}&source-zone-id=${attenuation2Data["source-zone-id"]}&listener-zone-id=${attenuation2Data["listener-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); responseJSON = await returnMessage.json(); expect(responseJSON.errors.description).toBe(`token isn't an admin token`); @@ -998,7 +1006,7 @@ describe('HiFi API REST Calls', () => { attenuation1Data['source-zone-id'] = zone3Data.id; attenuation1Data['za-offset'] = 20; - returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); + returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${nonAdminToken}&attenuation=${attenuation1Data["attenuation"]}&frequency-rolloff=${attenuation1Data["frequency-rolloff"]}&listener-zone-id=${attenuation1Data["listener-zone-id"]}&source-zone-id=${attenuation1Data["source-zone-id"]}&za-offset=${attenuation1Data["za-offset"]}`); responseJSON = await returnMessage.json(); expect(responseJSON.errors.description).toBe(`token isn't an admin token`); @@ -1017,6 +1025,7 @@ describe('HiFi API REST Calls', () => { let attenuationID = attenuation1Data.id; attenuation1Data = { "attenuation": 0.8, + "frequency-rolloff": 2, "listener-zone-id": zone3Data.id, "source-zone-id": zone4Data.id, "za-offset": -7 diff --git a/tests/testUtilities/testUtils.ts b/tests/testUtilities/testUtils.ts index a75dc7d1..d87660c3 100644 --- a/tests/testUtilities/testUtils.ts +++ b/tests/testUtilities/testUtils.ts @@ -140,6 +140,7 @@ export interface ZoneData { export interface AttenuationData { "attenuation": number, + "frequency-rolloff": number, "listener-zone-id": number, "source-zone-id": number, "za-offset": number, From 4092dd94ac209335c66ef6d515b13abe89da9704 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Wed, 19 May 2021 14:30:17 -0700 Subject: [PATCH 10/32] Add some null initial values --- tests/smoke/rest.smoke.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/smoke/rest.smoke.test.ts b/tests/smoke/rest.smoke.test.ts index f6861e0c..34729c31 100644 --- a/tests/smoke/rest.smoke.test.ts +++ b/tests/smoke/rest.smoke.test.ts @@ -569,7 +569,7 @@ describe('HiFi API REST Calls', () => { zone1Data['id'] = responseJSON[1].id; zone2Data['id'] = responseJSON[0].id; } - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data].map(a => a.id).sort()); + expect(responseJSON.map((a: { id: number; }) => a.id).sort()).toEqual([zone1Data, zone2Data].map(a => a.id).sort()); // Create one zone via space `settings/zones/create` POST request returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`, { @@ -594,7 +594,7 @@ describe('HiFi API REST Calls', () => { // Get the list of zones and make sure it is accurate returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zones?token=${adminToken}`); responseJSON = await returnMessage.json(); - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([zone1Data, zone2Data, zone3Data, zone4Data].map(a => a.id).sort()); + expect(responseJSON.map((a: { id: number; }) => a.id).sort()).toEqual([zone1Data, zone2Data, zone3Data, zone4Data].map(a => a.id).sort()); // Get a zone's settings via GET request @@ -655,15 +655,15 @@ describe('HiFi API REST Calls', () => { let attenuation3Data: AttenuationData; let attenuation4Data: AttenuationData; attenuation1Data = { - "attenuation": 0.5, - "frequency-rolloff": 12, + "attenuation": null, + "frequency-rolloff": null, "listener-zone-id": zone1Data.id, "source-zone-id": zone2Data.id, "za-offset": -5 }; attenuation2Data = { - "attenuation": 0.5, - "frequency-rolloff": 12, + "attenuation": null, + "frequency-rolloff": null, "listener-zone-id": zone1Data.id, "source-zone-id": zone2Data.id, "za-offset": -5 @@ -696,7 +696,7 @@ describe('HiFi API REST Calls', () => { expect(responseJSON[1]['id']).toBeDefined(); attenuation1Data['id'] = responseJSON[0]['id']; attenuation2Data['id'] = responseJSON[1]['id']; - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data].map(a => a.id).sort()); + expect(responseJSON.map((a: { id: number; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data].map(a => a.id).sort()); // Create one attenuation via space `settings/attenuations/create` POST request returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`, { @@ -721,7 +721,7 @@ describe('HiFi API REST Calls', () => { // Get the list of attenuations and make sure it is accurate returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations?token=${adminToken}`); responseJSON = await returnMessage.json(); - expect(responseJSON.map((a: { id: any; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data, attenuation3Data, attenuation4Data].map(a => a.id).sort()); + expect(responseJSON.map((a: { id: number; }) => a.id).sort()).toEqual([attenuation1Data, attenuation2Data, attenuation3Data, attenuation4Data].map(a => a.id).sort()); // Get a zone attenuation's settings via GET request returnMessage = await fetch(`${stackURL}/api/v1/spaces/${spaceID}/settings/zone_attenuations/${attenuation1Data.id}?token=${adminToken}`); From 7cbd4f47e10a7f1b3e441390cc94bef52e9c9ec4 Mon Sep 17 00:00:00 2001 From: hifibuild Date: Wed, 19 May 2021 21:41:48 +0000 Subject: [PATCH 11/32] Bump package version to 1.1.2-1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5422c0b8..5a30e02e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-0", + "version": "1.1.2-1", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 0d93c890..712b58bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-0", + "version": "1.1.2-1", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From e66a3312bac92aa04cd629166a587a7a44c2a675 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Fri, 21 May 2021 15:33:48 -0700 Subject: [PATCH 12/32] Add redirection for alternate eu stack names --- tests/integration/serverConnections.integration.test.ts | 3 +++ tests/smoke/clientAudio.smoke.test.ts | 3 +++ tests/smoke/rest.smoke.test.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/tests/integration/serverConnections.integration.test.ts b/tests/integration/serverConnections.integration.test.ts index afcbdf6e..5b558ffc 100644 --- a/tests/integration/serverConnections.integration.test.ts +++ b/tests/integration/serverConnections.integration.test.ts @@ -53,6 +53,9 @@ describe('Mixer connections', () => { } else if (stackname === "api-pro-east.highfidelity.com" || stackname === "api-pro-latest-east.highfidelity.com") { stackData = stacks.east; console.log("_______________USING EAST AUTH FILE_______________________"); + } else if (stackname === "api-pro-eu.highfidelity.com" || stackname === "api-pro-latest-eu.highfidelity.com") { + stackData = stacks['api-pro-eu.highfidelity.com']; + console.log("_______________USING EU AUTH FILE_______________________"); } else if (stackname === "api.highfidelity.com" || stackname === "api-hobby-latest.highfidelity.com") { stackData = stacks.hobby; console.log("_______________USING HOBBY AUTH FILE_______________________"); diff --git a/tests/smoke/clientAudio.smoke.test.ts b/tests/smoke/clientAudio.smoke.test.ts index c331f304..6c8a20c8 100644 --- a/tests/smoke/clientAudio.smoke.test.ts +++ b/tests/smoke/clientAudio.smoke.test.ts @@ -27,6 +27,9 @@ describe('Audio', () => { } else if (stackname === "api-pro-east.highfidelity.com" || stackname === "api-pro-latest-east.highfidelity.com") { stackData = stacks.east; console.log("_______________USING EAST AUTH FILE_______________________"); + } else if (stackname === "api-pro-eu.highfidelity.com" || stackname === "api-pro-latest-eu.highfidelity.com") { + stackData = stacks['api-pro-eu.highfidelity.com']; + console.log("_______________USING EU AUTH FILE_______________________"); } else if (stackname === "api.highfidelity.com" || stackname === "api-hobby-latest.highfidelity.com") { stackData = stacks.hobby; console.log("_______________USING HOBBY AUTH FILE_______________________"); diff --git a/tests/smoke/rest.smoke.test.ts b/tests/smoke/rest.smoke.test.ts index 34729c31..07fea926 100644 --- a/tests/smoke/rest.smoke.test.ts +++ b/tests/smoke/rest.smoke.test.ts @@ -23,6 +23,9 @@ describe('HiFi API REST Calls', () => { } else if (stackname === "api-pro-east.highfidelity.com" || stackname === "api-pro-latest-east.highfidelity.com") { stackData = stacks.east; console.log("_______________USING EAST AUTH FILE_______________________"); + } else if (stackname === "api-pro-eu.highfidelity.com" || stackname === "api-pro-latest-eu.highfidelity.com") { + stackData = stacks['api-pro-eu.highfidelity.com']; + console.log("_______________USING EU AUTH FILE_______________________"); } else if (stackname === "api.highfidelity.com" || stackname === "api-hobby-latest.highfidelity.com") { stackData = stacks.hobby; console.log("_______________USING HOBBY AUTH FILE_______________________"); From 4f07c57f801d01422c3db3825693d04397db296d Mon Sep 17 00:00:00 2001 From: hifibuild Date: Fri, 21 May 2021 23:04:23 +0000 Subject: [PATCH 13/32] Bump package version to 1.1.2-2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a30e02e..314f504d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-1", + "version": "1.1.2-2", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 712b58bb..da3bc45e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-1", + "version": "1.1.2-2", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 03a2cc885d0b833418c1405989c84d5485c63e96 Mon Sep 17 00:00:00 2001 From: hifibuild Date: Fri, 21 May 2021 23:05:41 +0000 Subject: [PATCH 14/32] Bump package version to 1.1.2-3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 314f504d..add9f580 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-2", + "version": "1.1.2-3", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index da3bc45e..87898ff5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-2", + "version": "1.1.2-3", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From b64dbab4fccdb453b9a7d0b28b51875d84ef005f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 26 May 2021 09:43:56 -0700 Subject: [PATCH 15/32] update the documentation regarding mute --- src/classes/HiFiAudioAPIData.ts | 2 +- src/classes/HiFiCommunicator.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/classes/HiFiAudioAPIData.ts b/src/classes/HiFiAudioAPIData.ts index 24fa6b9b..7b8f214a 100644 --- a/src/classes/HiFiAudioAPIData.ts +++ b/src/classes/HiFiAudioAPIData.ts @@ -396,7 +396,7 @@ export class HiFiAudioAPIData { /** * A volume level below this value is considered background noise and will be smoothly gated off. * The floating point value is specified in dBFS (decibels relative to full scale) with values between -96 dB (indicating no gating) - * and 0 dB. It is in the same decibel units as the VolumeDecibels component of UserDataSubscription. + * and 0 dB (effectively muting the input from this user). It is in the same decibel units as the VolumeDecibels component of UserDataSubscription. */ volumeThreshold: number; /** diff --git a/src/classes/HiFiCommunicator.ts b/src/classes/HiFiCommunicator.ts index 97fe1f69..544a4a14 100644 --- a/src/classes/HiFiCommunicator.ts +++ b/src/classes/HiFiCommunicator.ts @@ -394,13 +394,17 @@ export class HiFiCommunicator { } /** - * Use this function to set whether or not the user's input audio `MediaStream` should be "muted". - * A muted stream will have the `enabled` property of each of its `MediaStreamTrack`s set to `false` - * (and an unmuted stream -- the default -- will have the `enabled` property set to `true`). Be - * aware that if you are using the same `MediaStream` object in other ways, it will be affected by + * Use this function to set whether input audio stream will have the `enabled` property of each of its `MediaStreamTrack`s set to `false` + * (and an unmuted stream -- the default -- will have the `enabled` property set to `true`). This will silence the input, + * but has specific consequences: + * - If you are using the same `MediaStream` object in other ways, it will be affected by * calling this method. So, if you would like to mute/unmute the input audio stream separately for the * High Fidelity audio vs. some other use of it, it is recommended to clone the audio stream separately * for each use. + * - The effect is immediate and could result in a click or other audio artifact if there is steady sound at + * the moment the input is muted. + * + * An alterative is to set the user's {@link volumeThreshold} to 0, which smoothly gates off the user's input. * @returns `true` if the stream was successfully muted/unmuted, `false` if it was not. (The user should * assume that if this returns `false`, no change was made to the mute (track enabled) state of the stream.) */ From c2c04f1f6f79584b35fe7e7f276cb322b5ef0ee7 Mon Sep 17 00:00:00 2001 From: Maia Hansen Date: Wed, 26 May 2021 10:27:54 -0700 Subject: [PATCH 16/32] Migrating in changes from libravi: better websocket error handling, and dynamic turn servers --- src/libravi/RaviCommandController.ts | 2 - src/libravi/RaviSession.ts | 458 ++++++++++++++++--------- src/libravi/RaviSignalingConnection.ts | 175 ++++++---- 3 files changed, 395 insertions(+), 240 deletions(-) diff --git a/src/libravi/RaviCommandController.ts b/src/libravi/RaviCommandController.ts index 4fc9df35..8846caf0 100644 --- a/src/libravi/RaviCommandController.ts +++ b/src/libravi/RaviCommandController.ts @@ -593,8 +593,6 @@ export class RaviCommandController { return; } - RaviUtils.log("_continueProcessingListeningCommand: " + commandMessage, "RaviCommandController"); - // Let's try to find the matching listener(s) for the commandMessage received: var foundCommandInstance = this._commandQueueMap.get(commandMessage.command); if (foundCommandInstance) { diff --git a/src/libravi/RaviSession.ts b/src/libravi/RaviSession.ts index 08d5f344..9e70cdbb 100644 --- a/src/libravi/RaviSession.ts +++ b/src/libravi/RaviSession.ts @@ -1,5 +1,5 @@ -import { RaviSignalingConnection } from './RaviSignalingConnection'; +import { RaviSignalingConnection, RaviSignalingStates } from './RaviSignalingConnection'; import { RaviStreamController } from './RaviStreamController'; import { RaviUtils } from './RaviUtils'; import { RaviCommandController } from './RaviCommandController'; @@ -49,16 +49,6 @@ export enum RaviSessionStates { * */ export class RaviSession { - _stateChangeHandlers: Set; - _uuid: string; - - _commandController: RaviCommandController; - _streamController: RaviStreamController; - - _state: RaviSessionStates; - - _raviImplementation: RaviWebRTCImplementation; - /** * "Class" variables to be aware of: * @@ -71,8 +61,25 @@ export class RaviSession { * this._streamController * * this._state // The current state of this connection + * + * _resolveOpen, _rejectOpen, _resolveClose, and _rejectClose: Used for resolving the Promises + * made by the open and close functions, which get handled outside of those functions themselves */ + _stateChangeHandlers: Set; + _uuid: string; + + _commandController: RaviCommandController; + _streamController: RaviStreamController; + + _state: RaviSessionStates; + + _raviImplementation: RaviWebRTCImplementation; + + _resolveOpen: Function; _rejectOpen: Function; + _resolveClose: Function; _rejectClose: Function; + _openingTimeout: ReturnType; + /** * Create a new RaviSession. * Defaults to using new RaviCommandController and RaviStreamControllers @@ -81,7 +88,6 @@ export class RaviSession { * @constructor */ constructor() { - RaviUtils.log("Constructor", "RaviSession"); // Initialize the list of handlers and the UUID this._stateChangeHandlers = new Set(); this._uuid = RaviUtils.createUUID(); @@ -204,78 +210,36 @@ export class RaviSession { * @returns {Promise} */ openRAVISession({signalingConnection, timeout = 5000, params = null}: { signalingConnection: RaviSignalingConnection, timeout?: number, params?: WebRTCSessionParams}) { - var raviSession = this; + if (this._state === RaviSessionStates.CONNECTED || this._state === RaviSessionStates.COMPLETED) { + // Ref. iceconnectionstates at https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState + return Promise.resolve( + "There is already an open RAVI session. To reconnect, first close the existing connection, and then attempt to open again." + ); + } + + var raviSession = this; // Tell our connection implementation about this signaling connection -- // it may need to talk to it directly while it's attempting to negotiate // the connection. this._raviImplementation._assignSignalingConnection(signalingConnection); - return new Promise((resolve, reject) => { - RaviUtils.log("Opening RAVI session", "RaviSession"); - - // Set a timeout in case the session gets hung up in initialization - let timer = setTimeout(() => { + // Set a timeout in case the session gets hung up somewhere + this._openingTimeout = setTimeout(() => { RaviUtils.log("RaviSession.open timed out after " + timeout + " ms", "RaviSession"); - // Reject the promise with an explicit error message. - reject(Error("RaviSession.open timed out")); - // Close the session; this should trigger the state change - // handler to clean up. This would also reject the promise if we didn't do so explicitly above. + raviSession._fulfillPromises({}, RaviSessionStates.FAILED); + // Close the session to clean up objects. We've already rejected the promise above. raviSession.closeRAVISession(); - }, timeout); - - // Add a state change handler that will resolve the - // promise once the connection is open - const stateHandler = function(event: any) { - var state = ""; - if (event && event.state) state = event.state; - - if (state === RaviSessionStates.NEW || - state === RaviSessionStates.CONNECTING || - state === RaviSessionStates.DISCONNECTED) { - // NOTE: Per https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection#RTCIceConnectionState_enum - // "disconnected" is a potentially transient state, so in this call we will simply wait until we - // get to connected, complete, or failed. - RaviUtils.log("RAVI session state is " + state, "RaviSession"); - - } else if (state === RaviSessionStates.CONNECTED || - state === RaviSessionStates.COMPLETED) { - clearHandlerAndTimeout(); - resolve(state); - - } else if (state === RaviSessionStates.FAILED) { - clearHandlerAndTimeout(); - // Explicitly call the implementation's "close" method to make - // really, really sure it's closed in addition to being "failed". - // These are NOT the same state! A "failed" connection may still be - // aware of its signaling connection and other niceties. - // Kick off that close in a timeout to get it to run asynchronously - // from the Promise rejection. - const closeTimer = setTimeout(() => { - raviSession._raviImplementation._close(); - }, 0); - reject(Error(state)); - } else if (state === RaviSessionStates.CLOSED) { - clearHandlerAndTimeout(); - reject(Error(state)); - } - }; - - const clearHandlerAndTimeout = function () { - raviSession.removeStateChangeHandler(stateHandler); - // Clear the timer - if (timer) { - clearTimeout(timer); - timer = null; - } - } + }, timeout); - // The implementation will trigger connection state change events - raviSession.addStateChangeHandler(stateHandler); + return new Promise((resolve, reject) => { + raviSession._resolveOpen = resolve; + raviSession._rejectOpen = reject; - // And finally, call the implementation's open method + // Start the "opening" process + RaviUtils.log("Opening RAVI session", "RaviSession"); + this._state = RaviSessionStates.NEW; raviSession._raviImplementation._open(params); - }); } @@ -290,6 +254,9 @@ export class RaviSession { */ closeRAVISession() { var raviSession = this; + if (this._state === RaviSessionStates.CLOSED) return Promise.resolve( + "RAVI session is already closed." + ); // Start by closing out command controller // and the stream controller. @@ -297,38 +264,74 @@ export class RaviSession { this._commandController.stopMonitoringQueues(); return new Promise((resolve, reject) => { - RaviUtils.log("Closing RAVI session", "RaviSession"); - // Add a state change handler that will resolve the - // promise once the connection is closed - const stateHandler = function(event: any) { - var state = ""; - if (event && event.state) state = event.state; - - if (state === RaviSessionStates.DISCONNECTED) { - RaviUtils.log("Closing...", "RaviSession"); - } else if (state === RaviSessionStates.CLOSED) { - // Remove this as a state change handler - raviSession.removeStateChangeHandler(stateHandler); - // and resolve the Promise - resolve(state); - } else { - // Remove this as a state change handler - raviSession.removeStateChangeHandler(stateHandler); - // and reject the Promise - reject(Error(state)); - } - }; - raviSession.addStateChangeHandler(stateHandler); + raviSession._resolveClose = resolve; + raviSession._rejectClose = reject; - // And then alert about the "closing" process - var event = {"state":RaviSessionStates.DISCONNECTED}; - raviSession._handleStateChange(event, RaviSessionStates.DISCONNECTED); - // And finally, call the implementation's close method + RaviUtils.log("Closing RAVI session", "RaviSession"); raviSession._raviImplementation._close(); - }); } + + /** + * @private + * Gets called whenever the state changes (and sometimes when it doesn't, + * but when we just want to make sure). Depending on the new (or current) state, + * this will appropriately fulfill outstanding promises that are pending + * in either the open or close method (or both). + */ + _fulfillPromises(event: any, state: RaviSessionStates) { + let errorMessage = event.reason || event.message || state; + switch(state) { + case RaviSessionStates.CONNECTED: + case RaviSessionStates.COMPLETED: + if (this._openingTimeout) { + clearTimeout(this._openingTimeout); + this._openingTimeout = null; + } + if (this._resolveOpen) this._resolveOpen(); + if (this._rejectClose) this._rejectClose(errorMessage); + break; + case RaviSessionStates.DISCONNECTED: + // NOTE: Per https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection#RTCIceConnectionState_enum + // "disconnected" is a potentially transient state, so in this case we will simply wait until we + // get to connected, complete, or failed. + RaviUtils.log("_fulfillPromises: Possible transitory state DISCONNECTED; leaving promises pending", "RaviSession"); + break; + case RaviSessionStates.FAILED: + if (this._openingTimeout) { + clearTimeout(this._openingTimeout); + this._openingTimeout = null; + } + if (this._rejectOpen) this._rejectOpen(errorMessage); + if (this._rejectClose) this._rejectClose(errorMessage); + // Explicitly call the implementation's "close" method to make + // really, really sure it's closed in addition to being "failed". + // These are NOT the same state! A "failed" connection may still be + // aware of its signaling connection and other niceties. + // Kick off that close in a timeout to get it to run asynchronously + // from the Promise rejection. + let raviSession = this; + const closeTimer = setTimeout(() => { + raviSession._raviImplementation._close(); + }, 0); + break; + case RaviSessionStates.CLOSED: + if (this._openingTimeout) { + clearTimeout(this._openingTimeout); + this._openingTimeout = null; + } + if (this._rejectOpen) this._rejectOpen(errorMessage); + if (this._resolveClose) this._resolveClose(); + break; + default: + // Do nothing for the "in progress" states, like "NEW" or "CONNECTING" + RaviUtils.log("_fulfillPromises: Skipping in-progress state " + state, "RaviSession"); + } + } + + + /** * Handler for whenever a new "track channel" shows up. (When this event happens * should be determined by the implementation, and information about the track should be @@ -337,7 +340,7 @@ export class RaviSession { */ _doOntrack(event: any) { // TODO: This code is working if we have only one media track (video OR audio), not sure it works with more - // Need too make it more robust with different branches based on the event info + // Need to make it more robust with different branches based on the event info RaviUtils.log("Received new track: ", "RaviSession"); RaviUtils.log(event, "RaviSession"); @@ -379,18 +382,24 @@ export class RaviSession { * Generic handler * @private */ - _handleStateChange(event: any, state: any) { - this._state = state; - if (!event) { - return; - } + _handleStateChange(event: any, state: RaviSessionStates) { + if (!event) event = {}; event["state"] = state; - RaviUtils.log("_handleStateChange: " + JSON.stringify(event), "RaviSession"); - this._stateChangeHandlers.forEach(function(handler) { - if (handler) { - handler(event); - } - }); + + // Always try to fulfill any open promises, even if the state hasn't changed + this._fulfillPromises(event, state); + + // But only call handlers if the state did, in fact, change + if (state !== this._state) { + this._state = state; + event["state"] = state; + RaviUtils.log("_handleStateChange: " + RaviUtils.safelyPrintable(event), "RaviSession"); + this._stateChangeHandlers.forEach(function(handler) { + if (handler) { + handler(event); + } + }); + } } /** @@ -627,13 +636,18 @@ if (typeof self === 'undefined') { * @internal * Constants used during session negotiation */ -const peerConnectionConfig = { +const DEFAULT_STUN_CONFIG = { + 'urls': ['stun:stun.l.google.com:19302'] +}; +const LEGACY_TURN_CONFIG = { + 'urls': ['turn:turn.highfidelity.com:3478'], + 'username': 'clouduser', + 'credential': 'chariot-travesty-hook' +}; +let peerConnectionConfig = { 'iceServers': [ - {'urls': 'stun:stun.l.google.com:19302'}, - {'urls': 'turn:turn.highfidelity.com:3478', - 'username': 'clouduser', - 'credential': 'chariot-travesty-hook' - } + DEFAULT_STUN_CONFIG, + LEGACY_TURN_CONFIG ] }; @@ -651,6 +665,12 @@ class RaviWebRTCImplementation { _raviVideoSenders: any; _signalingConnection: RaviSignalingConnection; + // Need to keep track of the input streams in case they get set + // before the actual RTC connection is available. + _audioInputStream: MediaStream; + _videoInputStream: MediaStream; + _shortCircuitHandler: Function; + /** * "Class" variables to be aware of: * this._rtcConnection // The actual RTCPeerConnection @@ -668,21 +688,21 @@ class RaviWebRTCImplementation { RaviUtils.log("constructor", "RaviWebRTCImplementation"); this._raviSession = raviSession; this._negotiator = this._setupConnection.bind(this); - this._initRtcConnection(); this._statsWatcher = new RaviWebRTCStatsWatcher(this); + this._raviAudioSenders = []; + this._raviVideoSenders = []; } /** - * Initialize the RTC connection to a new fresh one. This should be called - * before attempting to open the connection. But since it's nice to have these - * handlers attached as early as possible, we actually call this right away in the constructor - * and then again when a connection is closed (to reinitialize). + * Initialize the RTC connection to a new fresh one. This gets called once we + * have received the initial SDP from the server (so that we can attach + * dynamic TURN information to it when it's created, as needed). * * @private */ _initRtcConnection() { const raviSession = this._raviSession; - const that = this; + const sessionImplementation = this; // Create a new RTC connection (for node or the browser) this._rtcConnection = new crossPlatformRTCPeerConnection(peerConnectionConfig); @@ -705,6 +725,10 @@ class RaviWebRTCImplementation { // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection#RTCPeerConnectionState_enum // to see why we're listening on iceconnectionstatechange instead of peerconnectionstatechange rtcConnection.addEventListener('iceconnectionstatechange', function(event: any) { + if (rtcConnection.iceConnectionState === "connected" || rtcConnection.iceConnectionState == "completed") { + RaviUtils.log("Session has fully connected; removing short-circuit handler", "RaviWebRTCImplementation"); + sessionImplementation._signalingConnection.removeStateChangeHandler(sessionImplementation._shortCircuitHandler); + } raviSession._handleStateChange(event, rtcConnection.iceConnectionState); }); @@ -714,14 +738,14 @@ class RaviWebRTCImplementation { // However, we need to listen at our own RTC implementation level for ice candidate events, // because they're part of the session negotiation - rtcConnection.addEventListener('icecandidate', function(event: any) { that._doOnicecandidate(event); }); + rtcConnection.addEventListener('icecandidate', function(event: any) { sessionImplementation._doOnicecandidate(event); }); // When a negotiationneeeded is triggered from this peer, signal the server side to initiate an offer // In Ravi, the webrtc negotiation is always initiated from the server side - rtcConnection.addEventListener('negotiationneeded', function(event: any) { that._doOnnegotiationneeded(event); }); + rtcConnection.addEventListener('negotiationneeded', function(event: any) { sessionImplementation._doOnnegotiationneeded(event); }); // Watch the signaling state changes for debug. - rtcConnection.addEventListener("signalingstatechange", function(event: any) { that._doOnsignalingstatechanged(event); }); + rtcConnection.addEventListener("signalingstatechange", function(event: any) { sessionImplementation._doOnsignalingstatechanged(event); }); } @@ -745,10 +769,16 @@ class RaviWebRTCImplementation { */ _addAudioInputStream(stream: MediaStream) { const rtcConnection = this._rtcConnection; - const that = this; + const sessionImplementation = this; var retval = false; if (stream) { + this._audioInputStream = stream; + if (! rtcConnection) { + RaviUtils.log("Setting audio input stream without available RTC connection; will store it until ready", "RaviWebRTCImplementation"); + return true; + } + // We keep track of the senders that we interact with // separately from the list of senders on the RTC connection, // because we want to make sure we're only working with @@ -804,6 +834,12 @@ class RaviWebRTCImplementation { var retval = false; if (stream) { + this._videoInputStream = stream; + if (! rtcConnection) { + RaviUtils.log("Setting video input stream without available RTC connection; will store it until ready", "RaviWebRTCImplementation"); + return true; + } + // We keep track of the senders that we interact with // separately from the list of senders on the RTC connection, // because we want to make sure we're only working with @@ -843,7 +879,7 @@ class RaviWebRTCImplementation { } return retval; } - + /** * Open a session. This implementation does this by adding a handler to the signalingConnection * that will listen for "ready to negotiate connection" messages so that the @@ -854,7 +890,10 @@ class RaviWebRTCImplementation { */ _open(params: any) { RaviUtils.log("Attempting to open connection...", "RaviWebRTCImplementation"); - if (this._rtcConnection.connectionState == 'connecting' || this._rtcConnection.connectionState == 'connected') { + if (this._rtcConnection + && (this._rtcConnection.connectionState == 'connecting' + || this._rtcConnection.connectionState == 'connected')) + { RaviUtils.log("We already have a connection in progress. Will not attempt a new one.", "RaviWebRTCImplementation"); // Trigger state change handler on the owning session to finalize any // residual Promises @@ -867,6 +906,15 @@ class RaviWebRTCImplementation { // signaling connection. This should listen for the appropriate // "ready to negotiate connection" message from the signaling connection. this._signalingConnection.addMessageHandler(this._negotiator); + + // Add a state change handler to the signaling connection. If the + // connection closes or fails _while we're in the process of negotiating + // the connection_ (i.e. before the connection is fully open), we should stop trying. + // We remove this handler once the WebRTC connection is fully open, because if the + // signaling connection closes _after_ we've got a stable connection, we want to + // leave our webrtc connection open as long as possible. + this._shortCircuitHandler = this._cancelOpeningProcessOnSignalingDisconnect.bind(this); + this._signalingConnection.addStateChangeHandler(this._shortCircuitHandler); // Send the magic string for opening a connection. // with params eventually @@ -881,40 +929,86 @@ class RaviWebRTCImplementation { this._signalingConnection.send(JSON.stringify({'request': message})); } } + + /** + * Cancel a session that's in the process of being opened. This gets called if + * we receive a state change from the signaling connection after the "open" has already + * been called. If so (and if that state change is an error/close), we short-circuit + * the process of opening the session and close out immediately. + * + * This handler is added by the _open() method and removed when either the connection is + * connected or when the _close() method is called, whichever comes first. + * + * @protected + */ + _cancelOpeningProcessOnSignalingDisconnect (event: any) { + let state = event.state || "unknown"; + const raviSession = this._raviSession; + switch(state) { + case RaviSignalingStates.CLOSED: + RaviUtils.log("Signaling state closed before session was established; closing RaviSession", "RaviWebRTCImplementation"); + raviSession._handleStateChange(event, RaviSessionStates.CLOSED); + // Call _close to clean up after ourselves + this._close(); + break; + case RaviSignalingStates.ERROR: + RaviUtils.log("Signaling state errored out before session was established; closing RaviSession", "RaviWebRTCImplementation"); + raviSession._handleStateChange(event, RaviSessionStates.FAILED); + this._close(); + break; + case RaviSignalingStates.UNAVAILABLE: + RaviUtils.log("Signaling state reached 'unavailable' before session was established; closing RaviSession", "RaviWebRTCImplementation"); + raviSession._handleStateChange(event, RaviSessionStates.FAILED); + this._close(); + break; + default: + RaviUtils.log("Signaling state has changed during opening of RAVI session, but is an OK change. New state: " + state, "RaviWebRTCImplementation"); + } + } /** * Close a session. * * This method is called by the owning RaviSession. + * Note: This method ALWAYS goes through its cleanup process, + * even if there isn't an underlying RTC connection or the underlying + * RTC connection is already closed. This is to make sure that other things + * (e.g. state change handlers on the signaling connection and promises) always + * get cleaned up appropriately. * @protected */ _close() { - if (this._rtcConnection) { - this._statsWatcher.stop(); + this._statsWatcher.stop(); + const raviSession = this._raviSession; + RaviUtils.log("closing", "RaviWebRTCImplementation"); - RaviUtils.log("closing", "RaviWebRTCImplementation"); + if (this._rtcConnection) { this._rtcConnection.close(); - this._rtcConnection = null; + } - var event = {"state":RaviSessionStates.CLOSED}; - this._raviSession._handleStateChange(event, RaviSessionStates.CLOSED); - - // Remove our session-negotiating message handler - this._signalingConnection.removeMessageHandler(this._negotiator); + // Remove our session-negotiating message handler from the signaling connection + this._signalingConnection.removeMessageHandler(this._negotiator); - // Reinitialize the RTC connection so it's ready for another session if needed. - this._initRtcConnection(); - } - } + // Remove the signaling connection's state change handler that would have + // short-circuited connection logic if the signaling disconnects while + // we're still trying to negotiate + this._signalingConnection.removeStateChangeHandler(this._shortCircuitHandler); + // _rtcConnection will be reinitialized if/when it's needed again, next time + // a connect is attemped and an sdp offer arrives. + this._rtcConnection = null; + + // Make absolutely sure the owning session knows we've closed + raviSession._handleStateChange({}, RaviSessionStates.CLOSED); + } /** * Used to send local ICE candidate proposals to the server. * * @private */ - _doOnicecandidate(event: any) { + _doOnicecandidate(event: any) { if (event.candidate && event.candidate != "") { RaviUtils.log("Sending local ICE candidate: " + JSON.stringify(event.candidate), "RaviWebRTCImplementation"); this._signalingConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': this._raviSession.getUUID()})); @@ -937,7 +1031,7 @@ class RaviWebRTCImplementation { const desc = JSON.stringify(msg); // negotiation needed but only if we are not already currently negotiating - if (this._signalingConnection && this._rtcConnection.signalingState === "stable") { + if (this._signalingConnection && this._rtcConnection && this._rtcConnection.signalingState === "stable") { this._signalingConnection.send(desc); } } @@ -948,8 +1042,10 @@ class RaviWebRTCImplementation { * @private */ _doOnsignalingstatechanged(event: any) { - // simple logging for now - RaviUtils.log("SignalingState changed: " + this._rtcConnection.signalingState, "RaviWebRTCImplementation"); + if (this._rtcConnection) { + // simple logging for now + RaviUtils.log("SignalingState changed: " + this._rtcConnection.signalingState, "RaviWebRTCImplementation"); + } } /** @@ -987,13 +1083,12 @@ class RaviWebRTCImplementation { // Local copies of useful variables to avoid having to bind const raviSession = this._raviSession; - const rtcConnection = this._rtcConnection; const signalingConnection = this._signalingConnection; - const that = this; + const sessionImplementation = this; // Just in case, make sure we have everything we need - if (!raviSession || !rtcConnection || !signalingConnection) { - RaviUtils.err("Missing one of raviSession, rtcConnection, or signalingConnection! Can't set up connection.", "RaviWebRTCImplementation"); + if (!raviSession || !signalingConnection) { + RaviUtils.err("Missing one of raviSession or signalingConnection! Can't set up connection.", "RaviWebRTCImplementation"); return; } @@ -1010,10 +1105,44 @@ class RaviWebRTCImplementation { // We have a signal; check first to see if it's an SDP if (signal.sdp) { - RaviUtils.log("Got sdp of type:" + signal.type, "RaviWebRTCImplementation"); + RaviUtils.log("Received sdp type=" + signal.type, "RaviWebRTCImplementation"); + + // grab the TURN config if found + if (signal.turn && signal.turn.urls && signal.turn.username && signal.turn.credential) { + // We appear to need to humor TypeScript by setting this to a constant the + // same way we do for the default + const DYNAMIC_TURN_CONFIG = { + 'urls': signal.turn.urls, + 'username': signal.turn.username, + 'credential': signal.turn.credential + }; + peerConnectionConfig = { + 'iceServers': [ + DEFAULT_STUN_CONFIG, + DYNAMIC_TURN_CONFIG + ] + }; + } + + // SAD STATE OF AFFAIRS: there is no way (AFAICT) to modify + // _rtcConnection.configuration after the RtcConnection ctor. + // WORKAROUND: we wait until the first websocket message (e.g. the sdp offer), + // which could include TURN config info, before we initialize _rtcConnection. + if (!this._rtcConnection) { + this._initRtcConnection(); + // If someone has already set the audio input stream, add it to the rtc connection + // now that it's been initialized. + if (this._audioInputStream) { + this._addAudioInputStream(this._audioInputStream); + } + if (this._videoInputStream) { + this._addVideoInputStream(this._videoInputStream); + } + } + let rtcConnection = this._rtcConnection; // Force our desired bitrate by munging the SDP, and create a session description for it - signal.sdp = that._forceBitrateUp(signal.sdp); + signal.sdp = sessionImplementation._forceBitrateUp(signal.sdp); const desc = new crossPlatformRTCSessionDescription(signal); // Set the description on the RTC connection, and send and handle the various SDPs @@ -1024,7 +1153,7 @@ class RaviWebRTCImplementation { }) .then(function(answer: any) { // Force stereo on the downstream stream by munging the SDP - answer.sdp = that._forceStereoDown(answer.sdp); + answer.sdp = sessionImplementation._forceStereoDown(answer.sdp); RaviUtils.log("Answer:", "RaviWebRTCImplementation"); RaviUtils.log(answer, "RaviWebRTCImplementation"); // set local description @@ -1044,13 +1173,18 @@ class RaviWebRTCImplementation { } else if (signal.ice) { RaviUtils.log("Received remote ICE candidate: " + JSON.stringify(signal.ice), "RaviWebRTCImplementation"); - rtcConnection.addIceCandidate(signal.ice) - .then(function() { - RaviUtils.log("Added remote candidate", "RaviWebRTCImplementation"); - }) - .catch(function(e: any) { - RaviUtils.err("Error attempting to add remote ICE candidate: " + e.message, "RaviWebRTCImplementation"); - }); + if (this._rtcConnection) { + this._rtcConnection.addIceCandidate(signal.ice) + .then(function() { + RaviUtils.log("Added remote candidate", "RaviWebRTCImplementation"); + }) + .catch(function(e: any) { + RaviUtils.err("Error attempting to add remote ICE candidate: " + e.message, "RaviWebRTCImplementation"); + }); + } else { + // TODO: Keep track of ice candidates until we have an rtcConnection + RaviUtils.log("Ignore ice candidate until we have an rtcConnection, ice='" + JSON.stringify(signal) + "'", "RaviWebRTCImplementation"); + } } else { // Some other handler's problem RaviUtils.log("Unknown message " + JSON.stringify(signal), "RaviWebRTCImplementation"); @@ -1090,7 +1224,7 @@ class RaviWebRTCImplementation { if (this._rtcConnection) { return this._rtcConnection.getStats(selector); } else { - return {}; + return []; } } } diff --git a/src/libravi/RaviSignalingConnection.ts b/src/libravi/RaviSignalingConnection.ts index d1c382d1..e02bb0ed 100644 --- a/src/libravi/RaviSignalingConnection.ts +++ b/src/libravi/RaviSignalingConnection.ts @@ -6,10 +6,7 @@ import { RaviUtils } from "./RaviUtils"; * * "UNAVAILABLE" is a custom state that gets set * if the server is in a "running, but not currently - * accepting incoming connections" state. Note however - * that this is a transient state -- a connection that's - * entered this state will usually then proceed to "ERROR" and - * then to "CLOSED". Handle appropriately! + * accepting incoming connections" state. * * @readonly * @enum {string} @@ -34,11 +31,6 @@ export enum RaviSignalingStates { * */ export class RaviSignalingConnection { - _stateChangeHandlers: Set; - _messageHandlers: Set; - _state: RaviSignalingStates; - _signalingImplementation: RaviSignalingWebSocketImplementation; - /** * "Class" variables to be aware of: * @@ -48,7 +40,17 @@ export class RaviSignalingConnection { * this._signalingImplementation // The implementation of signaling to use * * this._state // The current state of this connection + * + * _resolveOpen, _rejectOpen, _resolveClose, and _rejectClose: Used for resolving the Promises + * made by the open and close functions, which get handled outside of those functions themselves */ + _stateChangeHandlers: Set; + _messageHandlers: Set; + _state: RaviSignalingStates; + _signalingImplementation: RaviSignalingWebSocketImplementation; + + _resolveOpen: Function; _rejectOpen: Function; + _resolveClose: Function; _rejectClose: Function; /** * Create a new RaviSignalingConnection @@ -122,7 +124,7 @@ export class RaviSignalingConnection { */ removeStateChangeHandler(changeHandler: Function) { try { - this._stateChangeHandlers.delete(changeHandler); + const retval = this._stateChangeHandlers.delete(changeHandler); return true; } catch (err) { RaviUtils.err("Error removing a state change handler: " + @@ -180,40 +182,25 @@ export class RaviSignalingConnection { /** * Open a signaling connection to a particular URL. Returns a Promise - * that will resolve with the state once the RaviSignalingConnection is connected. + * that will resolve once the RaviSignalingConnection is connected. * * @param {string} URL The URL of the signaling server's endpoint (e.g. 'wss://foo.bar.baz:8889') * @returns {Promise} */ openRAVISignalingConnection(URL: string) { var signalingConnection = this; + if (this._state === RaviSignalingStates.OPEN) return Promise.resolve( + "There is already an open WebSocket connection. To reconnect, first close the existing WebSocket and then attempt to open again." + ); return new Promise((resolve, reject) => { + signalingConnection._resolveOpen = resolve; + signalingConnection._rejectOpen = reject; + // Start the "opening" process RaviUtils.log("Opening signaling connection to " + URL, "RaviSignalingController"); - // Add a state change handler that will resolve the - // promise when the connection is open - const stateHandler = function(event: any) { - var state = ""; - if (event && event.state) state = event.state; - - if (state === RaviSignalingStates.CONNECTING) { - RaviUtils.log("Connecting...", "RaviSignalingController") - } else if (state === RaviSignalingStates.OPEN) { - // Remove this as a state change handler - signalingConnection.removeStateChangeHandler(stateHandler); - // and resolve the Promise - resolve(state); - } else { - // Remove this as a state change handler - signalingConnection.removeStateChangeHandler(stateHandler); - // and reject the Promise - reject(event.error || new Error(event.message || state)); - } - } - signalingConnection.addStateChangeHandler(stateHandler); - // And then alert about the "opening" process var event = {"state":RaviSignalingStates.CONNECTING}; this._handleStateChange(event, RaviSignalingStates.CONNECTING); + // And call the implementation's open method this._signalingImplementation._open(URL); }); @@ -230,40 +217,25 @@ export class RaviSignalingConnection { /** * Close the signaling connection. Returns a Promise - * that will resolve with the closed state once the RaviSignalingConnection is closed. + * that will resolve once the RaviSignalingConnection is closed. * * @returns {Promise} */ closeRAVISignalingConnection() { var signalingConnection = this; + if (this._state === RaviSignalingStates.CLOSED) return Promise.resolve( + "Signaling connection is already closed." + ); return new Promise((resolve, reject) => { - RaviUtils.log("Closing signaling connection", "RaviSignalingConnection"); - // Add a state change handler that will resolve the - // promise when the connection is closed - const stateHandler = function(event: any) { - var state = ""; - if (event && event.state) state = event.state; - - if (state === RaviSignalingStates.CLOSING) { - RaviUtils.log("Closing...", "RaviSignalingConnection"); - } else if (state === RaviSignalingStates.CLOSED) { - // Remove this as a state change handler - signalingConnection.removeStateChangeHandler(stateHandler); - // and resolve the Promise - resolve(state); - } else { - // Remove this as a state change handler - signalingConnection.removeStateChangeHandler(stateHandler); - // and reject the Promise - reject(Error(state)); - } - } - signalingConnection.addStateChangeHandler(stateHandler); - // And then start the "closing" process + signalingConnection._resolveClose = resolve; + signalingConnection._rejectClose = reject; + // Start the "closing" process + RaviUtils.log("Closing signaling connection", "RaviSignalingController"); var event = {"state":RaviSignalingStates.CLOSING}; this._handleStateChange(event, RaviSignalingStates.CLOSING); - // And call the implementation's close method + + // And call the implementation's open method this._signalingImplementation._close(); }); } @@ -273,15 +245,54 @@ export class RaviSignalingConnection { /** * @private */ - _handleStateChange(event: any, state: any) { - this._state = state; - event["state"] = state; - RaviUtils.log("_handleStateChange: " + RaviUtils.safelyPrintable(event), "RaviSignalingConnection"); - this._stateChangeHandlers.forEach(function(handler) { - if (handler) { - handler(event); - } - }); + _handleStateChange(event: any, state: RaviSignalingStates) { + // Always try to fulfill any open promises, even if the state hasn't changed + this._fulfillPromises(event, state); + + // But only call handlers if the state did, in fact, change + if (state !== this._state) { + this._state = state; + event["state"] = state; + RaviUtils.log("_handleStateChange: " + RaviUtils.safelyPrintable(event), "RaviSignalingConnection"); + this._stateChangeHandlers.forEach(function(handler) { + if (handler) { + handler(event); + } + }); + } + } + + /** + * @private + * Gets called whenever the state changes (and sometimes when it doesn't, + * but when we just want to make sure). Depending on the new (or current) state, + * this will appropriately fulfill outstanding promises that are pending + * in either the open or close method (or both). + */ + _fulfillPromises(event: any, state: RaviSignalingStates) { + let errorMessage = event.reason || event.message || state; + RaviUtils.log("_fulfillPromises: Handling state " + state, "RaviSignalingConnection"); + switch(state) { + case RaviSignalingStates.OPEN: + if (this._resolveOpen) this._resolveOpen(); + if (this._rejectClose) this._rejectClose(errorMessage); + break; + case RaviSignalingStates.CLOSED: + if (this._rejectOpen) this._rejectOpen(errorMessage); + if (this._resolveClose) this._resolveClose(); + break; + case RaviSignalingStates.ERROR: + if (this._rejectOpen) this._rejectOpen(errorMessage); + if (this._rejectClose) this._rejectClose(errorMessage); + break; + case RaviSignalingStates.UNAVAILABLE: + if (this._rejectOpen) this._rejectOpen(errorMessage); + if (this._resolveClose) this._resolveClose(); + break; + default: + // Do nothing for the "in progress" states, like "OPENING" or "CLOSING" + RaviUtils.log("_fulfillPromises: Skipping in-progress state " + state, "RaviSignalingConnection"); + } } /** @@ -297,7 +308,7 @@ export class RaviSignalingConnection { if (message.data) { try { let messageData = JSON.parse(message.data); - if (messageData.error && messageData.error == "service-unavailable") { + if (messageData.error && messageData.error === "service-unavailable") { this._handleStateChange({}, RaviSignalingStates.UNAVAILABLE); } } catch(err) { @@ -370,11 +381,11 @@ class RaviSignalingWebSocketImplementation { * @private */ _open(socketAddress: string) { + var signalingConnection = this._raviSignalingConnection; - // If we already have an open websocket, just log and return + // If we already have an open websocket, make sure the signaling connection knows about it, and return immediately if (this._webSocket && this._webSocket.readyState === crossPlatformWebSocket.OPEN) { - RaviUtils.err("There is already an open WebSocket connection. To reconnect, first close the existing WebSocket and then attempt to open again.", - "RaviSignalingWebSocketImplementation"); + signalingConnection._handleStateChange(event, RaviSignalingStates.OPEN); return; } @@ -385,10 +396,18 @@ class RaviSignalingWebSocketImplementation { // stateChangeHandlers. // (We can't set these until we attempt to open the // WebSocket, because there's no other WebSocket constructor.) - var signalingConnection = this._raviSignalingConnection; this._webSocket.addEventListener('open', function(event: any) { signalingConnection._handleStateChange(event, RaviSignalingStates.OPEN); }); this._webSocket.addEventListener('error', function(event: any) { signalingConnection._handleStateChange(event, RaviSignalingStates.ERROR); }); - this._webSocket.addEventListener('close', function(event: any) { signalingConnection._handleStateChange(event, RaviSignalingStates.CLOSED); }); + this._webSocket.addEventListener('close', function(event: any) { + if (event && event.code && event.code > 4000) { + // This "close" event is really an error, because we're + // returning one of our custom error codes. Treat it as such. + RaviUtils.err("_handleStateChange: signaling error code " + event.code + ": " + event.reason, "RaviSignalingConnection"); + signalingConnection._handleStateChange(event, RaviSignalingStates.ERROR); + } else { + signalingConnection._handleStateChange(event, RaviSignalingStates.CLOSED); + } + }); // Any additional messaging handling gets done by the main // RaviSignalingConnection's messageHandlers @@ -409,10 +428,14 @@ class RaviSignalingWebSocketImplementation { * @private */ _close() { - if (this._webSocket) { - this._webSocket.close(); - this._webSocket = null; + var signalingConnection = this._raviSignalingConnection; + // If we're already closed, make sure the signaling connection knows about it and return immediately + if (! this._webSocket || this._webSocket.readyState === crossPlatformWebSocket.CLOSED) { + signalingConnection._handleStateChange(event, RaviSignalingStates.CLOSED); + return; } + this._webSocket.close(); + this._webSocket = null; } } From aa26e8a1f052b3633678956f445b7c8da984e395 Mon Sep 17 00:00:00 2001 From: Maia Hansen Date: Wed, 26 May 2021 12:24:29 -0700 Subject: [PATCH 17/32] Updated tests for new error messages --- tests/integration/serverConnections.integration.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/serverConnections.integration.test.ts b/tests/integration/serverConnections.integration.test.ts index 5b558ffc..7c2bd5d3 100644 --- a/tests/integration/serverConnections.integration.test.ts +++ b/tests/integration/serverConnections.integration.test.ts @@ -144,7 +144,7 @@ describe('Mixer connections', () => { } await expect(hifiCommunicator.connectToHiFiAudioAPIServer(nonadminUnsigned, stackURL)) - .rejects.toMatchObject({ error: expect.stringMatching(/Unexpected server response: 501/) }); + .rejects.toMatchObject({ error: expect.stringMatching(/signature verification failed/) }); // confirm user is not connected let usersListMessage = await fetch(`${stackURL}/api/v1/spaces/${space1id}/users?token=${admin}`); @@ -161,7 +161,7 @@ describe('Mixer connections', () => { // TEST THIS NEXT to ensure admin is not already connected test(`CANNOT connect to a space using a timed token after the token expires`, async () => { await expect(hifiCommunicator.connectToHiFiAudioAPIServer(nonadminExpired, stackURL)) - .rejects.toMatchObject({ error: expect.stringMatching(/Unexpected server response: 501/) }); + .rejects.toMatchObject({ error: expect.stringMatching(/signature verification failed/) }); // confirm user is not connected let usersListMessage = await fetch(`${stackURL}/api/v1/spaces/${space1id}/users?token=${admin}`); @@ -177,13 +177,13 @@ describe('Mixer connections', () => { test(`CANNOT connect to a space on staging that doesn’t exist (i.e. token contains an invalid space ID)`, async () => { await expect(hifiCommunicator.connectToHiFiAudioAPIServer(nonadminNonexistentSpaceID, stackURL)) - .rejects.toMatchObject({ error: expect.stringMatching(/Unexpected server response: 501/) }); + .rejects.toMatchObject({ error: expect.stringMatching(/token decode failed/) }); }); test(`CANNOT connect to a space BY NAME when multiple spaces with the same name exist in the same app`, async () => { await expect(hifiCommunicator.connectToHiFiAudioAPIServer(nonadminDupSpaceName, stackURL)) .rejects.toMatchObject({ - error: expect.stringMatching(/Unexpected server response: 501/) + error: expect.stringMatching(/possibly more than one space has the given name/) }); }); }); From 7e474deba3f5892940a7fe2c53d929c5a33059c3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 27 May 2021 11:52:44 -0700 Subject: [PATCH 18/32] get websocket port from url when available --- src/classes/HiFiCommunicator.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/classes/HiFiCommunicator.ts b/src/classes/HiFiCommunicator.ts index 97fe1f69..f2c75f4c 100644 --- a/src/classes/HiFiCommunicator.ts +++ b/src/classes/HiFiCommunicator.ts @@ -244,7 +244,13 @@ export class HiFiCommunicator { let signalingHostURLSafe; try { - signalingHostURLSafe = new URL(signalingHostURL).hostname; + let url = new URL(signalingHostURL); + signalingHostURLSafe = url.hostname; + if (signalingPort == null && url.port !== "") { + // sometimes the signalingPort is specified in the signalHostURL in which case + // we extract the port number rather than fallback to default + signalingPort = Number(url.port); + } } catch(e) { // If signalingHostURL is not defined, we assign the default URL signalingHostURLSafe = signalingHostURL ? signalingHostURL : HiFiConstants.DEFAULT_PROD_HIGH_FIDELITY_ENDPOINT; From d2e80a55dee8c8e69c71d1fc1c148f378d9be3ab Mon Sep 17 00:00:00 2001 From: hifibuild Date: Thu, 27 May 2021 19:11:16 +0000 Subject: [PATCH 19/32] Bump package version to 1.1.2-4 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index add9f580..00b59a85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-3", + "version": "1.1.2-4", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 87898ff5..b93e2898 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-3", + "version": "1.1.2-4", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From b5b674b52c480ad7718ea50e98112832933a3e6d Mon Sep 17 00:00:00 2001 From: hifibuild Date: Thu, 27 May 2021 19:13:05 +0000 Subject: [PATCH 20/32] Bump package version to 1.1.2-5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00b59a85..94562799 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-4", + "version": "1.1.2-5", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b93e2898..6c3a9f99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-4", + "version": "1.1.2-5", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 7cf90c4aa5d1c790177504e820ce038ed79b0888 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 02:48:56 +0000 Subject: [PATCH 21/32] Bump ws from 7.4.3 to 7.4.6 Bumps [ws](https://github.com/websockets/ws) from 7.4.3 to 7.4.6. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.4.3...7.4.6) Signed-off-by: dependabot[bot] --- package-lock.json | 563 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 551 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 94562799..4d0000f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "hifi-spatial-audio", - "version": "0.8.1-1", + "version": "1.1.2-5", "license": "MIT", "dependencies": { "esm": "^3.2.25", @@ -16,12 +16,15 @@ }, "devDependencies": { "@types/jest": "^26.0.20", + "@types/puppeteer": "^5.4.3", "aws-sdk": "^2.831.0", "clean-webpack-plugin": "^3.0.0", "jest": "^26.6.3", "jest-stare": "^2.0.1", "jose": "^3.11.0", + "minimist": "^1.2.5", "node-fetch": "^2.6.1", + "puppeteer": "^9.0.0", "ts-jest": "^26.4.4", "ts-loader": "^8.0.14", "typedoc": "^0.20.29", @@ -647,7 +650,6 @@ "jest-resolve": "^26.6.2", "jest-util": "^26.6.2", "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", @@ -922,6 +924,15 @@ "integrity": "sha512-O3SQC6+6AySHwrspYn2UvC6tjo6jCTMMmylxZUFhE1CulVu5l3AxU6ca9lrJDTQDVllF62LIxVSx5fuYL6LiZg==", "dev": true }, + "node_modules/@types/puppeteer": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", + "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -998,6 +1009,16 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "node_modules/@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", @@ -1219,6 +1240,18 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1609,6 +1642,55 @@ "node": "*" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bootstrap": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", @@ -1693,6 +1775,15 @@ "isarray": "^1.0.0" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2307,6 +2398,12 @@ "node": ">=8" } }, + "node_modules/devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2332,7 +2429,6 @@ "dev": true, "dependencies": { "diff": "4.0.2", - "highlight.js": "10.4.1", "hogan.js": "3.0.2" }, "engines": { @@ -2857,6 +2953,26 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -2899,6 +3015,15 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2968,6 +3093,12 @@ "node": ">=0.10.0" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -3208,7 +3339,6 @@ "minimist": "^1.2.5", "neo-async": "^2.6.0", "source-map": "^0.6.1", - "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" }, "bin": { @@ -3400,6 +3530,19 @@ "npm": ">=1.3.7" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -3992,7 +4135,6 @@ "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", "jest-serializer": "^26.6.2", @@ -4914,6 +5056,12 @@ "node": "*" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "node_modules/moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -5563,6 +5711,12 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -5756,6 +5910,12 @@ "node": ">= 6" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -5787,6 +5947,45 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz", + "integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/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/qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -7155,6 +7354,48 @@ "node": ">=4.5" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/tar/node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -7254,6 +7495,12 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, "node_modules/tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -7594,6 +7841,40 @@ "node": ">=0.8.0" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unbzip2-stream/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -8196,7 +8477,6 @@ "resolved": "https://registry.npmjs.org/wrtc/-/wrtc-0.4.7.tgz", "integrity": "sha512-P6Hn7VT4lfSH49HxLHcHhDq+aFf/jd9dPY7lDHeFhZ22N3858EKuwm2jmnlPzpsRGEPaoF6XwkcxY5SYnt4f/g==", "dependencies": { - "domexception": "^1.0.1", "node-pre-gyp": "^0.13.0" }, "engines": { @@ -8222,11 +8502,23 @@ "optional": true }, "node_modules/ws": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", - "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "engines": { "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/xml-name-validator": { @@ -8315,6 +8607,16 @@ "node": ">=6" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -9142,6 +9444,15 @@ "integrity": "sha512-O3SQC6+6AySHwrspYn2UvC6tjo6jCTMMmylxZUFhE1CulVu5l3AxU6ca9lrJDTQDVllF62LIxVSx5fuYL6LiZg==", "dev": true }, + "@types/puppeteer": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", + "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -9217,6 +9528,16 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@webassemblyjs/ast": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", @@ -9429,6 +9750,15 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -9746,6 +10076,40 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "bootstrap": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", @@ -9818,6 +10182,12 @@ "isarray": "^1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -10324,6 +10694,12 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -10752,6 +11128,18 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -10791,6 +11179,15 @@ "bser": "2.1.1" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -10842,6 +11239,12 @@ "map-cache": "^0.2.2" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -11185,6 +11588,16 @@ "sshpk": "^1.7.0" } }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -12403,6 +12816,12 @@ "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", "dev": true }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -12930,6 +13349,12 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -13077,6 +13502,12 @@ "sisteransi": "^1.0.5" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -13105,6 +13536,37 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "puppeteer": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz", + "integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -14235,6 +14697,44 @@ } } }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -14306,6 +14806,12 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -14566,6 +15072,28 @@ "dev": true, "optional": true }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -15083,9 +15611,10 @@ } }, "ws": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", - "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==" + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "requires": {} }, "xml-name-validator": { "version": "3.0.0", @@ -15163,6 +15692,16 @@ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", From 42e01a0936c4a0aa65870495b80508c6a9b1a569 Mon Sep 17 00:00:00 2001 From: hifibuild Date: Tue, 1 Jun 2021 22:55:50 +0000 Subject: [PATCH 22/32] Bump package version to 1.1.2-6 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d0000f8..33ea7e29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-5", + "version": "1.1.2-6", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6c3a9f99..f0877c83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-5", + "version": "1.1.2-6", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From eff3021c4d744c6ca81a88f18a9bc4953914b579 Mon Sep 17 00:00:00 2001 From: RebeccaStankus Date: Tue, 1 Jun 2021 16:16:19 -0700 Subject: [PATCH 23/32] Update auth file with new EU data --- tests/secrets/auth.json.gpg | Bin 928 -> 925 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/secrets/auth.json.gpg b/tests/secrets/auth.json.gpg index a373df2b26e72ac0ab6dd189a01b7614d1051872..b756a028b8634867abf3a552e9fe4e979e2946a8 100644 GIT binary patch literal 925 zcmV;O17iG)4Fm}T0u*AG$?!R%k;l^M0fgTPf035UNK2{pUgEPpqAkg(_%D5Lf7QXz zv5*$MYCIcKh9+%rV=`}_XvfcvG@E?f2xQS!B&u1Gvf~pbxZurjsj|fyflfR2!(%T6 zQ0;8XKrST#mjxm=G$u3Zj9*XAfMA@CS)1wKu0v zP4{&8hYp(B7ux!qGX3B2s6_%x5T6JJujS#)kP8eApC9-87oJtPZO{qf+IwG&fGMf4 zpiC98Ab9gL&qf?w-kCU9jsi#Rxd}@oAagO;^*L4A=fojs8B~1Ql>o{MEjEq^D1jI?wVhog4NO1xfEe5@f3>(&|@v0rRxxH zy1B(f!6E2Eyon*Jt|Lz_5mzrvG!U?*ySEcvhs}@Xc28SLQ}N4h@kq}#&)@xQHB#!q zO@P;08#btRDM$}(?ONw!z{;G`y-qp9EmY;IRp_v1@tm9G*ZMH3<;#>66INVoBnOnq z7q&#&qU`aB`liMecUdc2P=FR_QhyY?=-75EPd-;Yi3Z>eOd+c2V+zyLgX>&@Y~>C; zJ*~`fg!DcS?0^i#M+~HQjyfIdSrn`yN4x@|=CB6iu52#Fyd1qfohC2eK}GN2`J5P@ z{gYn^sF~DCOY>C1tC}qKdE?X?=Dm66-WF`Ox8frQj$8$egAcgpT7I^ysMGfNf>_SI zWPjPq(m5fksjQm6DF!d z2RgImkjjnNBP4R?PX~GUTbL&|m*r?xGj($6;1n7aUuz)zqF-Ep8gEnr$dDD+FTdyK z)&|>A9Vu2(_tF#ndfdFr@`wdWvTe>cOh@=lQ#*=eg3U2zR@f#y%(N5U(9I7HsrJ-{ literal 928 zcmV;R17G}%4Fm}T0wl+p(lHDrd&<)30Vfe0Qze*mx{ApeG)3#EE%+R~92H{a6h#k> zhiTIcLXO7P!_+TkPIpANzZhY|$~_uG{#))}3sw=E0&G6ZvVntG@q7~V)6WEzl`8%QLFiE4O&xq&Mu*t+ z!G-hY*x3UcK0fuak?dsG4YFKJB0s^A%s}>jy0&6ZjzGzJJyX7z7C)iR<-Tc+BIs)e z#e5Gvbr{rw3SAp zO<7+6(xIgj$U^ffrk7VpE47xI2`P(s4tG|1P-iKQ+8@9INOrW#;LM-gmgz+(r_`P| z7fNCFSgz0+_3lH0GP$f~R{!>0mBU!;?g6T|_nGs2FHCs5RMgqQZn)jXUeoN~>qg8iUx5p)>L#bF-LB_?FOiH1^CN4ej`J8D(#8tf01 zK&AxC;JX=@Wkw&(qCnbAm^g$eI&!49w+T;K^dDmceJboW9mWrxqFLsWgO-ZbLWZ`y zaYTap;spw~MNk}LH&BdlnWmc}Wtc&(zHt>|TVjlX1M!R9kd_@Ml-T`PP10Q7Z1(|` zIA}m9lfPFqpx}t zDN;JqDJs_xaiJ2tfn}e?zi}Xz#PyuCMM;&ZI(;|ztwFn!M`SqpDoz7J(JsL+ySnfC z;Ggr=XV;Z&xh1?D48+=B Date: Wed, 2 Jun 2021 18:07:51 +0000 Subject: [PATCH 24/32] Bump package version to 1.1.2-7 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33ea7e29..229c8408 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-6", + "version": "1.1.2-7", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index f0877c83..d3e3355f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-6", + "version": "1.1.2-7", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 126e280da067135aad7a6bd8075b11f4cd6441ba Mon Sep 17 00:00:00 2001 From: hifibuild Date: Wed, 2 Jun 2021 20:47:30 +0000 Subject: [PATCH 25/32] Bump package version to 1.1.2-8 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 229c8408..4ec989ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-7", + "version": "1.1.2-8", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index d3e3355f..6535168b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-7", + "version": "1.1.2-8", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 54af8fe5bb1a2854ab524afe2754c787b2d155bf Mon Sep 17 00:00:00 2001 From: Maia Hansen Date: Wed, 2 Jun 2021 16:09:22 -0700 Subject: [PATCH 26/32] HIFI-617: Provide the ability for a user to specify their own TURN config if desired. --- src/classes/HiFiCommunicator.ts | 29 ++++++++++++++-- src/classes/HiFiMixerSession.ts | 10 ++---- src/libravi/RaviSession.ts | 60 ++++++++++++++++++++++++++++++--- 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/classes/HiFiCommunicator.ts b/src/classes/HiFiCommunicator.ts index 624ad213..792ba20a 100644 --- a/src/classes/HiFiCommunicator.ts +++ b/src/classes/HiFiCommunicator.ts @@ -9,7 +9,7 @@ declare var HIFI_API_VERSION: string; import { HiFiConstants } from "../constants/HiFiConstants"; -import { WebRTCSessionParams } from "../libravi/RaviSession"; +import { WebRTCSessionParams, CustomSTUNandTURNConfig } from "../libravi/RaviSession"; import { HiFiLogger } from "../utilities/HiFiLogger"; import { HiFiUtilities } from "../utilities/HiFiUtilities"; import { HiFiAudioAPIData, ReceivedHiFiAudioAPIData, Point3D, OrientationQuat3D, OrientationEuler3D, OrientationEuler3DOrder, eulerToQuaternion, eulerFromQuaternion, OtherUserGainMap } from "./HiFiAudioAPIData"; @@ -88,6 +88,7 @@ export class HiFiCommunicator { private _mixerSession: HiFiMixerSession; private _webRTCSessionParams?: WebRTCSessionParams; + private _customSTUNandTURNConfig?: CustomSTUNandTURNConfig; /** * Constructor for the HiFiCommunicator object. Once you have created a HiFiCommunicator, you can use the @@ -103,6 +104,10 @@ export class HiFiCommunicator { * @param hiFiAxisConfiguration - Cannot be set later. The 3D axis configuration. See {@link ourHiFiAxisConfiguration} for defaults. * @param webrtcSessionParams - Cannot be set later. Extra parameters used for configuring the underlying WebRTC connection to the API servers. * These settings are not frequently used; they are primarily for specific jitter buffer configurations. + * @param customSTUNandTURNConfig - Cannot be set later. This object can be used if specific STUN and TURN server information needs to be + * provided for negotiating the underlying WebRTC connection. By default, High Fidelity's TURN server will be used, which should suffice + * for most operations. This is primarily useful for testing or for using a commercial TURN server provider for dealing with particularly challenging client networks/firewalls. + * See {@link CustomSTUNandTURNConfig} for the format of this object (note that _all_ values must be provided when setting this). * @param onMuteChanged - A function that will be called when the mute state of the client has changed, for example when muted by an admin. See {@link OnMuteChangedCallback} for the information this function will receive. */ constructor({ @@ -113,6 +118,7 @@ export class HiFiCommunicator { userDataStreamingScope = HiFiUserDataStreamingScopes.All, hiFiAxisConfiguration, webrtcSessionParams, + customSTUNandTURNConfig, onMuteChanged }: { initialHiFiAudioAPIData?: HiFiAudioAPIData, @@ -122,8 +128,27 @@ export class HiFiCommunicator { userDataStreamingScope?: HiFiUserDataStreamingScopes, hiFiAxisConfiguration?: HiFiAxisConfiguration, webrtcSessionParams?: WebRTCSessionParams, + customSTUNandTURNConfig?: CustomSTUNandTURNConfig, onMuteChanged?: OnMuteChangedCallback, } = {}) { + // If user passed in their own stun/turn config, make sure it matches our interface (ish). + // (I do so wish that TypeScript could just do this for us based on the interface definition, but it seems that it can not.) + if (customSTUNandTURNConfig) { + if (!customSTUNandTURNConfig.hasOwnProperty("stunUrls") || !Array.isArray(customSTUNandTURNConfig.stunUrls) || customSTUNandTURNConfig.stunUrls.length == 0 ) { + throw new Error(`\`customSTUNandTURNConfig.stunUrls\` must be specified and must be a list containing at least one STUN server.`); + } + if (!customSTUNandTURNConfig.hasOwnProperty("turnUrls") || !Array.isArray(customSTUNandTURNConfig.turnUrls) || customSTUNandTURNConfig.turnUrls.length == 0 ) { + throw new Error(`\`customSTUNandTURNConfig.turnUrls\` must be specified and must be a list containing at least one TURN server.`); + } + if (!customSTUNandTURNConfig.hasOwnProperty("turnUsername")) { + throw new Error(`\`customSTUNandTURNConfig.turnUsername\` must be specified.`); + } + if (!customSTUNandTURNConfig.hasOwnProperty("turnCredential")) { + throw new Error(`\`customSTUNandTURNConfig.turnCredential\` must be specified.`); + } + } + this._customSTUNandTURNConfig = customSTUNandTURNConfig; + // Make minimum 10ms if (transmitRateLimitTimeoutMS < HiFiConstants.MIN_TRANSMIT_RATE_LIMIT_TIMEOUT_MS) { HiFiLogger.warn(`\`transmitRateLimitTimeoutMS\` must be >= ${HiFiConstants.MIN_TRANSMIT_RATE_LIMIT_TIMEOUT_MS}ms! Setting to ${HiFiConstants.MIN_TRANSMIT_RATE_LIMIT_TIMEOUT_MS}ms...`); @@ -264,7 +289,7 @@ export class HiFiCommunicator { HiFiLogger.log(`Using WebRTC Signaling Address:\n${webRTCSignalingAddress}`); - mixerConnectionResponse = await this._mixerSession.connectToHiFiMixer({ webRTCSessionParams: this._webRTCSessionParams }); + mixerConnectionResponse = await this._mixerSession.connectToHiFiMixer({ webRTCSessionParams: this._webRTCSessionParams, customSTUNandTURNConfig: this._customSTUNandTURNConfig }); } catch (errorConnectingToMixer) { let errMsg = `Error when connecting to mixer!\n${errorConnectingToMixer}`; return Promise.reject({ diff --git a/src/classes/HiFiMixerSession.ts b/src/classes/HiFiMixerSession.ts index 70bfd19f..a7da1cb5 100644 --- a/src/classes/HiFiMixerSession.ts +++ b/src/classes/HiFiMixerSession.ts @@ -8,12 +8,8 @@ import { HiFiAudioAPIData, OrientationQuat3D, Point3D, ReceivedHiFiAudioAPIData, import { HiFiLogger } from "../utilities/HiFiLogger"; import { HiFiConnectionStates, HiFiUserDataStreamingScopes } from "./HiFiCommunicator"; -// We use @ts-ignore here so TypeScript doesn't complain about importing these plain JS modules. -// @ts-ignore import { RaviUtils } from "../libravi/RaviUtils"; -// @ts-ignore -import { RaviSession, RaviSessionStates, WebRTCSessionParams } from "../libravi/RaviSession"; -// @ts-ignore +import { RaviSession, RaviSessionStates, WebRTCSessionParams, CustomSTUNandTURNConfig } from "../libravi/RaviSession"; import { RaviSignalingConnection, RaviSignalingStates } from "../libravi/RaviSignalingConnection"; import { HiFiAxisUtilities, ourHiFiAxisConfiguration } from "./HiFiAxisConfiguration"; const pako = require('pako'); @@ -504,7 +500,7 @@ export class HiFiMixerSession { * @param webRTCSessionParams - Parameters passed to the RAVI session when opening that session. * @returns A Promise that rejects with an error message string upon failure, or resolves with the response from `audionet.init` as a string. */ - async connectToHiFiMixer({ webRTCSessionParams }: { webRTCSessionParams?: WebRTCSessionParams }): Promise { + async connectToHiFiMixer({ webRTCSessionParams, customSTUNandTURNConfig }: { webRTCSessionParams?: WebRTCSessionParams, customSTUNandTURNConfig?: CustomSTUNandTURNConfig }): Promise { if (this._currentHiFiConnectionState === HiFiConnectionStates.Connected && this.mixerInfo["connected"]) { let msg = `Already connected! If a reconnect is needed, please hang up and try again.`; @@ -547,7 +543,7 @@ export class HiFiMixerSession { } try { - await this._raviSession.openRAVISession({ signalingConnection: this._raviSignalingConnection, params: webRTCSessionParams }); + await this._raviSession.openRAVISession({ signalingConnection: this._raviSignalingConnection, params: webRTCSessionParams, customStunAndTurn: customSTUNandTURNConfig }); } catch (errorOpeningRAVISession) { let errMsg = `Couldn't open RAVI session associated with \`${this.webRTCAddress.slice(0, this.webRTCAddress.indexOf("token="))}\`! Error:\n${errorOpeningRAVISession}`; if (mixerIsUnavailable) { diff --git a/src/libravi/RaviSession.ts b/src/libravi/RaviSession.ts index 9e70cdbb..f42532b2 100644 --- a/src/libravi/RaviSession.ts +++ b/src/libravi/RaviSession.ts @@ -20,6 +20,40 @@ export interface WebRTCSessionParams { * of potentially higher round-trip audio latency on poor connections. */ audioMaxJitterBufferDuration?: number; +} + +/** + * Allows a user to override the default (or server-provided) + * STUN and TURN configuration that is offered by the client. Note that if a custom stun and turn configuration is + * used, all of its values must be provided! + * (Setting these values does not have any effect on what the server offers for its stun and turn configuration; + * however, the servers are generally expected to be configured so as to be easily reachable without the need for TURN + * or through the use of the standard TURN servers.) + */ +export interface CustomSTUNandTURNConfig { + /** + * A list of STUN server URLs to use. This should be a list of strings, each of which is in the + * format "stun:x.y.z" or "stun:x.y.z:port". For instance: + * + * [ "stun:foo.bar.com:19302", "stun:bar.baz.com" ] + */ + stunUrls: string[]; + /** + * A list of TURN server URLs to use. This should be a list of strings, each of which is in the + * format "turn:x.y.z" or "turn:x.y.z:port". For instance: + * + * [ "turn:foo.bar.com:3478", "turn:bar.baz.com" ] + * + */ + turnUrls: string[]; + /** + * A TURN username to use when connecting to the specified TURN server(s). + */ + turnUsername: string; + /** + * A TURN credential (password) to use when connecting to the specified TURN server(s). + */ + turnCredential: string; }; /** @@ -209,7 +243,7 @@ export class RaviSession { * * @returns {Promise} */ - openRAVISession({signalingConnection, timeout = 5000, params = null}: { signalingConnection: RaviSignalingConnection, timeout?: number, params?: WebRTCSessionParams}) { + openRAVISession({signalingConnection, timeout = 5000, params = null, customStunAndTurn = null}: { signalingConnection: RaviSignalingConnection, timeout?: number, params?: WebRTCSessionParams, customStunAndTurn?: CustomSTUNandTURNConfig}) { if (this._state === RaviSessionStates.CONNECTED || this._state === RaviSessionStates.COMPLETED) { // Ref. iceconnectionstates at https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState @@ -239,7 +273,7 @@ export class RaviSession { // Start the "opening" process RaviUtils.log("Opening RAVI session", "RaviSession"); this._state = RaviSessionStates.NEW; - raviSession._raviImplementation._open(params); + raviSession._raviImplementation._open(params, customStunAndTurn); }); } @@ -664,6 +698,7 @@ class RaviWebRTCImplementation { _raviAudioSenders: any; _raviVideoSenders: any; _signalingConnection: RaviSignalingConnection; + _customStunAndTurn: CustomSTUNandTURNConfig; // Need to keep track of the input streams in case they get set // before the actual RTC connection is available. @@ -888,8 +923,9 @@ class RaviWebRTCImplementation { * This method is called by the owning RaviSession. * @protected */ - _open(params: any) { + _open(params: WebRTCSessionParams, customStunAndTurn: CustomSTUNandTURNConfig) { RaviUtils.log("Attempting to open connection...", "RaviWebRTCImplementation"); + this._customStunAndTurn = customStunAndTurn; if (this._rtcConnection && (this._rtcConnection.connectionState == 'connecting' || this._rtcConnection.connectionState == 'connected')) @@ -1107,8 +1143,24 @@ class RaviWebRTCImplementation { if (signal.sdp) { RaviUtils.log("Received sdp type=" + signal.type, "RaviWebRTCImplementation"); + // use user-specified TURN config if found + if (sessionImplementation._customStunAndTurn) { + const CUSTOM_TURN_CONFIG = { + 'urls': sessionImplementation._customStunAndTurn.turnUrls, + 'username': sessionImplementation._customStunAndTurn.turnUsername, + 'credential': sessionImplementation._customStunAndTurn.turnCredential + }; + const CUSTOM_STUN_CONFIG = { + 'urls': sessionImplementation._customStunAndTurn.stunUrls + }; + peerConnectionConfig = { + 'iceServers': [ + CUSTOM_STUN_CONFIG, + CUSTOM_TURN_CONFIG + ] + }; // grab the TURN config if found - if (signal.turn && signal.turn.urls && signal.turn.username && signal.turn.credential) { + } else if (signal.turn && signal.turn.urls && signal.turn.username && signal.turn.credential) { // We appear to need to humor TypeScript by setting this to a constant the // same way we do for the default const DYNAMIC_TURN_CONFIG = { From cb0581d4dfb5f32c4d73bc06cb3ae59a9f09bdf6 Mon Sep 17 00:00:00 2001 From: hifibuild Date: Thu, 3 Jun 2021 00:06:58 +0000 Subject: [PATCH 27/32] Bump package version to 1.1.2-9 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ec989ce..a38772da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-8", + "version": "1.1.2-9", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6535168b..7026ed50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-8", + "version": "1.1.2-9", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From a85ccdfc13736037bed49719ef32da09a8976315 Mon Sep 17 00:00:00 2001 From: Maia Hansen Date: Thu, 3 Jun 2021 16:36:36 -0700 Subject: [PATCH 28/32] Pulling in v4.4.1 of libravi with event-handling bugfixes --- src/libravi/RaviSession.ts | 5 ++--- src/libravi/RaviSignalingConnection.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/libravi/RaviSession.ts b/src/libravi/RaviSession.ts index f42532b2..a401529a 100644 --- a/src/libravi/RaviSession.ts +++ b/src/libravi/RaviSession.ts @@ -314,7 +314,7 @@ export class RaviSession { * this will appropriately fulfill outstanding promises that are pending * in either the open or close method (or both). */ - _fulfillPromises(event: any, state: RaviSessionStates) { + _fulfillPromises(event: any = {}, state: RaviSessionStates) { let errorMessage = event.reason || event.message || state; switch(state) { case RaviSessionStates.CONNECTED: @@ -416,8 +416,7 @@ export class RaviSession { * Generic handler * @private */ - _handleStateChange(event: any, state: RaviSessionStates) { - if (!event) event = {}; + _handleStateChange(event: any = {}, state: RaviSessionStates) { event["state"] = state; // Always try to fulfill any open promises, even if the state hasn't changed diff --git a/src/libravi/RaviSignalingConnection.ts b/src/libravi/RaviSignalingConnection.ts index e02bb0ed..90e9ce2e 100644 --- a/src/libravi/RaviSignalingConnection.ts +++ b/src/libravi/RaviSignalingConnection.ts @@ -198,7 +198,7 @@ export class RaviSignalingConnection { signalingConnection._rejectOpen = reject; // Start the "opening" process RaviUtils.log("Opening signaling connection to " + URL, "RaviSignalingController"); - var event = {"state":RaviSignalingStates.CONNECTING}; + let event = {"state":RaviSignalingStates.CONNECTING}; this._handleStateChange(event, RaviSignalingStates.CONNECTING); // And call the implementation's open method @@ -232,7 +232,7 @@ export class RaviSignalingConnection { signalingConnection._rejectClose = reject; // Start the "closing" process RaviUtils.log("Closing signaling connection", "RaviSignalingController"); - var event = {"state":RaviSignalingStates.CLOSING}; + let event = {"state":RaviSignalingStates.CLOSING}; this._handleStateChange(event, RaviSignalingStates.CLOSING); // And call the implementation's open method @@ -245,7 +245,7 @@ export class RaviSignalingConnection { /** * @private */ - _handleStateChange(event: any, state: RaviSignalingStates) { + _handleStateChange(event: any = {}, state: RaviSignalingStates) { // Always try to fulfill any open promises, even if the state hasn't changed this._fulfillPromises(event, state); @@ -269,7 +269,7 @@ export class RaviSignalingConnection { * this will appropriately fulfill outstanding promises that are pending * in either the open or close method (or both). */ - _fulfillPromises(event: any, state: RaviSignalingStates) { + _fulfillPromises(event: any = {}, state: RaviSignalingStates) { let errorMessage = event.reason || event.message || state; RaviUtils.log("_fulfillPromises: Handling state " + state, "RaviSignalingConnection"); switch(state) { @@ -385,7 +385,7 @@ class RaviSignalingWebSocketImplementation { // If we already have an open websocket, make sure the signaling connection knows about it, and return immediately if (this._webSocket && this._webSocket.readyState === crossPlatformWebSocket.OPEN) { - signalingConnection._handleStateChange(event, RaviSignalingStates.OPEN); + signalingConnection._handleStateChange({}, RaviSignalingStates.OPEN); return; } @@ -431,7 +431,7 @@ class RaviSignalingWebSocketImplementation { var signalingConnection = this._raviSignalingConnection; // If we're already closed, make sure the signaling connection knows about it and return immediately if (! this._webSocket || this._webSocket.readyState === crossPlatformWebSocket.CLOSED) { - signalingConnection._handleStateChange(event, RaviSignalingStates.CLOSED); + signalingConnection._handleStateChange({}, RaviSignalingStates.CLOSED); return; } this._webSocket.close(); From 081aced7b6f32cb50b139d3c25a3e019d594eb40 Mon Sep 17 00:00:00 2001 From: hifibuild Date: Thu, 3 Jun 2021 23:38:55 +0000 Subject: [PATCH 29/32] Bump package version to 1.1.2-10 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a38772da..22d0f1bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-9", + "version": "1.1.2-10", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 7026ed50..0f57aec2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-9", + "version": "1.1.2-10", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 17b558e5076bb054114f11ba25a7e70fa82dfac4 Mon Sep 17 00:00:00 2001 From: Maia Hansen Date: Thu, 3 Jun 2021 17:12:48 -0700 Subject: [PATCH 30/32] Pulling in v4.4.2 of libravi, with updated error message for timeouts --- src/libravi/RaviSession.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libravi/RaviSession.ts b/src/libravi/RaviSession.ts index a401529a..274d9bfa 100644 --- a/src/libravi/RaviSession.ts +++ b/src/libravi/RaviSession.ts @@ -260,8 +260,9 @@ export class RaviSession { // Set a timeout in case the session gets hung up somewhere this._openingTimeout = setTimeout(() => { - RaviUtils.log("RaviSession.open timed out after " + timeout + " ms", "RaviSession"); - raviSession._fulfillPromises({}, RaviSessionStates.FAILED); + let errorMessage = "RaviSession.open timed out after " + timeout + " ms"; + RaviUtils.log(errorMessage, "RaviSession"); + raviSession._fulfillPromises({ message: errorMessage }, RaviSessionStates.FAILED); // Close the session to clean up objects. We've already rejected the promise above. raviSession.closeRAVISession(); }, timeout); From 83f1df0852c2e2c1d4654e1c45bde132cf3869e6 Mon Sep 17 00:00:00 2001 From: hifibuild Date: Fri, 4 Jun 2021 00:14:20 +0000 Subject: [PATCH 31/32] Bump package version to 1.1.2-11 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22d0f1bd..be109e19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-10", + "version": "1.1.2-11", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 0f57aec2..d0282149 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-10", + "version": "1.1.2-11", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 1a3b239ef1bfa829b25efa7bd0d23d3990fefd0a Mon Sep 17 00:00:00 2001 From: bridie-hifi Date: Mon, 7 Jun 2021 14:09:41 -0700 Subject: [PATCH 32/32] 1.2.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index be109e19..da3dcc3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-11", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index d0282149..9bbc1137 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hifi-spatial-audio", - "version": "1.1.2-11", + "version": "1.2.0", "description": "The High Fidelity Audio Client Library allows developers to integrate High Fidelity's spatial audio technology into their projects.", "main": "./dist/index.js", "types": "./dist/index.d.ts",