diff --git a/Project/package-lock.json b/Project/package-lock.json index ab4d1cc0..66be4cbc 100644 --- a/Project/package-lock.json +++ b/Project/package-lock.json @@ -8,8 +8,8 @@ "name": "ACSCallingSample", "version": "1.0.0", "dependencies": { - "@azure/communication-calling": "1.37.1-beta.1", - "@azure/communication-calling-effects": "1.1.1-beta.1", + "@azure/communication-calling": "1.45.1-alpha.1", + "@azure/communication-calling-effects": "1.45.1-alpha.1", "@azure/communication-common": "^2.4.0", "@azure/communication-identity": "^1.3.0", "@azure/communication-network-traversal": "^1.1.0-beta.1", @@ -99,56 +99,25 @@ } }, "node_modules/@azure/communication-calling": { - "version": "1.37.1-beta.1", - "resolved": "https://registry.npmjs.org/@azure/communication-calling/-/communication-calling-1.37.1-beta.1.tgz", - "integrity": "sha512-M9UdieOnNRny7WzXr76FB3eIvTjObLez4Q0cQHa2ek44YWIpdFDwLRCfQyhGhq8YVh0RyuTNYRlsjA/lOhCfyw==", + "version": "1.45.1-alpha.1", + "resolved": "https://registry.npmjs.org/@azure/communication-calling/-/communication-calling-1.45.1-alpha.1.tgz", + "integrity": "sha512-RrH3/GlQOjZmiyb2jryrTwyVgP5r4GwKwyaHwb+RxpTruQOFYDU9GKCoL0OxsYnkONDqgX4uasxETN4gtcGigw==", "license": "Microsoft Software License Terms for the Azure Communications Services, Azure CPaaS, ACS Software Development Kit SDK, see LICENSE file", "dependencies": { - "@azure/communication-common": "2.3.2-beta.1", + "@azure/communication-common": "^2.4.0", "@azure/logger": "^1.0.3" } }, "node_modules/@azure/communication-calling-effects": { - "version": "1.1.1-beta.1", - "resolved": "https://registry.npmjs.org/@azure/communication-calling-effects/-/communication-calling-effects-1.1.1-beta.1.tgz", - "integrity": "sha512-hz0wEHBFSNVivS7qnDwcnJuONYqlV41VpQpEi+hEGcM4+8G4MQwC4nG7dOr2OX2AkJoX3ZTY2ZC5X3igLy9U4A==", + "version": "1.45.1-alpha.1", + "resolved": "https://registry.npmjs.org/@azure/communication-calling-effects/-/communication-calling-effects-1.45.1-alpha.1.tgz", + "integrity": "sha512-aqpC3YenA6LCiU85yIaAn3Io3GCrVki2molrmRv5TMeedJtAVsaYJPnHK9GR0u3tmlxU772G/4IUR3WZDdNzpQ==", + "license": "Microsoft Software License Terms for the Azure Communications Services, Azure CPaaS, ACS Software Development Kit SDK, see LICENSE file", "dependencies": { "@azure/logger": "^1.0.2", "events": "3.3.0" } }, - "node_modules/@azure/communication-calling/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/communication-calling/node_modules/@azure/communication-common": { - "version": "2.3.2-beta.1", - "resolved": "https://registry.npmjs.org/@azure/communication-common/-/communication-common-2.3.2-beta.1.tgz", - "integrity": "sha512-iZmSxSXv/EuMIsyw6lxfADexpwA9Sst4Is84QLTD/pcQ6YkzuiGo6SfePbQelredgHHLHVjbqkwCcA19ym/yiw==", - "license": "MIT", - "dependencies": { - "@azure-rest/core-client": "^2.3.3", - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.9.0", - "@azure/core-rest-pipeline": "^1.17.0", - "@azure/core-tracing": "^1.2.0", - "@azure/core-util": "^1.11.0", - "events": "^3.3.0", - "jwt-decode": "^4.0.0", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@azure/communication-common": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@azure/communication-common/-/communication-common-2.4.0.tgz", diff --git a/Project/package.json b/Project/package.json index a1caa1e4..959e32ad 100644 --- a/Project/package.json +++ b/Project/package.json @@ -3,8 +3,8 @@ "version": "1.0.0", "private": true, "dependencies": { - "@azure/communication-calling": "1.37.1-beta.1", - "@azure/communication-calling-effects": "1.1.1-beta.1", + "@azure/communication-calling": "1.45.1-alpha.1", + "@azure/communication-calling-effects": "1.45.1-alpha.1", "@azure/communication-common": "^2.4.0", "@azure/communication-identity": "^1.3.0", "@azure/communication-network-traversal": "^1.1.0-beta.1", @@ -26,7 +26,10 @@ "start-local": "webpack-dev-server --port 5000 --mode development", "build-local": "webpack --mode development", "start": "webpack-dev-server --host 0.0.0.0", - "build": "webpack" + "clean": "rm -rf dist", + "build": "npm run clean && webpack --env all", + "build:esm": "npm run clean && webpack --env esm --mode production", + "build:umd": "npm run clean && webpack --env umd --mode production" }, "devDependencies": { "@babel/core": "^7.8.7", diff --git a/Project/public/index.html b/Project/public/index.html index 2bf4de62..6eb2fec0 100644 --- a/Project/public/index.html +++ b/Project/public/index.html @@ -15,6 +15,22 @@ \ No newline at end of file diff --git a/Project/webpack.config.js b/Project/webpack.config.js index ee543b58..54676a9c 100644 --- a/Project/webpack.config.js +++ b/Project/webpack.config.js @@ -20,7 +20,6 @@ const communicationIdentityClient = new CommunicationIdentityClient(config.conn const PORT = process.env.port || 8080; - const oneSignalRegistrationTokenToAcsUserAccesTokenMap = new Map(); const registerCommunicationUserForOneSignal = async (communicationAccessToken, communicationUserIdentifier) => { const oneSignalRegistrationToken = generateGuid(); @@ -78,8 +77,10 @@ const getACSAccessTokenInfo = async (aadToken, userObjectId) => { return tokenResponse; } -// comment devServer.webSocketServer: false to enable hot reloading -module.exports = { +const path = require('path'); + +// Base configuration shared between ESM and UMD builds +const baseConfig = { devtool: 'inline-source-map', mode: 'development', entry: "./src/index.js", @@ -110,244 +111,335 @@ module.exports = { use: ["style-loader", "css-loader"] } ] + } +}; + +// ESM bundle configuration (production) +const esmConfig = { + ...baseConfig, + name: 'esm', + output: { + filename: 'bundle.js', + chunkFilename: '[name].[contenthash].js', + path: path.resolve(__dirname, 'dist/esm'), + publicPath: 'auto', + module: true, + library: { + type: 'module' + }, + environment: { + module: true, + dynamicImport: true + } + }, + experiments: { + outputModule: true }, plugins: [ new HtmlWebPackPlugin({ template: "./public/index.html", - filename: "./index.html" + filename: "../index.html", + inject: false }) - ], - devServer: { - open: true, - port: PORT, - static:'./public', - allowedHosts:[ - '.azurewebsites.net' - ], - webSocketServer: false, - setupMiddlewares: (middlewares, devServer) => { - if (!devServer) { - throw new Error('webpack-dev-server is not defined'); + ] +}; + +// UMD bundle configuration (production) +const umdConfig = { + ...baseConfig, + name: 'umd', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist/umd'), + publicPath: 'auto' + }, + module: { + ...baseConfig.module, + parser: { + javascript: { + dynamicImportMode: 'eager' } + } + }, + optimization: { + splitChunks: false + }, + plugins: [ + new HtmlWebPackPlugin({ + template: "./public/index.html", + filename: "../index.html", + inject: false + }) + ] +}; - devServer.app.use(bodyParser.json()); - devServer.app.post('/getCommunicationUserToken', async (req, res) => { - try { - const communicationUserId = req.body.communicationUserId; - const isJoinOnlyToken = req.body.isJoinOnlyToken === true; - let CommunicationUserIdentifier; - if (!communicationUserId) { - CommunicationUserIdentifier = await communicationIdentityClient.createUser(); - } else { - CommunicationUserIdentifier = { communicationUserId: communicationUserId }; - } - const communicationUserToken = await communicationIdentityClient.getToken(CommunicationUserIdentifier, [isJoinOnlyToken ? "voip.join" : "voip"]); - let oneSignalRegistrationToken; - if (config.functionAppOneSignalTokenRegistrationUrl) { - oneSignalRegistrationToken = await registerCommunicationUserForOneSignal(communicationUserToken, CommunicationUserIdentifier); - } - res.setHeader('Content-Type', 'application/json'); - res.status(200).json({communicationUserToken, oneSignalRegistrationToken, userId: CommunicationUserIdentifier }); - } catch (e) { - console.log('Error setting registration token', e); - res.sendStatus(500); +// comment devServer.webSocketServer: false to enable hot reloading +// Dev server settings (shared across all compilers) +const devServerSettings = { + open: true, + port: PORT, + static:'./public', + allowedHosts:[ + '.azurewebsites.net' + ], + webSocketServer: false, + setupMiddlewares: (middlewares, devServer) => { + if (!devServer) { + throw new Error('webpack-dev-server is not defined'); + } + + devServer.app.use(bodyParser.json()); + devServer.app.post('/getCommunicationUserToken', async (req, res) => { + try { + const communicationUserId = req.body.communicationUserId; + const isJoinOnlyToken = req.body.isJoinOnlyToken === true; + let CommunicationUserIdentifier; + if (!communicationUserId) { + CommunicationUserIdentifier = await communicationIdentityClient.createUser(); + } else { + CommunicationUserIdentifier = { communicationUserId: communicationUserId }; } - }); - devServer.app.post('/getCommunicationUserTokenForOneSignalRegistrationToken', async (req, res) => { - try { - const oneSignalRegistrationToken = req.body.oneSignalRegistrationToken; - const { communicationUserToken, communicationUserIdentifier } = oneSignalRegistrationTokenToAcsUserAccesTokenMap.get(oneSignalRegistrationToken); - res.setHeader('Content-Type', 'application/json'); - res.status(200).json({ communicationUserToken, userId: communicationUserIdentifier, oneSignalRegistrationToken }); - } catch (e) { - console.log('Error setting registration token', e); - res.sendStatus(500); + const communicationUserToken = await communicationIdentityClient.getToken(CommunicationUserIdentifier, [isJoinOnlyToken ? "voip.join" : "voip"]); + let oneSignalRegistrationToken; + if (config.functionAppOneSignalTokenRegistrationUrl) { + oneSignalRegistrationToken = await registerCommunicationUserForOneSignal(communicationUserToken, CommunicationUserIdentifier); } - }); - devServer.app.post('/getOneSignalRegistrationTokenForCommunicationUserToken', async (req, res) => { - try { - const communicationUserToken = {token: req.body.token }; - const communicationUserIdentifier = { communicationUserId: req.body.communicationUserId }; - - if (!config.functionAppOneSignalTokenRegistrationUrl) { - res.setHeader('Content-Type', 'application/json'); - res.status(200).json({ - communicationUserToken, userId: communicationUserIdentifier - }); - return; - } + res.setHeader('Content-Type', 'application/json'); + res.status(200).json({communicationUserToken, oneSignalRegistrationToken, userId: CommunicationUserIdentifier }); + } catch (e) { + console.log('Error setting registration token', e); + res.sendStatus(500); + } + }); + devServer.app.post('/getCommunicationUserTokenForOneSignalRegistrationToken', async (req, res) => { + try { + const oneSignalRegistrationToken = req.body.oneSignalRegistrationToken; + const { communicationUserToken, communicationUserIdentifier } = oneSignalRegistrationTokenToAcsUserAccesTokenMap.get(oneSignalRegistrationToken); + res.setHeader('Content-Type', 'application/json'); + res.status(200).json({ communicationUserToken, userId: communicationUserIdentifier, oneSignalRegistrationToken }); + } catch (e) { + console.log('Error setting registration token', e); + res.sendStatus(500); + } + }); + devServer.app.post('/getOneSignalRegistrationTokenForCommunicationUserToken', async (req, res) => { + try { + const communicationUserToken = {token: req.body.token }; + const communicationUserIdentifier = { communicationUserId: req.body.communicationUserId }; - let pair = [...oneSignalRegistrationTokenToAcsUserAccesTokenMap.entries()].find((pair) => { - return pair[1].token === communicationUserToken.token && - pair[1].communicationUserId === communicationUserIdentifier.communicationUserId; - }); - let oneSignalRegistrationToken; - if (pair) { - oneSignalRegistrationToken = pair[0]; - } else { - oneSignalRegistrationToken = await registerCommunicationUserForOneSignal(communicationUserToken, communicationUserIdentifier); - } - res.setHeader('Content-Type', 'application/json'); - res.status(200).json({ - communicationUserToken, - userId: communicationUserIdentifier, - oneSignalRegistrationToken - }); - } catch (e) { - console.log('Error setting registration token', e); - res.sendStatus(500); - } - }); - devServer.app.post('/teamsPopupLogin', async (req, res) => { - try { - const aadToken = req.body.aadToken; - const userObjectId = req.body.userObjectId; - let acsTokenInfo = await getACSAccessTokenInfo(aadToken, userObjectId); + if (!config.functionAppOneSignalTokenRegistrationUrl) { res.setHeader('Content-Type', 'application/json'); res.status(200).json({ - communicationUserToken: {token: acsTokenInfo.token}, - userId: acsTokenInfo.userId + communicationUserToken, userId: communicationUserIdentifier }); - } catch (e) { - console.error(e); - res.sendStatus(400); + return; } - }); - devServer.app.get('/entraConfig', async (req, res) => { - try { - res.setHeader('Content-Type', 'application/json'); - res.status(200).json(entraCredentialConfig); - } catch (e) { - console.error(e); - res.sendStatus(400); + + let pair = [...oneSignalRegistrationTokenToAcsUserAccesTokenMap.entries()].find((pair) => { + return pair[1].token === communicationUserToken.token && + pair[1].communicationUserId === communicationUserIdentifier.communicationUserId; + }); + let oneSignalRegistrationToken; + if (pair) { + oneSignalRegistrationToken = pair[0]; + } else { + oneSignalRegistrationToken = await registerCommunicationUserForOneSignal(communicationUserToken, communicationUserIdentifier); } - }); - devServer.app.post('/teamsM365Login', async (req, res) => { - try { - const email = req.body.email; - const password = req.body.password; - - const pca = new msal.PublicClientApplication(authConfig.configuration); - let tokenRequest = {scopes: authConfig.scopes.m365Login} + res.setHeader('Content-Type', 'application/json'); + res.status(200).json({ + communicationUserToken, + userId: communicationUserIdentifier, + oneSignalRegistrationToken + }); + } catch (e) { + console.log('Error setting registration token', e); + res.sendStatus(500); + } + }); + devServer.app.post('/teamsPopupLogin', async (req, res) => { + try { + const aadToken = req.body.aadToken; + const userObjectId = req.body.userObjectId; + let acsTokenInfo = await getACSAccessTokenInfo(aadToken, userObjectId); + res.setHeader('Content-Type', 'application/json'); + res.status(200).json({ + communicationUserToken: {token: acsTokenInfo.token}, + userId: acsTokenInfo.userId + }); + } catch (e) { + console.error(e); + res.sendStatus(400); + } + }); + devServer.app.get('/entraConfig', async (req, res) => { + try { + res.setHeader('Content-Type', 'application/json'); + res.status(200).json(entraCredentialConfig); + } catch (e) { + console.error(e); + res.sendStatus(400); + } + }); + devServer.app.post('/teamsM365Login', async (req, res) => { + try { + const email = req.body.email; + const password = req.body.password; + + const pca = new msal.PublicClientApplication(authConfig.configuration); + let tokenRequest = {scopes: authConfig.scopes.m365Login} - tokenRequest.username = email; - tokenRequest.password = password; - const response = await pca.acquireTokenByUsernamePassword(tokenRequest); - let acsTokenInfo = await getACSAccessTokenInfo(response.accessToken, response.uniqueId); - - res.setHeader('Content-Type', 'application/json'); - res.status(200).json({ - communicationUserToken: {token: acsTokenInfo.token}, - userId: acsTokenInfo.userId + tokenRequest.username = email; + tokenRequest.password = password; + const response = await pca.acquireTokenByUsernamePassword(tokenRequest); + let acsTokenInfo = await getACSAccessTokenInfo(response.accessToken, response.uniqueId); + + res.setHeader('Content-Type', 'application/json'); + res.status(200).json({ + communicationUserToken: {token: acsTokenInfo.token}, + userId: acsTokenInfo.userId + }); + } catch (e) { + console.error(e); + res.sendStatus(400); + } + }); + devServer.app.get('/authConfig', async (req, res) => { + try { + res.setHeader('Content-Type', 'application/json'); + res.status(200).json(authConfig); + } catch (e) { + console.error(e); + res.sendStatus(400); + } + }); + devServer.app.get('/blank', async (req, res) => { + res.status(200).send(''); + }); + devServer.app.post('/createRoom', async (req, res) => { + try { + let participants = []; + console.log('req.body:', req.body); + if (req.body.presenterUserIds && Array.isArray(req.body.presenterUserIds)) { + req.body.presenterUserIds.forEach(presenterUserId => { + participants.push({ + id: { communicationUserId: presenterUserId }, + role: "Presenter" + }); }); - } catch (e) { - console.error(e); - res.sendStatus(400); - } - }); - devServer.app.get('/authConfig', async (req, res) => { - try { - res.setHeader('Content-Type', 'application/json'); - res.status(200).json(authConfig); - } catch (e) { - console.error(e); - res.sendStatus(400); } - }); - devServer.app.get('/blank', async (req, res) => { - res.status(200).send(''); - }); - devServer.app.post('/createRoom', async (req, res) => { - try { - let participants = []; - console.log('req.body:', req.body); - if (req.body.presenterUserIds && Array.isArray(req.body.presenterUserIds)) { - req.body.presenterUserIds.forEach(presenterUserId => { - participants.push({ - id: { communicationUserId: presenterUserId }, - role: "Presenter" - }); - }); - } - if (req.body.collaboratorUserIds && Array.isArray(req.body.collaboratorUserIds)) { - req.body.collaboratorUserIds.forEach(collaboratorUserId => { - participants.push({ - id: { communicationUserId: collaboratorUserId }, - role: "Collaborator" - }); + if (req.body.collaboratorUserIds && Array.isArray(req.body.collaboratorUserIds)) { + req.body.collaboratorUserIds.forEach(collaboratorUserId => { + participants.push({ + id: { communicationUserId: collaboratorUserId }, + role: "Collaborator" }); - } - if (req.body.attendeeUserIds && Array.isArray(req.body.attendeeUserIds)) { - req.body.attendeeUserIds.forEach(attendeeUserId => { - participants.push({ - id: { communicationUserId: attendeeUserId }, - role: "Attendee" - }); - }); - } - if (req.body.consumerUserIds && Array.isArray(req.body.consumerUserIds)) { - req.body.consumerUserIds.forEach(consumerUserId => { - participants.push({ - id: { communicationUserId: consumerUserId }, - role: "Consumer" - }); + }); + } + if (req.body.attendeeUserIds && Array.isArray(req.body.attendeeUserIds)) { + req.body.attendeeUserIds.forEach(attendeeUserId => { + participants.push({ + id: { communicationUserId: attendeeUserId }, + role: "Attendee" }); - } - - if (participants.length === 0) { - res.status(400).json({ - message: "At least one participant must be provided to create a room." + }); + } + if (req.body.consumerUserIds && Array.isArray(req.body.consumerUserIds)) { + req.body.consumerUserIds.forEach(consumerUserId => { + participants.push({ + id: { communicationUserId: consumerUserId }, + role: "Consumer" }); - return; - } - - console.log('participants:', participants); - const validFrom = new Date(Date.now()); - const validUntil = new Date(validFrom.getTime() + 60 * 60 * 1000); - const pstnDialOutEnabled = req.body.pstnDialOutEnabled; - const roomsClient = new RoomsClient(config.connectionString); - const createRoom = await roomsClient.createRoom({ - validFrom, - validUntil, - pstnDialOutEnabled, - participants }); - const roomId = createRoom.id; - console.log('\nRoom successfully created'); - console.log('Room ID:', roomId); - console.log('Participants:', participants); + } - res.setHeader('Content-Type', 'application/json'); - res.status(200).json({ - roomId + if (participants.length === 0) { + res.status(400).json({ + message: "At least one participant must be provided to create a room." }); - } catch (e) { - console.error(e); - throw e; - } - }); - devServer.app.patch('/updateParticipant', async (req, res) => { - try { - const roomId = req.body.patchRoomId; - const participantId = req.body.patchParticipantId; - const participantRole = req.body.patchParticipantRole; - const roomsClient = new RoomsClient(config.connectionString); - const participant = [ - { - id: { communicationUserId: participantId}, - role: participantRole, - }, - ]; - await roomsClient.addOrUpdateParticipants(roomId, participant); - res.setHeader('Content-Type', 'application/json'); - res.status(200).json({message: 'Participant updated successfully'}); - } catch (e) { - console.error(e); - throw e; + return; } - }); - return middlewares; - } + console.log('participants:', participants); + const validFrom = new Date(Date.now()); + const validUntil = new Date(validFrom.getTime() + 60 * 60 * 1000); + const pstnDialOutEnabled = req.body.pstnDialOutEnabled; + const roomsClient = new RoomsClient(config.connectionString); + const createRoom = await roomsClient.createRoom({ + validFrom, + validUntil, + pstnDialOutEnabled, + participants + }); + const roomId = createRoom.id; + console.log('\nRoom successfully created'); + console.log('Room ID:', roomId); + console.log('Participants:', participants); + + res.setHeader('Content-Type', 'application/json'); + res.status(200).json({ + roomId + }); + } catch (e) { + console.error(e); + throw e; + } + }); + devServer.app.patch('/updateParticipant', async (req, res) => { + try { + const roomId = req.body.patchRoomId; + const participantId = req.body.patchParticipantId; + const participantRole = req.body.patchParticipantRole; + const roomsClient = new RoomsClient(config.connectionString); + const participant = [ + { + id: { communicationUserId: participantId}, + role: participantRole, + }, + ]; + await roomsClient.addOrUpdateParticipants(roomId, participant); + res.setHeader('Content-Type', 'application/json'); + res.status(200).json({message: 'Participant updated successfully'}); + } catch (e) { + console.error(e); + throw e; + } + }); + + return middlewares; + } +}; + +// Export configuration based on environment variable +// npm run build:esm -> ESM bundle +// npm run build:umd -> UMD bundle +// npm run build -> Both bundles +// npm start -> Dev server (both ESM and UMD, switch via ?bundle=esm) +module.exports = (env, argv) => { + if (env && env.esm) { + return esmConfig; } + if (env && env.umd) { + return umdConfig; + } + if (env && env.all) { + return [esmConfig, umdConfig]; + } + // Default: dev server with both ESM and UMD bundles + // Override publicPath to absolute (required by webpack-dev-server) + const esmDevConfig = { + ...esmConfig, + output: { + ...esmConfig.output, + publicPath: '/esm/' + } + }; + const umdDevConfig = { + ...umdConfig, + output: { + ...umdConfig.output, + publicPath: '/umd/' + }, + devServer: devServerSettings + }; + return [esmDevConfig, umdDevConfig]; };