├── .cloud9 └── runners │ ├── Single-Page Application Builder.run │ └── Single-Page Application Test Runner.run ├── .codecatalyst └── workflows │ ├── onPullRequestBuildAndTest.yaml │ └── onPushToMainDeployPipeline.yaml ├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yml ├── .env ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation.yml │ ├── feature_request.yml │ └── questions_help.yml ├── labels.yml ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── build.yml │ ├── codeql-analysis.yml │ ├── pull-request-lint.yml │ ├── upgrade-asterisk-site.yml │ ├── upgrade-site.yml │ └── upgrade.yml ├── .gitignore ├── .idea ├── externalDependencies.xml └── runConfigurations │ ├── Debug_in_browser.xml │ └── npm_start.xml ├── .mergify.yml ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cdk.json ├── images ├── ClickToCallOverview.png ├── Cognito.png └── client.png ├── package.json ├── site ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── README.md ├── package.json ├── public │ ├── index.html │ └── robots.txt ├── src │ ├── App.tsx │ ├── Config.tsx │ ├── index.css │ └── index.tsx ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── src ├── amazon-chime-sdk-click-to-call.ts ├── chime-sdk-voice.ts ├── cognito.ts ├── distribution.ts ├── index.ts ├── infrastructure.ts ├── resources │ ├── asteriskConfig │ │ ├── asterisk.conf │ │ ├── config_asterisk.sh │ │ ├── extensions.conf │ │ ├── install.sh │ │ ├── logger.conf │ │ ├── modules.conf │ │ └── pjsip.conf │ ├── callControl │ │ └── callControl.ts │ ├── cognitoDomain │ │ └── domainValidator.ts │ ├── server │ │ ├── assets │ │ │ ├── audio │ │ │ │ ├── AGENT_AutoRepairs47.wav │ │ │ │ └── AGENT_Retail40.wav │ │ │ └── site │ │ │ │ ├── .babelrc │ │ │ │ ├── .gitignore │ │ │ │ ├── .prettierrc.js │ │ │ │ ├── package.json │ │ │ │ ├── public │ │ │ │ ├── index.html │ │ │ │ └── robots.txt │ │ │ │ ├── src │ │ │ │ ├── Amplify.js │ │ │ │ ├── App.js │ │ │ │ ├── CallControl.js │ │ │ │ ├── MediaContainer.js │ │ │ │ ├── PhoneNumberInput.js │ │ │ │ ├── SipProvider.js │ │ │ │ ├── SipUserAgent.js │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ │ ├── webpack.config.js │ │ │ │ └── yarn.lock │ │ ├── config │ │ │ ├── amazon-cloudwatch-agent.json │ │ │ ├── asterisk.conf │ │ │ ├── config_asterisk.sh │ │ │ ├── extensions.conf │ │ │ ├── http.conf │ │ │ ├── logger.conf │ │ │ ├── modules.conf │ │ │ ├── pjsip.conf │ │ │ └── rtp.conf │ │ └── nginx │ │ │ └── default │ ├── smaHandler │ │ ├── sip-media-application.ts │ │ └── smaHandler.ts │ └── updateCall │ │ └── updateCall.ts ├── server.ts ├── site.ts └── vpc.ts ├── test-reports └── junit.xml ├── test └── __snapshots__ │ └── main.test.ts.snap ├── tsconfig.dev.json ├── tsconfig.json └── yarn.lock /.cloud9/runners/Single-Page Application Builder.run: -------------------------------------------------------------------------------- 1 | { 2 | "script": [ 3 | "for directory in /projects/* ; do", 4 | " if [ -d \"$directory/.cloud9/runners\" ]; then", 5 | " REPOSITORY_DIR=$directory", 6 | " fi", 7 | "done", 8 | "cd $REPOSITORY_DIR", 9 | "npm install", 10 | "npm run build" 11 | ], 12 | "info": "This runner will install dependencies via npm, and build the application for production." 13 | } -------------------------------------------------------------------------------- /.cloud9/runners/Single-Page Application Test Runner.run: -------------------------------------------------------------------------------- 1 | { 2 | "script": [ 3 | "for directory in /projects/* ; do", 4 | " if [ -d \"$directory/.cloud9/runners\" ]; then", 5 | " REPOSITORY_DIR=$directory", 6 | " fi", 7 | "done", 8 | "cd $REPOSITORY_DIR", 9 | "npm test" 10 | ], 11 | "info": "This runner will install dependencies via npm, and execute tests." 12 | } -------------------------------------------------------------------------------- /.codecatalyst/workflows/onPullRequestBuildAndTest.yaml: -------------------------------------------------------------------------------- 1 | Name: onPullRequestBuildAndTest 2 | SchemaVersion: '1.0' 3 | Triggers: 4 | - Type: PULLREQUEST 5 | Branches: 6 | - main 7 | - develop 8 | Events: 9 | - OPEN 10 | - REVISION 11 | Actions: 12 | Build: 13 | Identifier: aws/build@v1 14 | Inputs: 15 | Sources: 16 | - WorkflowSource 17 | Outputs: 18 | Artifacts: 19 | - Name: build 20 | Files: 21 | - build/**/* 22 | Configuration: 23 | Steps: 24 | - Run: npm install -g yarn 25 | - Run: yarn 26 | - Run: yarn projen 27 | - Run: yarn run build 28 | -------------------------------------------------------------------------------- /.codecatalyst/workflows/onPushToMainDeployPipeline.yaml: -------------------------------------------------------------------------------- 1 | Name: onPushToMainDeployPipeline 2 | SchemaVersion: '1.0' 3 | Triggers: 4 | - Type: PUSH 5 | Branches: 6 | - main 7 | Actions: 8 | BuildAndTest: 9 | Identifier: aws/build@v1 10 | Compute: 11 | Type: EC2 12 | Fleet: Linux.x86-64.Large 13 | Inputs: 14 | Sources: 15 | - WorkflowSource 16 | Outputs: 17 | Artifacts: 18 | - Name: build 19 | Files: 20 | - "**/*" 21 | AutoDiscoverReports: 22 | IncludePaths: 23 | - coverage/** 24 | - reports/** 25 | Enabled: true 26 | ReportNamePrefix: rpt 27 | Configuration: 28 | Steps: 29 | - Run: npm install -g yarn 30 | - Run: yarn 31 | - Run: yarn projen 32 | - Run: yarn run build 33 | Environment: 34 | Connections: 35 | - Role: CodeCatalystPreviewDevelopmentAdministrator-qfc7vn 36 | Name: "104621577074" 37 | Name: development 38 | CDKDeploy: 39 | Identifier: aws/cdk-deploy@v1 40 | Configuration: 41 | Context: '{"stack_name":"AmazonChimeSDKClickToCall"}' 42 | Region: us-west-2 43 | StackName: AmazonChimeSDKClickToCall 44 | Compute: 45 | Type: Lambda 46 | Environment: 47 | Connections: 48 | - Role: CodeCatalystPreviewDevelopmentAdministrator-qfc7vn 49 | Name: "104621577074" 50 | Name: development 51 | Inputs: 52 | Artifacts: 53 | - build 54 | Sources: [] 55 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Note: You can use any Debian/Ubuntu based image you want. 2 | FROM mcr.microsoft.com/devcontainers/base:bullseye 3 | 4 | # [Optional] Uncomment this section to install additional OS packages. 5 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 6 | # && apt-get -y install --no-install-recommends -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker-compose 3 | { 4 | "name": "Docker from Docker Compose", 5 | "dockerComposeFile": "docker-compose.yml", 6 | "service": "app", 7 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", 8 | // Use this environment variable if you need to bind mount your local source code into a new container. 9 | "remoteEnv": { 10 | "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" 11 | }, 12 | "hostRequirements": { 13 | "cpus": 8, 14 | "memory": "8gb", 15 | "storage": "32gb" 16 | }, 17 | "features": { 18 | "ghcr.io/devcontainers/features/common-utils:1": { 19 | "installZsh": "true", 20 | "upgradePackages": "true", 21 | "uid": "1000", 22 | "gid": "1000", 23 | "installOhMyZsh": "true", 24 | "nonFreePackages": "true" 25 | }, 26 | "ghcr.io/devcontainers/features/docker-from-docker:1": { 27 | "version": "latest", 28 | "enableNonRootDocker": "true", 29 | "moby": "true" 30 | }, 31 | "ghcr.io/devcontainers/features/aws-cli:1": {}, 32 | "ghcr.io/devcontainers/features/node:1": { 33 | "version": "16" 34 | }, 35 | "ghcr.io/devcontainers-contrib/features/jest:1": {}, 36 | "ghcr.io/eitsupi/devcontainer-features/jq-likes:1": {} 37 | }, 38 | // Configure tool-specific properties. 39 | "customizations": { 40 | // Configure properties specific to VS Code. 41 | "vscode": { 42 | // Add the IDs of extensions you want installed when the container is created. 43 | "extensions": [ 44 | "ms-azuretools.vscode-docker" 45 | ] 46 | } 47 | }, 48 | "mounts": [ 49 | "source=${localEnv:HOME}${localEnv:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind" 50 | ] 51 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 52 | // "forwardPorts": [], 53 | // Use 'postCreateCommand' to run commands after the container is created. 54 | // "postCreateCommand": "docker --version", 55 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 56 | // "remoteUser": "root" 57 | } -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | 9 | volumes: 10 | # Forwards the local Docker socket to the container. 11 | - /var/run/docker.sock:/var/run/docker-host.sock 12 | # Update this to wherever you want VS Code to mount the folder of your project 13 | - ../..:/workspaces:cached 14 | 15 | # Overrides default command so things don't shut down after the process ends. 16 | entrypoint: /usr/local/share/docker-init.sh 17 | command: sleep infinity 18 | 19 | # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust. 20 | # cap_add: 21 | # - SYS_PTRACE 22 | # security_opt: 23 | # - seccomp:unconfined 24 | 25 | # Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 26 | # user: root 27 | 28 | # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 29 | # (Adding the "ports" property to this file will not forward from a Codespace.) 30 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BUILD_ASTERISK='true' 2 | LOG_LEVEL='INFO' 3 | SSH_PUB_KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAqJ++WfXyL/uqAb/eZ1fT9/MzHCKd/n4gUgddjlcbCV' -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module", 16 | "project": "./tsconfig.dev.json" 17 | }, 18 | "extends": [ 19 | "plugin:import/typescript" 20 | ], 21 | "settings": { 22 | "import/parsers": { 23 | "@typescript-eslint/parser": [ 24 | ".ts", 25 | ".tsx" 26 | ] 27 | }, 28 | "import/resolver": { 29 | "node": {}, 30 | "typescript": { 31 | "project": "./tsconfig.dev.json", 32 | "alwaysTryTypes": true 33 | } 34 | } 35 | }, 36 | "ignorePatterns": [ 37 | "*.js", 38 | "*.d.ts", 39 | "node_modules/", 40 | "*.generated.ts", 41 | "coverage", 42 | "!.projenrc.ts", 43 | "!projenrc/**/*.ts" 44 | ], 45 | "rules": { 46 | "indent": [ 47 | "off" 48 | ], 49 | "@typescript-eslint/indent": [ 50 | "error", 51 | 2 52 | ], 53 | "quotes": [ 54 | "error", 55 | "single", 56 | { 57 | "avoidEscape": true 58 | } 59 | ], 60 | "comma-dangle": [ 61 | "error", 62 | "always-multiline" 63 | ], 64 | "comma-spacing": [ 65 | "error", 66 | { 67 | "before": false, 68 | "after": true 69 | } 70 | ], 71 | "no-multi-spaces": [ 72 | "error", 73 | { 74 | "ignoreEOLComments": false 75 | } 76 | ], 77 | "array-bracket-spacing": [ 78 | "error", 79 | "never" 80 | ], 81 | "array-bracket-newline": [ 82 | "error", 83 | "consistent" 84 | ], 85 | "object-curly-spacing": [ 86 | "error", 87 | "always" 88 | ], 89 | "object-curly-newline": [ 90 | "error", 91 | { 92 | "multiline": true, 93 | "consistent": true 94 | } 95 | ], 96 | "object-property-newline": [ 97 | "error", 98 | { 99 | "allowAllPropertiesOnSameLine": true 100 | } 101 | ], 102 | "keyword-spacing": [ 103 | "error" 104 | ], 105 | "brace-style": [ 106 | "error", 107 | "1tbs", 108 | { 109 | "allowSingleLine": true 110 | } 111 | ], 112 | "space-before-blocks": [ 113 | "error" 114 | ], 115 | "curly": [ 116 | "error", 117 | "multi-line", 118 | "consistent" 119 | ], 120 | "@typescript-eslint/member-delimiter-style": [ 121 | "error" 122 | ], 123 | "semi": [ 124 | "error", 125 | "always" 126 | ], 127 | "max-len": [ 128 | "error", 129 | { 130 | "code": 150, 131 | "ignoreUrls": true, 132 | "ignoreStrings": true, 133 | "ignoreTemplateLiterals": true, 134 | "ignoreComments": true, 135 | "ignoreRegExpLiterals": true 136 | } 137 | ], 138 | "quote-props": [ 139 | "error", 140 | "consistent-as-needed" 141 | ], 142 | "@typescript-eslint/no-require-imports": [ 143 | "error" 144 | ], 145 | "import/no-extraneous-dependencies": [ 146 | "error", 147 | { 148 | "devDependencies": [ 149 | "**/test/**", 150 | "**/build-tools/**", 151 | ".projenrc.ts", 152 | "projenrc/**/*.ts" 153 | ], 154 | "optionalDependencies": false, 155 | "peerDependencies": true 156 | } 157 | ], 158 | "import/no-unresolved": [ 159 | "error" 160 | ], 161 | "import/order": [ 162 | "warn", 163 | { 164 | "groups": [ 165 | "builtin", 166 | "external" 167 | ], 168 | "alphabetize": { 169 | "order": "asc", 170 | "caseInsensitive": true 171 | } 172 | } 173 | ], 174 | "import/no-duplicates": [ 175 | "error" 176 | ], 177 | "no-shadow": [ 178 | "off" 179 | ], 180 | "@typescript-eslint/no-shadow": [ 181 | "error" 182 | ], 183 | "key-spacing": [ 184 | "error" 185 | ], 186 | "no-multiple-empty-lines": [ 187 | "error" 188 | ], 189 | "@typescript-eslint/no-floating-promises": [ 190 | "error" 191 | ], 192 | "no-return-await": [ 193 | "off" 194 | ], 195 | "@typescript-eslint/return-await": [ 196 | "error" 197 | ], 198 | "no-trailing-spaces": [ 199 | "error" 200 | ], 201 | "dot-notation": [ 202 | "error" 203 | ], 204 | "no-bitwise": [ 205 | "error" 206 | ], 207 | "@typescript-eslint/member-ordering": [ 208 | "error", 209 | { 210 | "default": [ 211 | "public-static-field", 212 | "public-static-method", 213 | "protected-static-field", 214 | "protected-static-method", 215 | "private-static-field", 216 | "private-static-method", 217 | "field", 218 | "constructor", 219 | "method" 220 | ] 221 | } 222 | ] 223 | }, 224 | "overrides": [ 225 | { 226 | "files": [ 227 | ".projenrc.ts" 228 | ], 229 | "rules": { 230 | "@typescript-eslint/no-require-imports": "off", 231 | "import/no-extraneous-dependencies": "off" 232 | } 233 | } 234 | ] 235 | } 236 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | /.eslintrc.json linguist-generated 4 | /.gitattributes linguist-generated 5 | /.github/pull_request_template.md linguist-generated 6 | /.github/workflows/auto-approve.yml linguist-generated 7 | /.github/workflows/build.yml linguist-generated 8 | /.github/workflows/pull-request-lint.yml linguist-generated 9 | /.github/workflows/upgrade-asterisk-site.yml linguist-generated 10 | /.github/workflows/upgrade-site.yml linguist-generated 11 | /.github/workflows/upgrade.yml linguist-generated 12 | /.gitignore linguist-generated 13 | /.mergify.yml linguist-generated 14 | /.npmignore linguist-generated 15 | /.projen/** linguist-generated 16 | /.projen/deps.json linguist-generated 17 | /.projen/files.json linguist-generated 18 | /.projen/tasks.json linguist-generated 19 | /cdk.json linguist-generated 20 | /LICENSE linguist-generated 21 | /package.json linguist-generated 22 | /tsconfig.dev.json linguist-generated 23 | /tsconfig.json linguist-generated 24 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41B Bug Report" 2 | description: "Something isn't working as expected 🤔" 3 | title: '(short issue description)' 4 | labels: [bug, needs-triage] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the bug 10 | description: A clear and concise description of what the bug is. 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | id: expected 16 | attributes: 17 | label: Expected behavior 18 | description: | 19 | Tell us what should happen. 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: current 25 | attributes: 26 | label: Current behavior 27 | description: | 28 | Tell us what happens instead of the expected behavior. 29 | Include full errors, uncaught exceptions, stack traces, and relevant logs. 30 | To turn on SDK logging, follow instructions here: <<-LINK->> 31 | If service responses are relevant, please include wirelogs (after removing any sensitive information!). 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: reproduction 37 | attributes: 38 | label: Steps to Reproduce 39 | description: | 40 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. 41 | For more complex issues provide a repo with the smallest sample that reproduces the bug. 42 | Avoid including business logic or unrelated code, it makes diagnosis more difficult. 43 | validations: 44 | required: true 45 | 46 | - type: textarea 47 | id: solution 48 | attributes: 49 | label: Possible Solution 50 | description: | 51 | Suggest a fix/reason for the bug 52 | validations: 53 | required: false 54 | 55 | - type: textarea 56 | id: context 57 | attributes: 58 | label: Context 59 | description: | 60 | How has this issue affected you? What are you trying to accomplish? 61 | Providing context helps us come up with a solution that is most useful in the real world. 62 | validations: 63 | required: false 64 | 65 | - type: input 66 | id: sdk-version 67 | attributes: 68 | label: AWS SDK version used 69 | validations: 70 | required: true 71 | 72 | - type: input 73 | id: operating-system 74 | attributes: 75 | label: Operating System and version 76 | validations: 77 | required: true 78 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F4D5 Documentation Issue" 2 | description: Report an issue in the API Reference documentation or Developer Guide 3 | title: "(short issue description)" 4 | labels: [documentation, needs-triage] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the issue 10 | description: A clear and concise description of the issue. 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | id: links 16 | attributes: 17 | label: Links 18 | description: | 19 | Include links to affected documentation page(s). 20 | validations: 21 | required: true 22 | 23 | # Description of impact 24 | Is a lack of a sample or a documentation improvement slowing you down, blocking your work, or misleading you? 25 | Let us know how important this is to you so we can include that as a factor in our prioritization. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 Feature Request" 2 | description: Suggest an idea for this project 3 | title: '(short issue description)' 4 | labels: [feature-request, needs-triage] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## Community Note 10 | 1. Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request 11 | 2. Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request 12 | 3. If you are interested in working on this issue, please leave a comment 13 | 14 | - type: textarea 15 | id: description 16 | attributes: 17 | label: Describe the feature 18 | description: A clear and concise description of the feature you are proposing. 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: problem 24 | attributes: 25 | label: Is your Feature Request related to a problem? 26 | description: | 27 | A description of the issue, e.g. "It would be great if..." 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: solution 33 | attributes: 34 | label: Proposed Solution 35 | description: | 36 | Suggest how to implement the addition or change. 37 | validations: 38 | required: false 39 | 40 | - type: textarea 41 | id: alternatives 42 | attributes: 43 | label: Describe alternative solutions or features you've considered 44 | description: | 45 | Any alternative solutions or features you've considered. 46 | validations: 47 | required: false 48 | 49 | - type: checkboxes 50 | id: acknowledgment 51 | attributes: 52 | label: Acknowledge 53 | options: 54 | - label: I may be able to implement this feature request 55 | required: false 56 | 57 | - type: input 58 | id: sdk-version 59 | attributes: 60 | label: AWS SDK version used 61 | validations: 62 | required: true 63 | 64 | - type: input 65 | id: operating-system 66 | attributes: 67 | label: Operating System and version 68 | validations: 69 | required: true 70 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/questions_help.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F4AC Questions/Help" 2 | description: Create a new issue. If you don't know which type to choose, choose this one. 3 | title: '(short issue description)' 4 | labels: [guidance, needs-triage] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the issue 10 | description: A clear and concise description of the issue. 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | id: reproduction 16 | attributes: 17 | label: Steps to Reproduce 18 | description: | 19 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. 20 | For more complex issues provide a repo with the smallest reproducible example. 21 | Avoid including business logic or unrelated code, it makes diagnosis more difficult. 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: current 27 | attributes: 28 | label: Current behavior 29 | description: | 30 | Tell us what happens instead of the expected behavior. 31 | Include full errors, uncaught exceptions, stack traces, and relevant logs. 32 | To turn on SDK logging, follow instructions here: <<-LINK->> 33 | If service responses are relevant, please include wirelogs (after removing any sensitive information!). 34 | validations: 35 | required: true 36 | 37 | - type: input 38 | id: sdk-version 39 | attributes: 40 | label: AWS SDK version used 41 | validations: 42 | required: true 43 | 44 | - type: input 45 | id: operating-system 46 | attributes: 47 | label: Operating System and version 48 | validations: 49 | required: true 50 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: bug 2 | color: d73a4a 3 | description: "This issue is a bug." 4 | - name: duplicate 5 | color: cfd8d7 6 | description: "This issue is a duplicate." 7 | - name: needs-triage 8 | color: c7cafc 9 | description: "This issue or PR still needs to be triaged." 10 | - name: investigating 11 | color: 0000ff 12 | description: "This issue is being investigated and/or work is in progress to resolve the issue." 13 | - name: response-requested 14 | color: "666666" 15 | description: "Waiting on additional info and feedback. Will move to 'closing-soon' in 5 days." 16 | - name: closing-soon 17 | color: "000000" 18 | description: "This issue will automatically close in 2 days unless further comments are made." 19 | - name: feature-request 20 | color: 008672 21 | description: "A feature should be added or improved." 22 | - name: guidance 23 | color: 8a19e7 24 | description: "Question that needs advice or information." 25 | - name: documentation 26 | color: f7bc35 27 | description: "This is a problem with documentation." 28 | - name: third-party 29 | color: 40C8B6 30 | description: "This issue is related to third-party libraries or applications." 31 | - name: dependencies 32 | color: 40C8B6 33 | description: "This issue is a problem in a dependency." 34 | - name: blocked 35 | color: 86499B 36 | description: "Work is blocked on this issue for this codebase. Other labels or comments may indicate why." 37 | - name: pending-release 38 | color: 86499B 39 | description: "This issue will be fixed by an approved PR that hasn't been released yet." 40 | - name: service-api 41 | color: E06A18 42 | description: "This issue is due to a problem in a service API, not the SDK implementation." 43 | - name: breaking-change 44 | color: d85658 45 | description: "This issue requires a breaking change to remediate." 46 | - name: needs-reproduction 47 | color: ffe79b 48 | description: "This issue needs reproduction." 49 | - name: needs-discussion 50 | color: ffe79b 51 | description: "This issue/PR requires more discussion with community." 52 | - name: SECURITY 53 | color: d85658 54 | description: "" 55 | - name: help wanted 56 | color: ffe79b 57 | description: "We are asking the community to submit a PR to resolve this issue." 58 | - name: wontfix 59 | color: d85658 60 | description: "We have determined that we will not resolve the issue" 61 | - name: pr/do-not-merge 62 | color: 358F18 63 | description: "This PR should not be merged at this time." 64 | - name: pr/breaking-change 65 | color: 358F18 66 | description: "This PR is a breaking change. It needs to be modified to be allowed in the current major version." 67 | - name: pr/blocked 68 | color: 358F18 69 | description: "This PR cannot be merged or reviewed, because it is blocked for some reason." 70 | - name: pr/work-in-progress 71 | color: 358F18 72 | description: "This PR is a draft and needs further work." 73 | - name: needs-review 74 | color: f996e2 75 | description: "" 76 | - name: pr/ready-to-merge 77 | color: 358F18 78 | description: "This PR is ready to be merged." 79 | - name: pr/needs-tests 80 | color: 358F18 81 | description: "This PR is missing tests." 82 | - name: contribution/core 83 | color: DA5F98 84 | description: "This is a PR that came from AWS." 85 | - name: no-auto-closure 86 | color: 009698 87 | description: "We want this issue to not be automatically closed." 88 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'schuettc') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | outputs: 13 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 14 | env: 15 | CI: "true" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 18.x 26 | - name: Install dependencies 27 | run: yarn install --check-files 28 | - name: build 29 | run: npx projen build 30 | - name: Find mutations 31 | id: self_mutation 32 | run: |- 33 | git add . 34 | git diff --staged --patch --exit-code > .repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.self_mutation.outputs.self_mutation_happened 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: .repo.patch 41 | path: .repo.patch 42 | overwrite: true 43 | - name: Fail build on mutation 44 | if: steps.self_mutation.outputs.self_mutation_happened 45 | run: |- 46 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 47 | cat .repo.patch 48 | exit 1 49 | self-mutation: 50 | needs: build 51 | runs-on: ubuntu-latest 52 | permissions: 53 | contents: write 54 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v4 58 | with: 59 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 60 | ref: ${{ github.event.pull_request.head.ref }} 61 | repository: ${{ github.event.pull_request.head.repo.full_name }} 62 | - name: Download patch 63 | uses: actions/download-artifact@v4 64 | with: 65 | name: .repo.patch 66 | path: ${{ runner.temp }} 67 | - name: Apply patch 68 | run: '[ -s ${{ runner.temp }}/.repo.patch ] && git apply ${{ runner.temp }}/.repo.patch || echo "Empty patch. Skipping."' 69 | - name: Set git identity 70 | run: |- 71 | git config user.name "github-actions" 72 | git config user.email "github-actions@github.com" 73 | - name: Push changes 74 | env: 75 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 76 | run: |- 77 | git add . 78 | git commit -s -m "chore: self mutation" 79 | git push origin HEAD:$PULL_REQUEST_REF 80 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '34 16 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | jobs: 14 | validate: 15 | name: Validate PR title 16 | runs-on: ubuntu-latest 17 | permissions: 18 | pull-requests: write 19 | steps: 20 | - uses: amannn/action-semantic-pull-request@v5.4.0 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | types: |- 25 | feat 26 | fix 27 | chore 28 | requireScope: false 29 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-asterisk-site.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-asterisk-site 4 | on: 5 | schedule: 6 | - cron: 0 0 * * 1 7 | workflow_dispatch: {} 8 | jobs: 9 | upgradeSite: 10 | name: upgrade-asterisk-site 11 | runs-on: ubuntu-latest 12 | permissions: 13 | actions: write 14 | contents: read 15 | id-token: write 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: "18" 22 | - run: yarn install --check-files --frozen-lockfile 23 | working-directory: src/resources/server/assets/site 24 | - run: yarn upgrade 25 | working-directory: src/resources/server/assets/site 26 | - name: Create Pull Request 27 | uses: peter-evans/create-pull-request@v4 28 | with: 29 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 30 | commit-message: "chore: upgrade asterisk site" 31 | branch: auto/projen-upgrade 32 | title: "chore: upgrade asterisk site" 33 | body: This PR upgrades asterisk site 34 | labels: auto-merge, auto-approve 35 | author: github-actions 36 | committer: github-actions 37 | signoff: true 38 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-site.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-site 4 | on: 5 | schedule: 6 | - cron: 0 0 * * 1 7 | workflow_dispatch: {} 8 | jobs: 9 | upgradeSite: 10 | name: upgrade-site 11 | runs-on: ubuntu-latest 12 | permissions: 13 | actions: write 14 | contents: read 15 | id-token: write 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: "18" 22 | - run: yarn install --check-files --frozen-lockfile 23 | working-directory: site 24 | - run: yarn upgrade 25 | working-directory: site 26 | - name: Create Pull Request 27 | uses: peter-evans/create-pull-request@v4 28 | with: 29 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 30 | commit-message: "chore: upgrade site" 31 | branch: auto/projen-upgrade 32 | title: "chore: upgrade site" 33 | body: This PR upgrades site 34 | labels: auto-merge, auto-approve 35 | author: github-actions 36 | committer: github-actions 37 | signoff: true 38 | -------------------------------------------------------------------------------- /.github/workflows/upgrade.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 * * 1 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 18.x 23 | - name: Install dependencies 24 | run: yarn install --check-files --frozen-lockfile 25 | - name: Upgrade dependencies 26 | run: npx projen upgrade 27 | - name: Find mutations 28 | id: create_patch 29 | run: |- 30 | git add . 31 | git diff --staged --patch --exit-code > .repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 32 | working-directory: ./ 33 | - name: Upload patch 34 | if: steps.create_patch.outputs.patch_created 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: .repo.patch 38 | path: .repo.patch 39 | overwrite: true 40 | pr: 41 | name: Create Pull Request 42 | needs: upgrade 43 | runs-on: ubuntu-latest 44 | permissions: 45 | contents: read 46 | if: ${{ needs.upgrade.outputs.patch_created }} 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v4 50 | - name: Download patch 51 | uses: actions/download-artifact@v4 52 | with: 53 | name: .repo.patch 54 | path: ${{ runner.temp }} 55 | - name: Apply patch 56 | run: '[ -s ${{ runner.temp }}/.repo.patch ] && git apply ${{ runner.temp }}/.repo.patch || echo "Empty patch. Skipping."' 57 | - name: Set git identity 58 | run: |- 59 | git config user.name "github-actions" 60 | git config user.email "github-actions@github.com" 61 | - name: Create Pull Request 62 | id: create-pr 63 | uses: peter-evans/create-pull-request@v6 64 | with: 65 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 66 | commit-message: |- 67 | chore(deps): upgrade dependencies 68 | 69 | Upgrades project dependencies. See details in [workflow run]. 70 | 71 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 72 | 73 | ------ 74 | 75 | *Automatically created by projen via the "upgrade" workflow* 76 | branch: github-actions/upgrade 77 | title: "chore(deps): upgrade dependencies" 78 | labels: auto-approve,auto-merge 79 | body: |- 80 | Upgrades project dependencies. See details in [workflow run]. 81 | 82 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 83 | 84 | ------ 85 | 86 | *Automatically created by projen via the "upgrade" workflow* 87 | author: github-actions 88 | committer: github-actions 89 | signoff: true 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | !/.github/workflows/build.yml 35 | !/.mergify.yml 36 | !/.github/workflows/upgrade.yml 37 | !/.github/pull_request_template.md 38 | !/test/ 39 | !/tsconfig.json 40 | !/tsconfig.dev.json 41 | !/src/ 42 | /lib 43 | /dist/ 44 | !/.eslintrc.json 45 | /assets/ 46 | !/cdk.json 47 | /cdk.out/ 48 | .cdk.staging/ 49 | .parcel-cache/ 50 | !/.github/workflows/upgrade-site.yml 51 | !/.github/workflows/upgrade-asterisk-site.yml 52 | cdk.out 53 | cdk.context.json 54 | yarn-error.log 55 | dependabot.yml 56 | .DS_Store 57 | !/.projenrc.ts 58 | -------------------------------------------------------------------------------- /.idea/externalDependencies.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Debug_in_browser.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/runConfigurations/npm_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | pull_request_rules: 11 | - name: Automatic merge on approval and successful build 12 | actions: 13 | delete_head_branch: {} 14 | queue: 15 | method: squash 16 | name: default 17 | commit_message_template: |- 18 | {{ title }} (#{{ number }}) 19 | 20 | {{ body }} 21 | conditions: 22 | - "#approved-reviews-by>=1" 23 | - -label~=(do-not-merge) 24 | - status-success=build 25 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | /.projen/ 3 | permissions-backup.acl 4 | /.mergify.yml 5 | /test/ 6 | /tsconfig.dev.json 7 | /src/ 8 | !/lib/ 9 | !/lib/**/*.js 10 | !/lib/**/*.d.ts 11 | dist 12 | /tsconfig.json 13 | /.github/ 14 | /.vscode/ 15 | /.idea/ 16 | /.projenrc.js 17 | tsconfig.tsbuildinfo 18 | /.eslintrc.json 19 | !/assets/ 20 | cdk.out/ 21 | .cdk.staging/ 22 | /.gitattributes 23 | /.projenrc.ts 24 | /projenrc 25 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@types/node", 5 | "version": "^18", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@typescript-eslint/eslint-plugin", 10 | "version": "^7", 11 | "type": "build" 12 | }, 13 | { 14 | "name": "@typescript-eslint/parser", 15 | "version": "^7", 16 | "type": "build" 17 | }, 18 | { 19 | "name": "aws-cdk", 20 | "version": "^2.114.1", 21 | "type": "build" 22 | }, 23 | { 24 | "name": "esbuild", 25 | "type": "build" 26 | }, 27 | { 28 | "name": "eslint-import-resolver-typescript", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "eslint-plugin-import", 33 | "type": "build" 34 | }, 35 | { 36 | "name": "eslint", 37 | "version": "^8", 38 | "type": "build" 39 | }, 40 | { 41 | "name": "projen", 42 | "type": "build" 43 | }, 44 | { 45 | "name": "ts-node", 46 | "type": "build" 47 | }, 48 | { 49 | "name": "typescript", 50 | "type": "build" 51 | }, 52 | { 53 | "name": "@aws-sdk/client-chime-sdk-meetings", 54 | "type": "runtime" 55 | }, 56 | { 57 | "name": "@aws-sdk/client-chime-sdk-voice", 58 | "type": "runtime" 59 | }, 60 | { 61 | "name": "@types/aws-lambda", 62 | "type": "runtime" 63 | }, 64 | { 65 | "name": "@types/fs-extra", 66 | "type": "runtime" 67 | }, 68 | { 69 | "name": "aws-cdk-lib", 70 | "version": "^2.114.1", 71 | "type": "runtime" 72 | }, 73 | { 74 | "name": "aws-lambda", 75 | "type": "runtime" 76 | }, 77 | { 78 | "name": "cdk-amazon-chime-resources", 79 | "type": "runtime" 80 | }, 81 | { 82 | "name": "constructs", 83 | "version": "^10.0.5", 84 | "type": "runtime" 85 | }, 86 | { 87 | "name": "dotenv", 88 | "type": "runtime" 89 | }, 90 | { 91 | "name": "fs-extra", 92 | "type": "runtime" 93 | } 94 | ], 95 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 96 | } 97 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/build.yml", 8 | ".github/workflows/pull-request-lint.yml", 9 | ".github/workflows/upgrade-asterisk-site.yml", 10 | ".github/workflows/upgrade-site.yml", 11 | ".github/workflows/upgrade.yml", 12 | ".gitignore", 13 | ".mergify.yml", 14 | ".npmignore", 15 | ".projen/deps.json", 16 | ".projen/files.json", 17 | ".projen/tasks.json", 18 | "cdk.json", 19 | "LICENSE", 20 | "tsconfig.dev.json", 21 | "tsconfig.json" 22 | ], 23 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 24 | } 25 | -------------------------------------------------------------------------------- /.projen/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "name": "build", 5 | "description": "Full release build", 6 | "steps": [ 7 | { 8 | "spawn": "default" 9 | }, 10 | { 11 | "spawn": "pre-compile" 12 | }, 13 | { 14 | "spawn": "compile" 15 | }, 16 | { 17 | "spawn": "post-compile" 18 | }, 19 | { 20 | "spawn": "test" 21 | }, 22 | { 23 | "spawn": "package" 24 | } 25 | ] 26 | }, 27 | "bundle": { 28 | "name": "bundle", 29 | "description": "Prepare assets" 30 | }, 31 | "clobber": { 32 | "name": "clobber", 33 | "description": "hard resets to HEAD of origin and cleans the local repo", 34 | "env": { 35 | "BRANCH": "$(git branch --show-current)" 36 | }, 37 | "steps": [ 38 | { 39 | "exec": "git checkout -b scratch", 40 | "name": "save current HEAD in \"scratch\" branch" 41 | }, 42 | { 43 | "exec": "git checkout $BRANCH" 44 | }, 45 | { 46 | "exec": "git fetch origin", 47 | "name": "fetch latest changes from origin" 48 | }, 49 | { 50 | "exec": "git reset --hard origin/$BRANCH", 51 | "name": "hard reset to origin commit" 52 | }, 53 | { 54 | "exec": "git clean -fdx", 55 | "name": "clean all untracked files" 56 | }, 57 | { 58 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 59 | } 60 | ], 61 | "condition": "git diff --exit-code > /dev/null" 62 | }, 63 | "compile": { 64 | "name": "compile", 65 | "description": "Only compile" 66 | }, 67 | "configLocal": { 68 | "name": "configLocal", 69 | "steps": [ 70 | { 71 | "exec": "aws s3 cp s3://$(yarn run --silent getBucket)/config.json site/public/" 72 | } 73 | ] 74 | }, 75 | "default": { 76 | "name": "default", 77 | "description": "Synthesize project files", 78 | "steps": [ 79 | { 80 | "exec": "ts-node --project tsconfig.dev.json .projenrc.ts" 81 | } 82 | ] 83 | }, 84 | "deploy": { 85 | "name": "deploy", 86 | "description": "Deploys your CDK app to the AWS cloud", 87 | "steps": [ 88 | { 89 | "exec": "cdk deploy", 90 | "receiveArgs": true 91 | } 92 | ] 93 | }, 94 | "destroy": { 95 | "name": "destroy", 96 | "description": "Destroys your cdk app in the AWS cloud", 97 | "steps": [ 98 | { 99 | "exec": "cdk destroy", 100 | "receiveArgs": true 101 | } 102 | ] 103 | }, 104 | "diff": { 105 | "name": "diff", 106 | "description": "Diffs the currently deployed app against your code", 107 | "steps": [ 108 | { 109 | "exec": "cdk diff" 110 | } 111 | ] 112 | }, 113 | "eject": { 114 | "name": "eject", 115 | "description": "Remove projen from the project", 116 | "env": { 117 | "PROJEN_EJECTING": "true" 118 | }, 119 | "steps": [ 120 | { 121 | "spawn": "default" 122 | } 123 | ] 124 | }, 125 | "eslint": { 126 | "name": "eslint", 127 | "description": "Runs eslint against the codebase", 128 | "steps": [ 129 | { 130 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", 131 | "receiveArgs": true 132 | } 133 | ] 134 | }, 135 | "getBucket": { 136 | "name": "getBucket", 137 | "steps": [ 138 | { 139 | "exec": "aws cloudformation describe-stacks --stack-name AmazonChimeSDKClickToCall --region us-east-1 --query 'Stacks[0].Outputs[?OutputKey==`siteBucket`].OutputValue' --output text" 140 | } 141 | ] 142 | }, 143 | "install": { 144 | "name": "install", 145 | "description": "Install project dependencies and update lockfile (non-frozen)", 146 | "steps": [ 147 | { 148 | "exec": "yarn install --check-files" 149 | } 150 | ] 151 | }, 152 | "install:ci": { 153 | "name": "install:ci", 154 | "description": "Install project dependencies using frozen lockfile", 155 | "steps": [ 156 | { 157 | "exec": "yarn install --check-files --frozen-lockfile" 158 | } 159 | ] 160 | }, 161 | "launch": { 162 | "name": "launch", 163 | "steps": [ 164 | { 165 | "exec": "yarn && yarn projen && yarn build && yarn cdk bootstrap && yarn cdk deploy --require-approval never && yarn configLocal" 166 | } 167 | ] 168 | }, 169 | "package": { 170 | "name": "package", 171 | "description": "Creates the distribution package" 172 | }, 173 | "post-compile": { 174 | "name": "post-compile", 175 | "description": "Runs after successful compilation", 176 | "steps": [ 177 | { 178 | "spawn": "synth:silent" 179 | } 180 | ] 181 | }, 182 | "post-upgrade": { 183 | "name": "post-upgrade", 184 | "description": "Runs after upgrading dependencies" 185 | }, 186 | "pre-compile": { 187 | "name": "pre-compile", 188 | "description": "Prepare the project for compilation" 189 | }, 190 | "synth": { 191 | "name": "synth", 192 | "description": "Synthesizes your cdk app into cdk.out", 193 | "steps": [ 194 | { 195 | "exec": "cdk synth" 196 | } 197 | ] 198 | }, 199 | "synth:silent": { 200 | "name": "synth:silent", 201 | "description": "Synthesizes your cdk app into cdk.out and suppresses the template in stdout (part of \"yarn build\")", 202 | "steps": [ 203 | { 204 | "exec": "cdk synth -q" 205 | } 206 | ] 207 | }, 208 | "test": { 209 | "name": "test", 210 | "description": "Run tests", 211 | "steps": [ 212 | { 213 | "spawn": "eslint" 214 | } 215 | ] 216 | }, 217 | "upgrade": { 218 | "name": "upgrade", 219 | "description": "upgrade dependencies", 220 | "env": { 221 | "CI": "0" 222 | }, 223 | "steps": [ 224 | { 225 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --dep=dev,peer,prod,optional --filter=esbuild,eslint-import-resolver-typescript,eslint-plugin-import,projen,ts-node,typescript,@aws-sdk/client-chime-sdk-meetings,@aws-sdk/client-chime-sdk-voice,@types/aws-lambda,@types/fs-extra,aws-lambda,cdk-amazon-chime-resources,dotenv,fs-extra" 226 | }, 227 | { 228 | "exec": "yarn install --check-files" 229 | }, 230 | { 231 | "exec": "yarn upgrade @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser aws-cdk esbuild eslint-import-resolver-typescript eslint-plugin-import eslint projen ts-node typescript @aws-sdk/client-chime-sdk-meetings @aws-sdk/client-chime-sdk-voice @types/aws-lambda @types/fs-extra aws-cdk-lib aws-lambda cdk-amazon-chime-resources constructs dotenv fs-extra" 232 | }, 233 | { 234 | "exec": "npx projen" 235 | }, 236 | { 237 | "spawn": "post-upgrade" 238 | } 239 | ] 240 | }, 241 | "watch": { 242 | "name": "watch", 243 | "description": "Watches changes in your source code and rebuilds and deploys to the current account", 244 | "steps": [ 245 | { 246 | "exec": "cdk deploy --hotswap" 247 | }, 248 | { 249 | "exec": "cdk watch" 250 | } 251 | ] 252 | } 253 | }, 254 | "env": { 255 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 256 | }, 257 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 258 | } 259 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | const { awscdk } = require('projen'); 2 | const { JobPermission } = require('projen/lib/github/workflows-model'); 3 | const { UpgradeDependenciesSchedule } = require('projen/lib/javascript'); 4 | const AUTOMATION_TOKEN = 'PROJEN_GITHUB_TOKEN'; 5 | 6 | const project = new awscdk.AwsCdkTypeScriptApp({ 7 | cdkVersion: '2.114.1', 8 | license: 'MIT-0', 9 | author: 'Court Schuett', 10 | copyrightOwner: 'Amazon.com, Inc.', 11 | authorAddress: 'https://aws.amazon.com', 12 | defaultReleaseBranch: 'main', 13 | projenrcTs: true, 14 | jest: false, 15 | name: 'amazon-chime-sdk-click-to-call', 16 | workflowNodeVersion: '18.x', 17 | appEntrypoint: 'amazon-chime-sdk-click-to-call.ts', 18 | tsconfig: { 19 | compilerOptions: { 20 | lib: ['es2020', 'dom'], 21 | }, 22 | }, 23 | depsUpgradeOptions: { 24 | ignoreProjen: false, 25 | workflowOptions: { 26 | labels: ['auto-approve', 'auto-merge'], 27 | schedule: UpgradeDependenciesSchedule.WEEKLY, 28 | }, 29 | }, 30 | autoApproveOptions: { 31 | secret: 'GITHUB_TOKEN', 32 | allowedUsernames: ['schuettc'], 33 | }, 34 | autoApproveUpgrades: true, 35 | devDeps: ['esbuild'], 36 | deps: [ 37 | 'dotenv', 38 | 'cdk-amazon-chime-resources', 39 | 'fs-extra', 40 | '@types/fs-extra', 41 | '@aws-sdk/client-chime-sdk-meetings', 42 | '@aws-sdk/client-chime-sdk-voice', 43 | 'aws-lambda', 44 | '@types/aws-lambda', 45 | ], 46 | projenUpgradeSecret: 'PROJEN_GITHUB_TOKEN', 47 | }); 48 | 49 | const upgradeSite = project.github.addWorkflow('upgrade-site'); 50 | upgradeSite.on({ schedule: [{ cron: '0 0 * * 1' }], workflowDispatch: {} }); 51 | 52 | upgradeSite.addJobs({ 53 | upgradeSite: { 54 | runsOn: ['ubuntu-latest'], 55 | name: 'upgrade-site', 56 | permissions: { 57 | actions: JobPermission.WRITE, 58 | contents: JobPermission.READ, 59 | idToken: JobPermission.WRITE, 60 | }, 61 | steps: [ 62 | { uses: 'actions/checkout@v3' }, 63 | { 64 | name: 'Setup Node.js', 65 | uses: 'actions/setup-node@v3', 66 | with: { 67 | 'node-version': '18', 68 | }, 69 | }, 70 | { 71 | run: 'yarn install --check-files --frozen-lockfile', 72 | workingDirectory: 'site', 73 | }, 74 | { run: 'yarn upgrade', workingDirectory: 'site' }, 75 | { 76 | name: 'Create Pull Request', 77 | uses: 'peter-evans/create-pull-request@v4', 78 | with: { 79 | 'token': '${{ secrets.' + AUTOMATION_TOKEN + ' }}', 80 | 'commit-message': 'chore: upgrade site', 81 | 'branch': 'auto/projen-upgrade', 82 | 'title': 'chore: upgrade site', 83 | 'body': 'This PR upgrades site', 84 | 'labels': 'auto-merge, auto-approve', 85 | 'author': 'github-actions ', 86 | 'committer': 'github-actions ', 87 | 'signoff': true, 88 | }, 89 | }, 90 | ], 91 | }, 92 | }); 93 | 94 | const upgradeAsteriskSite = project.github.addWorkflow('upgrade-asterisk-site'); 95 | upgradeAsteriskSite.on({ 96 | schedule: [{ cron: '0 0 * * 1' }], 97 | workflowDispatch: {}, 98 | }); 99 | 100 | upgradeAsteriskSite.addJobs({ 101 | upgradeSite: { 102 | runsOn: ['ubuntu-latest'], 103 | name: 'upgrade-asterisk-site', 104 | permissions: { 105 | actions: JobPermission.WRITE, 106 | contents: JobPermission.READ, 107 | idToken: JobPermission.WRITE, 108 | }, 109 | steps: [ 110 | { uses: 'actions/checkout@v3' }, 111 | { 112 | name: 'Setup Node.js', 113 | uses: 'actions/setup-node@v3', 114 | with: { 115 | 'node-version': '18', 116 | }, 117 | }, 118 | { 119 | run: 'yarn install --check-files --frozen-lockfile', 120 | workingDirectory: 'src/resources/server/assets/site', 121 | }, 122 | { 123 | run: 'yarn upgrade', 124 | workingDirectory: 'src/resources/server/assets/site', 125 | }, 126 | { 127 | name: 'Create Pull Request', 128 | uses: 'peter-evans/create-pull-request@v4', 129 | with: { 130 | 'token': '${{ secrets.' + AUTOMATION_TOKEN + ' }}', 131 | 'commit-message': 'chore: upgrade asterisk site', 132 | 'branch': 'auto/projen-upgrade', 133 | 'title': 'chore: upgrade asterisk site', 134 | 'body': 'This PR upgrades asterisk site', 135 | 'labels': 'auto-merge, auto-approve', 136 | 'author': 'github-actions ', 137 | 'committer': 'github-actions ', 138 | 'signoff': true, 139 | }, 140 | }, 141 | ], 142 | }, 143 | }); 144 | 145 | const common_exclude = [ 146 | 'cdk.out', 147 | 'cdk.context.json', 148 | 'yarn-error.log', 149 | 'dependabot.yml', 150 | '.DS_Store', 151 | ]; 152 | 153 | project.addTask('launch', { 154 | exec: 'yarn && yarn projen && yarn build && yarn cdk bootstrap && yarn cdk deploy --require-approval never && yarn configLocal', 155 | }); 156 | project.addTask('getBucket', { 157 | exec: "aws cloudformation describe-stacks --stack-name AmazonChimeSDKClickToCall --region us-east-1 --query 'Stacks[0].Outputs[?OutputKey==`siteBucket`].OutputValue' --output text", 158 | }); 159 | 160 | project.addTask('configLocal', { 161 | exec: 'aws s3 cp s3://$(yarn run --silent getBucket)/config.json site/public/', 162 | }); 163 | 164 | project.gitignore.exclude(...common_exclude); 165 | project.synth(); 166 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Amazon.com, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node -P tsconfig.json --prefer-ts-exts src/amazon-chime-sdk-click-to-call.ts", 3 | "output": "cdk.out", 4 | "build": "npx projen bundle", 5 | "watch": { 6 | "include": [ 7 | "src/**/*.ts", 8 | "test/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "README.md", 12 | "cdk*.json", 13 | "**/*.d.ts", 14 | "**/*.js", 15 | "tsconfig.json", 16 | "package*.json", 17 | "yarn.lock", 18 | "node_modules" 19 | ] 20 | }, 21 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 22 | } 23 | -------------------------------------------------------------------------------- /images/ClickToCallOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-chime-sdk-click-to-call/c83333d24192f857619ff821bfb2bce6ca5ccb8e/images/ClickToCallOverview.png -------------------------------------------------------------------------------- /images/Cognito.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-chime-sdk-click-to-call/c83333d24192f857619ff821bfb2bce6ca5ccb8e/images/Cognito.png -------------------------------------------------------------------------------- /images/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-chime-sdk-click-to-call/c83333d24192f857619ff821bfb2bce6ca5ccb8e/images/client.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon-chime-sdk-click-to-call", 3 | "scripts": { 4 | "build": "npx projen build", 5 | "bundle": "npx projen bundle", 6 | "clobber": "npx projen clobber", 7 | "compile": "npx projen compile", 8 | "configLocal": "npx projen configLocal", 9 | "default": "npx projen default", 10 | "deploy": "npx projen deploy", 11 | "destroy": "npx projen destroy", 12 | "diff": "npx projen diff", 13 | "eject": "npx projen eject", 14 | "eslint": "npx projen eslint", 15 | "getBucket": "npx projen getBucket", 16 | "launch": "npx projen launch", 17 | "package": "npx projen package", 18 | "post-compile": "npx projen post-compile", 19 | "post-upgrade": "npx projen post-upgrade", 20 | "pre-compile": "npx projen pre-compile", 21 | "synth": "npx projen synth", 22 | "synth:silent": "npx projen synth:silent", 23 | "test": "npx projen test", 24 | "upgrade": "npx projen upgrade", 25 | "watch": "npx projen watch", 26 | "projen": "npx projen" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^18", 30 | "@typescript-eslint/eslint-plugin": "^7", 31 | "@typescript-eslint/parser": "^7", 32 | "aws-cdk": "^2.114.1", 33 | "esbuild": "^0.23.0", 34 | "eslint": "^8", 35 | "eslint-import-resolver-typescript": "^3.6.1", 36 | "eslint-plugin-import": "^2.29.1", 37 | "projen": "^0.84.6", 38 | "ts-node": "10.9.2", 39 | "typescript": "^4.9.5" 40 | }, 41 | "dependencies": { 42 | "@aws-sdk/client-chime-sdk-meetings": "^3.616.0", 43 | "@aws-sdk/client-chime-sdk-voice": "^3.616.0", 44 | "@types/aws-lambda": "^8.10.141", 45 | "@types/fs-extra": "^9.0.13", 46 | "aws-cdk-lib": "^2.114.1", 47 | "aws-lambda": "^1.0.7", 48 | "cdk-amazon-chime-resources": "^3.2.20", 49 | "constructs": "^10.0.5", 50 | "dotenv": "^16.4.5", 51 | "fs-extra": "^10.1.0" 52 | }, 53 | "license": "MIT-0", 54 | "publishConfig": { 55 | "access": "public" 56 | }, 57 | "version": "0.0.0", 58 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 59 | } 60 | -------------------------------------------------------------------------------- /site/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /site/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react 5 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 6 | ], 7 | parserOptions: { 8 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 9 | sourceType: 'module', // Allows for the use of imports 10 | ecmaFeatures: { 11 | jsx: true, // Allows for the parsing of JSX 12 | }, 13 | }, 14 | rules: { 15 | '@typescript-eslint/ban-ts-ignore': 0, 16 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 17 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 18 | '@typescript-eslint/no-empty-function': 0, 19 | // '@typescript-eslint/no-empty-function': [ 20 | // 'error', 21 | // { 22 | // allow: [ 23 | // 'methods', 24 | // 'functions', 25 | // 'arrowFunctions', 26 | // 'generatorFunctions', 27 | // 'asyncMethods', 28 | // 'generatorMethods', 29 | // 'asyncFunctions', 30 | // 'getters', 31 | // 'setters', 32 | // ], 33 | // }, 34 | // ], 35 | }, 36 | settings: { 37 | react: { 38 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 39 | }, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /dist 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | !*.js 27 | cdk-outputs.json 28 | favicon.ico 29 | 30 | config.json 31 | 32 | -------------------------------------------------------------------------------- /site/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # Amazon Chime Click-To-Call Starter Project Client 2 | 3 | ## To Use 4 | 5 | ``` 6 | yarn 7 | yarn run start 8 | ``` 9 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "webpack serve --config ./webpack.config.js --mode development", 8 | "build": "webpack --config webpack.config.js --mode production" 9 | }, 10 | "dependencies": { 11 | "@aws-amplify/ui-react": "^2.8.0", 12 | "@cloudscape-design/components": "^3.0.65", 13 | "@cloudscape-design/global-styles": "^1.0.1", 14 | "@types/react": "^18.2.43", 15 | "@types/react-dom": "^18.2.17", 16 | "amazon-chime-sdk-component-library-react": "^3.7.0", 17 | "amazon-chime-sdk-js": "^3.15.0", 18 | "aws-amplify": "^4.3.15", 19 | "axios": "^1.6.0", 20 | "protobufjs": "6.11.4", 21 | "react": "^17.0.2", 22 | "react-dom": "^17.0.2", 23 | "regenerator-runtime": "^0.13.9", 24 | "styled-components": "^5.3.3", 25 | "styled-system": "^5.1.5" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.17.5", 29 | "@babel/preset-env": "^7.16.11", 30 | "@babel/preset-react": "^7.16.7", 31 | "@types/styled-components": "^5.1.34", 32 | "babel-loader": "^8.2.3", 33 | "copy-webpack-plugin": "^11.0.0", 34 | "css-loader": "^6.6.0", 35 | "file-loader": "^6.2.0", 36 | "html-loader": "^3.1.0", 37 | "html-webpack-plugin": "^5.5.0", 38 | "style-loader": "^3.3.1", 39 | "ts-loader": "^9.5.1", 40 | "webpack": "^5.94.0", 41 | "webpack-cli": "^4.9.2", 42 | "webpack-dev-server": "^4.9.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /site/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Amazon Chime Click to Call 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /site/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /site/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { 3 | useMeetingManager, 4 | Dialer, 5 | Phone, 6 | ControlBar, 7 | ControlBarButton, 8 | MeetingStatus, 9 | AudioInputControl, 10 | useMeetingStatus, 11 | AudioOutputControl, 12 | } from 'amazon-chime-sdk-component-library-react'; 13 | import { MeetingSessionConfiguration } from 'amazon-chime-sdk-js'; 14 | import { AmplifyConfig } from './Config'; 15 | import { Amplify, API, Auth } from 'aws-amplify'; 16 | import { Authenticator } from '@aws-amplify/ui-react'; 17 | import '@aws-amplify/ui-react/styles.css'; 18 | import { 19 | ContentLayout, 20 | FormField, 21 | Input, 22 | Container, 23 | Header, 24 | SpaceBetween, 25 | Button, 26 | Grid, 27 | Box, 28 | Modal, 29 | } from '@cloudscape-design/components'; 30 | 31 | import '@cloudscape-design/global-styles/index.css'; 32 | 33 | // Rest of your code remains the same 34 | 35 | Amplify.configure(AmplifyConfig); 36 | API.configure(AmplifyConfig); 37 | Amplify.Logger.LOG_LEVEL = 'DEBUG'; 38 | 39 | const App: React.FC = () => { 40 | const meetingManager = useMeetingManager(); 41 | const meetingStatus = useMeetingStatus(); 42 | const [phoneNumber, setPhone] = useState(''); 43 | const [meetingId, setMeetingId] = useState(''); 44 | const [transactionId, setTransactionId] = useState(''); 45 | const [visible, setVisible] = useState(false); 46 | const [isActive, setIsActive] = useState(false); 47 | 48 | useEffect(() => { 49 | if (meetingStatus === MeetingStatus.Succeeded) { 50 | setIsActive(true); 51 | } else { 52 | setIsActive(false); 53 | } 54 | }, [meetingStatus]); 55 | 56 | const DialButtonProps = { 57 | icon: , 58 | onClick: () => handleDialOut(), 59 | label: 'Dial', 60 | }; 61 | 62 | const EndButtonProps = { 63 | icon: , 64 | onClick: () => handleEnd(), 65 | label: 'End', 66 | }; 67 | 68 | const DTMFButtonProps = { 69 | icon: , 70 | onClick: () => setVisible(true), 71 | label: 'Dial Pad', 72 | }; 73 | 74 | const VALID_PHONE_NUMBER = /^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})\s*$/; 75 | 76 | async function signOut() { 77 | console.log('Signing Out'); 78 | try { 79 | await Auth.signOut(); 80 | } catch (error) { 81 | console.log('error signing out: ', error); 82 | } 83 | } 84 | 85 | const handleDialOut = async () => { 86 | // event.preventDefault(); 87 | if (VALID_PHONE_NUMBER.test(phoneNumber) || phoneNumber == '') { 88 | try { 89 | const dialOutResponse = await API.post('callControlAPI', 'dial', { 90 | body: { 91 | toNumber: phoneNumber, 92 | }, 93 | }); 94 | console.log(dialOutResponse); 95 | const meetingSessionConfiguration = new MeetingSessionConfiguration( 96 | dialOutResponse.responseInfo.Meeting, 97 | dialOutResponse.responseInfo.Attendee, 98 | ); 99 | await meetingManager.join(meetingSessionConfiguration); 100 | await meetingManager.start(); 101 | console.log('Meeting started'); 102 | console.log(dialOutResponse); 103 | setMeetingId(dialOutResponse.responseInfo.Meeting.MeetingId); 104 | setTransactionId(dialOutResponse.dialInfo.SipMediaApplicationCall.TransactionId); 105 | } catch (err) { 106 | console.log(err); 107 | } 108 | } else { 109 | console.log('Bad Phone Number'); 110 | } 111 | }; 112 | 113 | const handleEnd = async () => { 114 | // event.preventDefault(); 115 | try { 116 | const updateResponse = await API.post('updateCallAPI', 'update', { 117 | body: { 118 | update: 'end', 119 | meetingId: meetingId, 120 | }, 121 | }); 122 | console.info(`updateResponse: ${JSON.stringify(updateResponse)}`); 123 | } catch (err) { 124 | console.log(err); 125 | } 126 | }; 127 | 128 | const handleSendDigit = (digit: string) => { 129 | // event.preventDefault(); 130 | try { 131 | const updateResponse = API.post('updateCallAPI', 'update', { 132 | body: { 133 | update: 'digit', 134 | digit: digit, 135 | transactionId: transactionId, 136 | }, 137 | }); 138 | console.info(`updateResponse: ${JSON.stringify(updateResponse)}`); 139 | } catch (err) { 140 | console.log(err); 141 | } 142 | }; 143 | 144 | return ( 145 | 146 | 149 |
Sign Out}> 150 | Amazon Chime SDK Click to Call 151 |
152 | 153 | } 154 | > 155 | 156 | 160 | { 167 | setPhone(detail.value); 168 | }} 169 | placeholder="Enter Phone Number to Dial" 170 | inputMode="tel" 171 | autoFocus 172 | 173 | // layout="horizontal" 174 | /> 175 | 176 | 182 | {isActive ? ( 183 | 184 | ) : ( 185 | 186 | )} 187 | 188 | 189 | 190 | setVisible(false)} 192 | size={'small'} 193 | visible={visible} 194 | closeAriaLabel="Close modal" 195 | footer={ 196 | 197 | 213 |
214 | 215 |
216 |
217 | 218 |
219 |
220 | 221 |
222 |
223 | 224 |
225 |
226 | 227 |
228 |
229 | 230 |
231 |
232 | 233 |
234 |
235 | 236 |
237 |
238 | 239 |
240 |
241 | 242 |
243 |
244 | 245 |
246 |
247 | 248 |
249 |
250 |
251 | } 252 | header="Send Digits" 253 | >
254 |
255 |
256 |
257 |
258 | ); 259 | }; 260 | 261 | export default App; 262 | -------------------------------------------------------------------------------- /site/src/Config.tsx: -------------------------------------------------------------------------------- 1 | console.log('Getting Config'); 2 | const config = await fetch('./config.json').then((response) => response.json()); 3 | 4 | import { Auth } from 'aws-amplify'; 5 | 6 | export const AmplifyConfig = { 7 | Auth: { 8 | region: config.userPoolRegion, 9 | userPoolId: config.userPoolId, 10 | userPoolWebClientId: config.userPoolClientId, 11 | identityPoolId: config.identityPoolId, 12 | mandatorySignIn: true, 13 | cookieStorage: { 14 | domain: `${window.location.hostname}`, 15 | path: '/', 16 | expires: 365, 17 | secure: true, 18 | }, 19 | }, 20 | Analytics: { 21 | disabled: true, 22 | }, 23 | API: { 24 | endpoints: [ 25 | { 26 | name: 'callControlAPI', 27 | endpoint: config.apiUrl, 28 | custom_header: async () => { 29 | return { Authorization: `${(await Auth.currentSession()).getIdToken().getJwtToken()}` }; 30 | }, 31 | }, 32 | { 33 | name: 'updateCallAPI', 34 | endpoint: config.apiUrl, 35 | custom_header: async () => { 36 | return { Authorization: `${(await Auth.currentSession()).getIdToken().getJwtToken()}` }; 37 | }, 38 | }, 39 | ], 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /site/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 4 | 'Droid Sans', 'Helvetica Neue', sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | code { 10 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 11 | } 12 | -------------------------------------------------------------------------------- /site/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | import { ThemeProvider } from 'styled-components'; 6 | import { MeetingProvider, lightTheme } from 'amazon-chime-sdk-component-library-react'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | 14 | 15 | , 16 | document.getElementById('root'), 17 | ); 18 | -------------------------------------------------------------------------------- /site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": false, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } -------------------------------------------------------------------------------- /site/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: './src/index.tsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'dist'), 10 | clean: true, 11 | }, 12 | resolve: { 13 | extensions: ['.tsx', '.ts', '.js'], 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.tsx?$/, 19 | use: 'ts-loader', 20 | exclude: /node_modules/, 21 | }, 22 | { 23 | test: /\.css$/, 24 | use: ['style-loader', 'css-loader'], 25 | }, 26 | ], 27 | }, 28 | plugins: [ 29 | new HtmlWebpackPlugin({ 30 | template: './public/index.html', 31 | }), 32 | ], 33 | devServer: { 34 | static: { 35 | directory: path.join(__dirname, 'public'), 36 | }, 37 | compress: true, 38 | port: 3000, 39 | open: true, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/amazon-chime-sdk-click-to-call.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unresolved */ 2 | import { App, CfnOutput, Stack, StackProps } from 'aws-cdk-lib'; 3 | import { Construct } from 'constructs'; 4 | import { config } from 'dotenv'; 5 | import { 6 | SMAResources, 7 | Infrastructure, 8 | Cognito, 9 | Site, 10 | VPCResources, 11 | ServerResources, 12 | VoiceConnectorResources, 13 | DistributionResources, 14 | } from './'; 15 | 16 | config(); 17 | 18 | interface AmazonChimeSDKClickToCallProps extends StackProps { 19 | buildAsterisk: string; 20 | logLevel: string; 21 | allowedDomain: string; 22 | sshPubKey: string; 23 | } 24 | 25 | export class AmazonChimeSDKClickToCall extends Stack { 26 | constructor( 27 | scope: Construct, 28 | id: string, 29 | props: AmazonChimeSDKClickToCallProps, 30 | ) { 31 | super(scope, id, props); 32 | 33 | const smaResources = new SMAResources(this, 'SMAResources'); 34 | const cognitoResources = new Cognito(this, 'Cognito', { 35 | allowedDomain: props.allowedDomain, 36 | }); 37 | 38 | let voiceConnectorResources; 39 | 40 | if (props.buildAsterisk == 'true') { 41 | const vpcResources = new VPCResources(this, 'VPC'); 42 | 43 | const distributionResources = new DistributionResources( 44 | this, 45 | 'DistributionResources', 46 | { 47 | applicationLoadBalancer: vpcResources.applicationLoadBalancer, 48 | }, 49 | ); 50 | 51 | voiceConnectorResources = new VoiceConnectorResources( 52 | this, 53 | 'VoiceConnector', 54 | { 55 | asteriskEip: vpcResources.serverEip, 56 | }, 57 | ); 58 | 59 | const serverResources = new ServerResources(this, 'Asterisk', { 60 | serverEip: vpcResources.serverEip, 61 | voiceConnector: voiceConnectorResources.voiceConnector, 62 | phoneNumber: voiceConnectorResources.phoneNumber, 63 | vpc: vpcResources.vpc, 64 | voiceSecurityGroup: vpcResources.voiceSecurityGroup, 65 | albSecurityGroup: vpcResources.albSecurityGroup, 66 | sshSecurityGroup: vpcResources.sshSecurityGroup, 67 | logLevel: props.logLevel, 68 | sshPubKey: props.sshPubKey, 69 | applicationLoadBalancer: vpcResources.applicationLoadBalancer, 70 | distribution: distributionResources.distribution, 71 | userPool: cognitoResources.userPool, 72 | userPoolClient: cognitoResources.userPoolClient, 73 | userPoolRegion: cognitoResources.userPoolRegion, 74 | identityPool: cognitoResources.identityPool, 75 | }); 76 | new CfnOutput(this, 'instanceId', { value: serverResources.instanceId }); 77 | new CfnOutput(this, 'ssmCommand', { 78 | value: `aws ssm start-session --target ${serverResources.instanceId}`, 79 | }); 80 | new CfnOutput(this, 'sshCommand', { 81 | value: `ssh ubuntu@${vpcResources.serverEip.ref}`, 82 | }); 83 | new CfnOutput(this, 'voiceConnectorPhone', { 84 | value: voiceConnectorResources.phoneNumber.phoneNumber, 85 | }); 86 | new CfnOutput(this, 'asteriskSite', { 87 | value: distributionResources.distribution.distributionDomainName, 88 | }); 89 | } 90 | 91 | const infrastructure = new Infrastructure(this, 'Infrastructure', { 92 | fromPhoneNumber: smaResources.fromNumber, 93 | smaId: smaResources.smaId, 94 | userPool: cognitoResources.userPool, 95 | ...(voiceConnectorResources?.phoneNumber && { 96 | voiceConnectorPhone: voiceConnectorResources.phoneNumber, 97 | }), 98 | ...(voiceConnectorResources?.voiceConnector && { 99 | voiceConnector: voiceConnectorResources.voiceConnector, 100 | }), 101 | }); 102 | 103 | const site = new Site(this, 'Site', { 104 | apiUrl: infrastructure.apiUrl, 105 | userPool: cognitoResources.userPool, 106 | userPoolClient: cognitoResources.userPoolClient, 107 | userPoolRegion: cognitoResources.userPoolRegion, 108 | identityPool: cognitoResources.identityPool, 109 | }); 110 | 111 | new CfnOutput(this, 'smaNumber', { value: smaResources.fromNumber }); 112 | new CfnOutput(this, 'siteBucket', { value: site.siteBucket.bucketName }); 113 | new CfnOutput(this, 'clickToCallSite', { 114 | value: site.distribution.distributionDomainName, 115 | }); 116 | } 117 | } 118 | 119 | const devEnv = { 120 | account: process.env.CDK_DEFAULT_ACCOUNT, 121 | region: 'us-east-1', 122 | }; 123 | 124 | const stackProps = { 125 | sshPubKey: process.env.SSH_PUB_KEY || '', 126 | allowedDomain: process.env.ALLOWED_DOMAIN || '', 127 | logLevel: process.env.LOG_LEVEL || 'INFO', 128 | buildAsterisk: process.env.BUILD_ASTERISK || 'false', 129 | }; 130 | 131 | const app = new App(); 132 | 133 | new AmazonChimeSDKClickToCall(app, 'AmazonChimeSDKClickToCall', { 134 | ...stackProps, 135 | env: devEnv, 136 | }); 137 | 138 | app.synth(); 139 | -------------------------------------------------------------------------------- /src/chime-sdk-voice.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from 'aws-cdk-lib'; 2 | import { CfnEIP } from 'aws-cdk-lib/aws-ec2'; 3 | import { 4 | ServicePrincipal, 5 | Role, 6 | ManagedPolicy, 7 | PolicyDocument, 8 | PolicyStatement, 9 | } from 'aws-cdk-lib/aws-iam'; 10 | import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda'; 11 | import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; 12 | import { 13 | ChimeVoiceConnector, 14 | Protocol, 15 | ChimeSipMediaApp, 16 | ChimePhoneNumber, 17 | PhoneProductType, 18 | PhoneNumberType, 19 | PhoneCountry, 20 | } from 'cdk-amazon-chime-resources'; 21 | 22 | import { Construct } from 'constructs'; 23 | 24 | interface VoiceConnectorResourcesProps { 25 | asteriskEip: CfnEIP; 26 | } 27 | export class VoiceConnectorResources extends Construct { 28 | public readonly voiceConnector: ChimeVoiceConnector; 29 | public readonly phoneNumber: ChimePhoneNumber; 30 | 31 | constructor( 32 | scope: Construct, 33 | id: string, 34 | props: VoiceConnectorResourcesProps, 35 | ) { 36 | super(scope, id); 37 | 38 | const phoneNumber = new ChimePhoneNumber( 39 | this, 40 | 'voiceConnectorPhoneNumber', 41 | { 42 | phoneProductType: PhoneProductType.VC, 43 | phoneCountry: PhoneCountry.US, 44 | phoneState: 'IL', 45 | phoneNumberType: PhoneNumberType.LOCAL, 46 | }, 47 | ); 48 | 49 | const pstnVoiceConnector = new ChimeVoiceConnector( 50 | this, 51 | 'pstnVoiceConnector', 52 | { 53 | termination: { 54 | terminationCidrs: [`${props.asteriskEip.ref}/32`], 55 | callingRegions: ['US'], 56 | }, 57 | origination: [ 58 | { 59 | host: props.asteriskEip.ref, 60 | port: 5060, 61 | protocol: Protocol.UDP, 62 | priority: 1, 63 | weight: 1, 64 | }, 65 | ], 66 | encryption: false, 67 | // loggingConfiguration: { 68 | // enableMediaMetricLogs: true, 69 | // enableSIPLogs: true, 70 | // }, 71 | }, 72 | ); 73 | 74 | phoneNumber.associateWithVoiceConnector(pstnVoiceConnector); 75 | this.voiceConnector = pstnVoiceConnector; 76 | this.phoneNumber = phoneNumber; 77 | } 78 | } 79 | 80 | export class SMAResources extends Construct { 81 | public readonly fromNumber: string; 82 | public readonly smaId: string; 83 | 84 | constructor(scope: Construct, id: string) { 85 | super(scope, id); 86 | 87 | const phoneNumber = new ChimePhoneNumber(this, 'phoneNumber', { 88 | phoneState: 'IL', 89 | phoneNumberType: PhoneNumberType.LOCAL, 90 | phoneProductType: PhoneProductType.SMA, 91 | }); 92 | 93 | const smaHandlerRole = new Role(this, 'smaHandlerRole', { 94 | assumedBy: new ServicePrincipal('lambda.amazonaws.com'), 95 | inlinePolicies: { 96 | ['chimePolicy']: new PolicyDocument({ 97 | statements: [ 98 | new PolicyStatement({ 99 | resources: ['*'], 100 | actions: ['chime:*'], 101 | }), 102 | ], 103 | }), 104 | }, 105 | managedPolicies: [ 106 | ManagedPolicy.fromAwsManagedPolicyName( 107 | 'service-role/AWSLambdaBasicExecutionRole', 108 | ), 109 | ], 110 | }); 111 | 112 | const smaHandlerLambda = new NodejsFunction(this, 'smaHandlerLambda', { 113 | entry: 'src/resources/smaHandler/smaHandler.ts', 114 | handler: 'lambdaHandler', 115 | runtime: Runtime.NODEJS_20_X, 116 | role: smaHandlerRole, 117 | architecture: Architecture.ARM_64, 118 | timeout: Duration.seconds(60), 119 | environment: { 120 | FROM_NUMBER: phoneNumber.phoneNumber, 121 | }, 122 | }); 123 | 124 | const sipMediaApp = new ChimeSipMediaApp(this, 'sipMediaApp', { 125 | region: Stack.of(this).region, 126 | endpoint: smaHandlerLambda.functionArn, 127 | }); 128 | 129 | this.fromNumber = phoneNumber.phoneNumber; 130 | this.smaId = sipMediaApp.sipMediaAppId; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/cognito.ts: -------------------------------------------------------------------------------- 1 | import { RemovalPolicy, Duration, Stack } from 'aws-cdk-lib'; 2 | import { 3 | CfnIdentityPool, 4 | IUserPool, 5 | IUserPoolClient, 6 | UserPool, 7 | UserPoolClient, 8 | UserPoolClientIdentityProvider, 9 | CfnIdentityPoolRoleAttachment, 10 | AccountRecovery, 11 | Mfa, 12 | } from 'aws-cdk-lib/aws-cognito'; 13 | import { 14 | IRole, 15 | Role, 16 | PolicyStatement, 17 | FederatedPrincipal, 18 | Effect, 19 | } from 'aws-cdk-lib/aws-iam'; 20 | import { Runtime, Architecture } from 'aws-cdk-lib/aws-lambda'; 21 | import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; 22 | import { Construct } from 'constructs'; 23 | 24 | export interface CognitoStackProps { 25 | readonly allowedDomain: string; 26 | } 27 | 28 | export class Cognito extends Construct { 29 | public readonly authenticatedRole: IRole; 30 | public readonly identityPool: CfnIdentityPool; 31 | public readonly userPool: IUserPool; 32 | public readonly userPoolClient: IUserPoolClient; 33 | public readonly userPoolRegion: string; 34 | 35 | constructor(scope: Construct, id: string, props: CognitoStackProps) { 36 | super(scope, id); 37 | 38 | const domainValidator = new NodejsFunction(this, 'domainValidator', { 39 | entry: 'src/resources/cognitoDomain/domainValidator.ts', 40 | bundling: { 41 | externalModules: ['aws-sdk'], 42 | }, 43 | runtime: Runtime.NODEJS_20_X, 44 | architecture: Architecture.ARM_64, 45 | timeout: Duration.seconds(60), 46 | environment: { 47 | ALLOWED_DOMAIN: props.allowedDomain, 48 | }, 49 | }); 50 | 51 | const userPool = new UserPool(this, 'UserPool', { 52 | removalPolicy: RemovalPolicy.DESTROY, 53 | selfSignUpEnabled: true, 54 | lambdaTriggers: { 55 | preSignUp: domainValidator, 56 | }, 57 | signInAliases: { 58 | username: false, 59 | phone: false, 60 | email: true, 61 | }, 62 | accountRecovery: AccountRecovery.EMAIL_ONLY, 63 | standardAttributes: { 64 | email: { 65 | required: true, 66 | mutable: true, 67 | }, 68 | }, 69 | mfa: Mfa.OPTIONAL, 70 | mfaSecondFactor: { 71 | sms: true, 72 | otp: true, 73 | }, 74 | userInvitation: { 75 | emailSubject: 'Your Click-To-Call web app temporary password', 76 | emailBody: 77 | 'Your Click-To-Call web app username is {username} and temporary password is {####}', 78 | }, 79 | userVerification: { 80 | emailSubject: 'Verify your new Click-To-Call web app account', 81 | emailBody: 82 | 'The verification code to your new Click-To-Call web app account is {####}', 83 | }, 84 | }); 85 | 86 | const userPoolClient = new UserPoolClient(this, 'UserPoolClient', { 87 | userPool: userPool, 88 | generateSecret: false, 89 | supportedIdentityProviders: [UserPoolClientIdentityProvider.COGNITO], 90 | authFlows: { 91 | userSrp: true, 92 | custom: true, 93 | }, 94 | refreshTokenValidity: Duration.hours(1), 95 | }); 96 | 97 | const identityPool = new CfnIdentityPool(this, 'cognitoIdentityPool', { 98 | identityPoolName: 'cognitoIdentityPool', 99 | allowUnauthenticatedIdentities: false, 100 | cognitoIdentityProviders: [ 101 | { 102 | clientId: userPoolClient.userPoolClientId, 103 | providerName: userPool.userPoolProviderName, 104 | }, 105 | ], 106 | }); 107 | 108 | const unauthenticatedRole = new Role( 109 | this, 110 | 'CognitoDefaultUnauthenticatedRole', 111 | { 112 | assumedBy: new FederatedPrincipal( 113 | 'cognito-identity.amazonaws.com', 114 | { 115 | // eslint-disable-next-line quote-props 116 | 'StringEquals': { 117 | 'cognito-identity.amazonaws.com:aud': identityPool.ref, 118 | }, 119 | 'ForAnyValue:StringLike': { 120 | 'cognito-identity.amazonaws.com:amr': 'unauthenticated', 121 | }, 122 | }, 123 | 'sts:AssumeRoleWithWebIdentity', 124 | ), 125 | }, 126 | ); 127 | 128 | unauthenticatedRole.addToPolicy( 129 | new PolicyStatement({ 130 | effect: Effect.ALLOW, 131 | actions: ['mobileanalytics:PutEvents', 'cognito-sync:*'], 132 | resources: ['*'], 133 | }), 134 | ); 135 | 136 | const authenticatedRole = new Role( 137 | this, 138 | 'CognitoDefaultAuthenticatedRole', 139 | { 140 | assumedBy: new FederatedPrincipal( 141 | 'cognito-identity.amazonaws.com', 142 | { 143 | // eslint-disable-next-line quote-props 144 | 'StringEquals': { 145 | 'cognito-identity.amazonaws.com:aud': identityPool.ref, 146 | }, 147 | 'ForAnyValue:StringLike': { 148 | 'cognito-identity.amazonaws.com:amr': 'authenticated', 149 | }, 150 | }, 151 | 'sts:AssumeRoleWithWebIdentity', 152 | ), 153 | }, 154 | ); 155 | 156 | authenticatedRole.addToPolicy( 157 | new PolicyStatement({ 158 | effect: Effect.ALLOW, 159 | actions: [ 160 | 'mobileanalytics:PutEvents', 161 | 'cognito-sync:*', 162 | 'cognito-identity:*', 163 | ], 164 | resources: ['*'], 165 | }), 166 | ); 167 | 168 | new CfnIdentityPoolRoleAttachment(this, 'DefaultValid', { 169 | identityPoolId: identityPool.ref, 170 | roles: { 171 | unauthenticated: unauthenticatedRole.roleArn, 172 | authenticated: authenticatedRole.roleArn, 173 | }, 174 | }); 175 | 176 | this.authenticatedRole = authenticatedRole; 177 | this.identityPool = identityPool; 178 | this.userPool = userPool; 179 | this.userPoolClient = userPoolClient; 180 | this.userPoolRegion = Stack.of(this).region; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/distribution.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from 'aws-cdk-lib'; 2 | import { 3 | AllowedMethods, 4 | CachePolicy, 5 | Distribution, 6 | OriginProtocolPolicy, 7 | OriginRequestCookieBehavior, 8 | OriginRequestHeaderBehavior, 9 | OriginRequestPolicy, 10 | OriginRequestQueryStringBehavior, 11 | PriceClass, 12 | ViewerProtocolPolicy, 13 | } from 'aws-cdk-lib/aws-cloudfront'; 14 | import { LoadBalancerV2Origin } from 'aws-cdk-lib/aws-cloudfront-origins'; 15 | import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; 16 | import { Construct } from 'constructs'; 17 | 18 | interface DistributionResourcesProps { 19 | applicationLoadBalancer: ApplicationLoadBalancer; 20 | } 21 | export class DistributionResources extends Construct { 22 | public distribution: Distribution; 23 | 24 | constructor(scope: Construct, id: string, props: DistributionResourcesProps) { 25 | super(scope, id); 26 | 27 | const removeCookiesPolicy = new OriginRequestPolicy( 28 | this, 29 | 'removeCookiesPolicy', 30 | { 31 | originRequestPolicyName: `RemoveCookies-${Stack.of(this).stackName}`, 32 | cookieBehavior: OriginRequestCookieBehavior.none(), 33 | headerBehavior: OriginRequestHeaderBehavior.denyList('Cookie'), 34 | queryStringBehavior: OriginRequestQueryStringBehavior.none(), 35 | }, 36 | ); 37 | 38 | this.distribution = new Distribution(this, 'Distribution', { 39 | defaultBehavior: { 40 | origin: new LoadBalancerV2Origin(props.applicationLoadBalancer, { 41 | httpPort: 80, 42 | protocolPolicy: OriginProtocolPolicy.HTTP_ONLY, 43 | }), 44 | viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, 45 | cachePolicy: CachePolicy.CACHING_DISABLED, 46 | allowedMethods: AllowedMethods.ALLOW_ALL, 47 | originRequestPolicy: OriginRequestPolicy.ALL_VIEWER, 48 | }, 49 | additionalBehaviors: { 50 | '/ws': { 51 | origin: new LoadBalancerV2Origin(props.applicationLoadBalancer, { 52 | httpPort: 8088, 53 | protocolPolicy: OriginProtocolPolicy.HTTP_ONLY, 54 | }), 55 | viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS, 56 | cachePolicy: CachePolicy.CACHING_DISABLED, 57 | allowedMethods: AllowedMethods.ALLOW_ALL, 58 | originRequestPolicy: removeCookiesPolicy, 59 | }, 60 | }, 61 | defaultRootObject: 'index.html', 62 | priceClass: PriceClass.PRICE_CLASS_100, 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vpc'; 2 | export * from './server'; 3 | export * from './chime-sdk-voice'; 4 | export * from './cognito'; 5 | export * from './infrastructure'; 6 | export * from './site'; 7 | export * from './distribution'; 8 | -------------------------------------------------------------------------------- /src/infrastructure.ts: -------------------------------------------------------------------------------- 1 | import { Stack, Duration } from 'aws-cdk-lib'; 2 | import { 3 | RestApi, 4 | LambdaIntegration, 5 | EndpointType, 6 | MethodLoggingLevel, 7 | CognitoUserPoolsAuthorizer, 8 | AuthorizationType, 9 | } from 'aws-cdk-lib/aws-apigateway'; 10 | import { IUserPool } from 'aws-cdk-lib/aws-cognito'; 11 | import { 12 | ManagedPolicy, 13 | Role, 14 | PolicyStatement, 15 | PolicyDocument, 16 | ServicePrincipal, 17 | } from 'aws-cdk-lib/aws-iam'; 18 | import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda'; 19 | import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; 20 | import { 21 | ChimePhoneNumber, 22 | ChimeVoiceConnector, 23 | } from 'cdk-amazon-chime-resources'; 24 | import { Construct } from 'constructs'; 25 | 26 | interface InfrastructureProps { 27 | readonly fromPhoneNumber: string; 28 | readonly smaId: string; 29 | readonly userPool: IUserPool; 30 | readonly voiceConnectorPhone?: ChimePhoneNumber; 31 | readonly voiceConnector?: ChimeVoiceConnector; 32 | } 33 | 34 | export class Infrastructure extends Construct { 35 | public readonly apiUrl: string; 36 | 37 | constructor(scope: Construct, id: string, props: InfrastructureProps) { 38 | super(scope, id); 39 | 40 | const infrastructureRole = new Role(this, 'infrastructureRole', { 41 | assumedBy: new ServicePrincipal('lambda.amazonaws.com'), 42 | inlinePolicies: { 43 | ['chimePolicy']: new PolicyDocument({ 44 | statements: [ 45 | new PolicyStatement({ 46 | resources: ['*'], 47 | actions: ['chime:*'], 48 | }), 49 | ], 50 | }), 51 | }, 52 | managedPolicies: [ 53 | ManagedPolicy.fromAwsManagedPolicyName( 54 | 'service-role/AWSLambdaBasicExecutionRole', 55 | ), 56 | ], 57 | }); 58 | const callControlLambda = new NodejsFunction(this, 'callControlLambda', { 59 | entry: 'src/resources/callControl/callControl.ts', 60 | runtime: Runtime.NODEJS_20_X, 61 | architecture: Architecture.ARM_64, 62 | role: infrastructureRole, 63 | timeout: Duration.seconds(60), 64 | environment: { 65 | SMA_ID: props.smaId, 66 | FROM_NUMBER: props.fromPhoneNumber, 67 | VOICE_CONNECTOR_PHONE: props.voiceConnectorPhone?.phoneNumber || '', 68 | VOICE_CONNECTOR_ARN: 69 | `arn:aws:chime:${Stack.of(this).region}:${ 70 | Stack.of(this).account 71 | }:vc/${props.voiceConnector!.voiceConnectorId}` || '', 72 | }, 73 | }); 74 | 75 | const updateCallLambda = new NodejsFunction(this, 'updateCallLambda', { 76 | entry: 'src/resources/updateCall/updateCall.ts', 77 | runtime: Runtime.NODEJS_20_X, 78 | architecture: Architecture.ARM_64, 79 | role: infrastructureRole, 80 | timeout: Duration.seconds(60), 81 | environment: { 82 | SMA_ID: props.smaId, 83 | FROM_NUMBER: props.fromPhoneNumber, 84 | VOICE_CONNECTOR_PHONE: props.voiceConnectorPhone?.phoneNumber || '', 85 | VOICE_CONNECTOR_ARN: 86 | `arn:aws:chime:${Stack.of(this).region}:${ 87 | Stack.of(this).account 88 | }:vc/${props.voiceConnector!.voiceConnectorId}` || '', 89 | }, 90 | }); 91 | 92 | const api = new RestApi(this, 'clickToCallApi', { 93 | defaultCorsPreflightOptions: { 94 | allowHeaders: [ 95 | 'Content-Type', 96 | 'X-Amz-Date', 97 | 'Authorization', 98 | 'X-Api-Key', 99 | ], 100 | allowMethods: ['OPTIONS', 'POST'], 101 | allowCredentials: true, 102 | allowOrigins: ['*'], 103 | }, 104 | deployOptions: { 105 | loggingLevel: MethodLoggingLevel.INFO, 106 | dataTraceEnabled: true, 107 | }, 108 | endpointConfiguration: { 109 | types: [EndpointType.REGIONAL], 110 | }, 111 | }); 112 | 113 | const auth = new CognitoUserPoolsAuthorizer(this, 'auth', { 114 | cognitoUserPools: [props.userPool], 115 | }); 116 | 117 | const dial = api.root.addResource('dial'); 118 | const update = api.root.addResource('update'); 119 | 120 | const callControlIntegration = new LambdaIntegration(callControlLambda); 121 | const updateCallIntegration = new LambdaIntegration(updateCallLambda); 122 | 123 | dial.addMethod('POST', callControlIntegration, { 124 | authorizer: auth, 125 | authorizationType: AuthorizationType.COGNITO, 126 | }); 127 | update.addMethod('POST', updateCallIntegration, { 128 | authorizer: auth, 129 | authorizationType: AuthorizationType.COGNITO, 130 | }); 131 | 132 | this.apiUrl = api.url; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/resources/asteriskConfig/asterisk.conf: -------------------------------------------------------------------------------- 1 | [options] 2 | runuser = asterisk 3 | rungroup = asterisk -------------------------------------------------------------------------------- /src/resources/asteriskConfig/config_asterisk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | IP=$( jq -r '.IP' /etc/config.json ) 3 | OUTBOUND_HOSTNAME=$( jq -r '.OutboundHostName' /etc/config.json ) 4 | PHONE_NUMBER=$( jq -r '.PhoneNumber' /etc/config.json ) 5 | 6 | sed -i "s/IP_ADDRESS/$IP/g" /etc/asterisk/pjsip.conf 7 | sed -i "s/OUTBOUND_HOST_NAME/$OUTBOUND_HOSTNAME/g" /etc/asterisk/pjsip.conf 8 | sed -i "s/PHONE_NUMBER/$PHONE_NUMBER/g" /etc/asterisk/pjsip.conf 9 | sed -i "s/PHONE_NUMBER/$PHONE_NUMBER/g" /etc/asterisk/extensions.conf 10 | 11 | groupadd asterisk 12 | useradd -r -d /var/lib/asterisk -g asterisk asterisk 13 | usermod -aG audio,dialout asterisk 14 | chown -R asterisk.asterisk /etc/asterisk 15 | chown -R asterisk.asterisk /var/{lib,log,spool}/asterisk 16 | echo '0 * * * * /sbin/asterisk -rx "core reload"' > /etc/asterisk/crontab.txt 17 | crontab /etc/asterisk/crontab.txt 18 | 19 | systemctl start asterisk -------------------------------------------------------------------------------- /src/resources/asteriskConfig/extensions.conf: -------------------------------------------------------------------------------- 1 | ; extensions.conf - the Asterisk dial plan 2 | ; 3 | [general] 4 | static=yes 5 | writeprotect=no 6 | clearglobalvars=no 7 | [catch-all] 8 | exten => _[+0-9].,1,Answer() 9 | exten => _[+0-9].,n,Wait(1) 10 | exten => _[+0-9].,n,Playback(hello-world) 11 | exten => _[+0-9].,n,Wait(1) 12 | exten => _[+0-9].,n,Echo() 13 | exten => _[+0-9].,n,Wait(1) 14 | exten => _[+0-9].,n,Hangup() 15 | [from-phone] 16 | include => outbound_phone 17 | [outbound_phone] 18 | exten => _+X.,1,NoOP(Outbound Normal) 19 | same => n,Dial(PJSIP/${EXTEN}@VoiceConnector,20) 20 | same => n,Congestion 21 | [from-voiceConnector] 22 | include => phones 23 | include => catch-all 24 | [phones] 25 | exten => PHONE_NUMBER,1,Dial(PJSIP/PHONE_NUMBER) -------------------------------------------------------------------------------- /src/resources/asteriskConfig/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | ## Copyright Amazon.com Inc. or its affiliates. 3 | HOMEDIR=/home/ec2-user 4 | cd /tmp 5 | yum -y install make gcc gcc-c++ make subversion libxml2-devel ncurses-devel openssl-devel vim-enhanced man glibc-devel autoconf libnewt kernel-devel kernel-headers linux-headers openssl-devel zlib-devel libsrtp libsrtp-devel uuid libuuid-devel mariadb-server jansson-devel libsqlite3x libsqlite3x-devel epel-release.noarch bash-completion bash-completion-extras unixODBC unixODBC-devel libtool-ltdl libtool-ltdl-devel mysql-connector-odbc mlocate libiodbc sqlite sqlite-devel sql-devel.i686 sqlite-doc.noarch sqlite-tcl.x86_64 patch libedit-devel jq 6 | wget https://downloads.asterisk.org/pub/telephony/asterisk/asterisk-18-current.tar.gz 7 | tar xvzf asterisk-18-current.tar.gz 8 | cd asterisk-18*/ 9 | ./configure --libdir=/usr/lib64 --with-jansson-bundled 10 | make menuselect.makeopts 11 | menuselect/menuselect \ 12 | --disable BUILD_NATIVE \ 13 | --disable chan_sip \ 14 | --disable chan_skinny \ 15 | --enable cdr_csv \ 16 | --enable res_snmp \ 17 | --enable res_http_websocket \ 18 | menuselect.makeopts 19 | make 20 | make install 21 | make basic-pbx 22 | touch /etc/redhat-release 23 | make config 24 | ldconfig 25 | -------------------------------------------------------------------------------- /src/resources/asteriskConfig/logger.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | [logfiles] 3 | console = verbose,notice,warning,error 4 | messages = notice,warning,error -------------------------------------------------------------------------------- /src/resources/asteriskConfig/modules.conf: -------------------------------------------------------------------------------- 1 | [modules] 2 | autoload = no 3 | 4 | ; This is a minimal module load. We are loading only the modules 5 | ; required for the Asterisk features used in the "Super Awesome 6 | ; Company" configuration. 7 | 8 | ; Applications 9 | 10 | load = app_bridgewait.so 11 | load = app_dial.so 12 | load = app_playback.so 13 | load = app_stack.so 14 | load = app_verbose.so 15 | load = app_voicemail.so 16 | load = app_directory.so 17 | load = app_confbridge.so 18 | load = app_queue.so 19 | load = app_echo.so 20 | 21 | ; Bridging 22 | 23 | load = bridge_builtin_features.so 24 | load = bridge_builtin_interval_features.so 25 | load = bridge_holding.so 26 | load = bridge_native_rtp.so 27 | load = bridge_simple.so 28 | load = bridge_softmix.so 29 | 30 | ; Call Detail Records 31 | 32 | load = cdr_custom.so 33 | 34 | ; Channel Drivers 35 | 36 | load = chan_bridge_media.so 37 | load = chan_pjsip.so 38 | 39 | ; Codecs 40 | 41 | load = codec_gsm.so 42 | load = codec_resample.so 43 | load = codec_ulaw.so 44 | load = codec_g722.so 45 | 46 | ; Formats 47 | 48 | load = format_gsm.so 49 | load = format_pcm.so 50 | load = format_wav_gsm.so 51 | load = format_wav.so 52 | 53 | ; Functions 54 | 55 | load = func_callerid.so 56 | load = func_cdr.so 57 | load = func_pjsip_endpoint.so 58 | load = func_sorcery.so 59 | load = func_devstate.so 60 | load = func_strings.so 61 | 62 | ; Core/PBX 63 | 64 | load = pbx_config.so 65 | 66 | ; Resources 67 | 68 | load = res_http_websocket.so 69 | load = res_musiconhold.so 70 | load = res_pjproject.so 71 | load = res_pjsip_acl.so 72 | load = res_pjsip_authenticator_digest.so 73 | load = res_pjsip_caller_id.so 74 | load = res_pjsip_dialog_info_body_generator.so 75 | load = res_pjsip_diversion.so 76 | load = res_pjsip_dtmf_info.so 77 | load = res_pjsip_endpoint_identifier_anonymous.so 78 | load = res_pjsip_endpoint_identifier_ip.so 79 | load = res_pjsip_endpoint_identifier_user.so 80 | load = res_pjsip_exten_state.so 81 | load = res_pjsip_header_funcs.so 82 | load = res_pjsip_logger.so 83 | load = res_pjsip_messaging.so 84 | load = res_pjsip_mwi_body_generator.so 85 | load = res_pjsip_mwi.so 86 | load = res_pjsip_nat.so 87 | load = res_pjsip_notify.so 88 | load = res_pjsip_one_touch_record_info.so 89 | load = res_pjsip_outbound_authenticator_digest.so 90 | load = res_pjsip_outbound_publish.so 91 | load = res_pjsip_outbound_registration.so 92 | load = res_pjsip_path.so 93 | load = res_pjsip_pidf_body_generator.so 94 | load = res_pjsip_pidf_digium_body_supplement.so 95 | load = res_pjsip_pidf_eyebeam_body_supplement.so 96 | load = res_pjsip_publish_asterisk.so 97 | load = res_pjsip_pubsub.so 98 | load = res_pjsip_refer.so 99 | load = res_pjsip_registrar.so 100 | load = res_pjsip_rfc3326.so 101 | load = res_pjsip_sdp_rtp.so 102 | load = res_pjsip_send_to_voicemail.so 103 | load = res_pjsip_session.so 104 | load = res_pjsip.so 105 | load = res_pjsip_t38.so 106 | load = res_pjsip_transport_websocket.so 107 | load = res_pjsip_xpidf_body_generator.so 108 | load = res_rtp_asterisk.so 109 | load = res_sorcery_astdb.so 110 | load = res_sorcery_config.so 111 | load = res_sorcery_memory.so 112 | load = res_sorcery_realtime.so 113 | load = res_timing_timerfd.so 114 | 115 | ; Do not load res_hep and kin unless you are using HEP monitoring 116 | ; in your network. 117 | 118 | noload = res_hep.so 119 | noload = res_hep_pjsip.so 120 | noload = res_hep_rtcp.so -------------------------------------------------------------------------------- /src/resources/asteriskConfig/pjsip.conf: -------------------------------------------------------------------------------- 1 | [udp] 2 | type=transport 3 | protocol=udp 4 | bind=0.0.0.0 5 | external_media_address=IP_ADDRESS 6 | external_signaling_address=IP_ADDRESS 7 | allow_reload=yes 8 | [VoiceConnector] 9 | type=endpoint 10 | context=from-voiceConnector 11 | transport=udp 12 | disallow=all 13 | allow=ulaw 14 | aors=VoiceConnector 15 | direct_media=no 16 | ice_support=yes 17 | force_rport=yes 18 | [VoiceConnector] 19 | type=identify 20 | endpoint=VoiceConnector 21 | match=OUTBOUND_HOST_NAME 22 | [VoiceConnector] 23 | type=aor 24 | contact=sip:OUTBOUND_HOST_NAME 25 | [PHONE_NUMBER] 26 | type=endpoint 27 | context=from-phone 28 | disallow=all 29 | allow=ulaw 30 | transport=udp 31 | auth=PHONE_NUMBER 32 | aors=PHONE_NUMBER 33 | send_pai=yes 34 | direct_media=no 35 | rewrite_contact=yes 36 | ice_support=yes 37 | force_rport=yes 38 | [PHONE_NUMBER] 39 | type=auth 40 | auth_type=userpass 41 | password=ChimeDemo 42 | username=PHONE_NUMBER 43 | [PHONE_NUMBER] 44 | type=aor 45 | max_contacts=5 -------------------------------------------------------------------------------- /src/resources/callControl/callControl.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto'; 2 | import { 3 | ChimeSDKMeetingsClient, 4 | CreateMeetingCommand, 5 | CreateAttendeeCommand, 6 | } from '@aws-sdk/client-chime-sdk-meetings'; 7 | import { 8 | ChimeSDKVoiceClient, 9 | CreateSipMediaApplicationCallCommand, 10 | } from '@aws-sdk/client-chime-sdk-voice'; 11 | import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; 12 | 13 | const EXCLUDE_DIAL_TO = 14 | /^\+((1900)|(1976)|(1268)|(1284)|(1473)|(1649)|(1664)|(1767)|(1809)|(1829)|(1849)|(1876))(\d{7})$/; 15 | const INCLUDE_DIAL_TO = /^\+1[2-9]\d{2}[2-9]\d{6}$/; 16 | 17 | const AWS_REGION = process.env.AWS_REGION; 18 | const config = { 19 | region: AWS_REGION, 20 | }; 21 | 22 | const chimeSdkVoiceClient = new ChimeSDKVoiceClient(config); 23 | const chimeSdkMeetingsClient = new ChimeSDKMeetingsClient(config); 24 | 25 | const fromNumber = process.env.FROM_NUMBER || ''; 26 | const voiceConnectorPhone = process.env.VOICE_CONNECTOR_PHONE || ''; 27 | const voiceConnectorArn = process.env.VOICE_CONNECTOR_ARN || ''; 28 | const smaId = process.env.SMA_ID || ''; 29 | const numberToCall = process.env.NUMBER_TO_CALL || ''; 30 | 31 | export const handler = async ( 32 | event: APIGatewayProxyEvent, 33 | ): Promise => { 34 | console.info(`Event: ${JSON.stringify(event)}`); 35 | 36 | const body = JSON.parse(event.body || '{}'); 37 | console.info('Body: ' + JSON.stringify(body)); 38 | 39 | let toNumber = body.toNumber || numberToCall; 40 | 41 | if (!toNumber) { 42 | return { 43 | statusCode: 503, 44 | body: 'Missing Number', 45 | headers: { 46 | 'Access-Control-Allow-Origin': '*', 47 | 'Access-Control-Allow-Headers': '*', 48 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 49 | 'Content-Type': 'application/json', 50 | }, 51 | }; 52 | } 53 | 54 | toNumber = toNumber.replace(/\s+/g, '').replace(/[\(\)\-]/g, ''); 55 | 56 | if (!toNumber.startsWith('+')) { 57 | toNumber = toNumber.length === 10 ? `+1${toNumber}` : `+${toNumber}`; 58 | } 59 | 60 | if ( 61 | !EXCLUDE_DIAL_TO.test(toNumber) || 62 | INCLUDE_DIAL_TO.test(toNumber) || 63 | toNumber === '' 64 | ) { 65 | const meetingInfo = await createMeeting(randomUUID()); 66 | 67 | if (meetingInfo) { 68 | const clientAttendeeInfo = await createAttendee( 69 | meetingInfo.Meeting!.MeetingId!, 70 | 'client-user', 71 | ); 72 | 73 | if (clientAttendeeInfo) { 74 | const responseInfo = { 75 | Meeting: meetingInfo.Meeting, 76 | Attendee: clientAttendeeInfo.Attendee, 77 | }; 78 | 79 | const phoneAttendeeInfo = await createAttendee( 80 | meetingInfo.Meeting!.MeetingId!, 81 | 'phone-user', 82 | ); 83 | const dialInfo = await executeDial( 84 | event, 85 | meetingInfo, 86 | phoneAttendeeInfo, 87 | toNumber, 88 | ); 89 | 90 | console.info('joinInfo: ' + JSON.stringify({ responseInfo, dialInfo })); 91 | 92 | return { 93 | statusCode: 200, 94 | body: JSON.stringify({ responseInfo, dialInfo }), 95 | headers: { 96 | 'Access-Control-Allow-Origin': '*', 97 | 'Access-Control-Allow-Headers': '*', 98 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 99 | 'Content-Type': 'application/json', 100 | }, 101 | }; 102 | } else { 103 | return { 104 | statusCode: 503, 105 | body: 'Error creating attendee', 106 | headers: { 107 | 'Access-Control-Allow-Origin': '*', 108 | 'Access-Control-Allow-Headers': '*', 109 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 110 | 'Content-Type': 'application/json', 111 | }, 112 | }; 113 | } 114 | } else { 115 | return { 116 | statusCode: 503, 117 | body: 'Error creating meeting', 118 | headers: { 119 | 'Access-Control-Allow-Origin': '*', 120 | 'Access-Control-Allow-Headers': '*', 121 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 122 | 'Content-Type': 'application/json', 123 | }, 124 | }; 125 | } 126 | } else { 127 | return { 128 | statusCode: 503, 129 | body: 'Bad Number', 130 | headers: { 131 | 'Access-Control-Allow-Origin': '*', 132 | 'Access-Control-Allow-Headers': '*', 133 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 134 | 'Content-Type': 'application/json', 135 | }, 136 | }; 137 | } 138 | }; 139 | 140 | async function executeDial( 141 | event: APIGatewayProxyEvent, 142 | meetingInfo: any, 143 | phoneAttendeeInfo: any, 144 | toNumber: string, 145 | ) { 146 | const dialVC = toNumber === voiceConnectorPhone ? 'true' : 'false'; 147 | 148 | const params = { 149 | FromPhoneNumber: fromNumber, 150 | SipMediaApplicationId: smaId, 151 | ToPhoneNumber: '+17035550122', // Replace with your desired phone number 152 | SipHeaders: { 153 | 'X-chime-join-token': phoneAttendeeInfo.Attendee.JoinToken, 154 | 'X-chime-meeting-id': meetingInfo.Meeting.MeetingId, 155 | }, 156 | ArgumentsMap: { 157 | MeetingId: meetingInfo.Meeting.MeetingId, 158 | RequestedDialNumber: toNumber, 159 | RequestedVCArn: voiceConnectorArn, 160 | RequestorEmail: event.requestContext.authorizer?.claims?.email || '', 161 | DialVC: dialVC, 162 | }, 163 | }; 164 | 165 | console.info('Dial Params: ' + JSON.stringify(params)); 166 | 167 | try { 168 | const dialInfo = await chimeSdkVoiceClient.send( 169 | new CreateSipMediaApplicationCallCommand(params), 170 | ); 171 | return dialInfo; 172 | } catch (err) { 173 | console.info(`Error: ${err}`); 174 | return false; 175 | } 176 | } 177 | 178 | async function createMeeting(requestId: string) { 179 | console.log(`Creating Meeting for Request ID: ${requestId}`); 180 | 181 | try { 182 | const meetingInfo = await chimeSdkMeetingsClient.send( 183 | new CreateMeetingCommand({ 184 | ClientRequestToken: requestId, 185 | MediaRegion: 'us-east-1', 186 | ExternalMeetingId: randomUUID(), 187 | }), 188 | ); 189 | 190 | return meetingInfo; 191 | } catch (err) { 192 | console.info(`Error: ${err}`); 193 | return false; 194 | } 195 | } 196 | 197 | async function createAttendee(meetingId: string, externalUserId: string) { 198 | console.log(`Creating Attendee for Meeting: ${meetingId}`); 199 | 200 | try { 201 | const attendeeInfo = await chimeSdkMeetingsClient.send( 202 | new CreateAttendeeCommand({ 203 | MeetingId: meetingId, 204 | ExternalUserId: externalUserId, 205 | }), 206 | ); 207 | 208 | return attendeeInfo; 209 | } catch (err) { 210 | console.info(`${err}`); 211 | return false; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/resources/cognitoDomain/domainValidator.ts: -------------------------------------------------------------------------------- 1 | import { PreSignUpTriggerHandler } from 'aws-lambda'; 2 | 3 | const allowedDomain = process.env.ALLOWED_DOMAIN || ''; 4 | 5 | export const handler: PreSignUpTriggerHandler = async (event, _context) => { 6 | try { 7 | const userEmailDomain = event.request.userAttributes.email.split('@')[1]; 8 | 9 | if ( 10 | userEmailDomain === allowedDomain || 11 | !allowedDomain || 12 | allowedDomain.length === 0 13 | ) { 14 | return event; 15 | } else { 16 | throw new Error( 17 | 'Cannot authenticate users from domains different from ' + 18 | allowedDomain, 19 | ); 20 | } 21 | } catch (error) { 22 | throw error; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/resources/server/assets/audio/AGENT_AutoRepairs47.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-chime-sdk-click-to-call/c83333d24192f857619ff821bfb2bce6ca5ccb8e/src/resources/server/assets/audio/AGENT_AutoRepairs47.wav -------------------------------------------------------------------------------- /src/resources/server/assets/audio/AGENT_Retail40.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-chime-sdk-click-to-call/c83333d24192f857619ff821bfb2bce6ca5ccb8e/src/resources/server/assets/audio/AGENT_Retail40.wav -------------------------------------------------------------------------------- /src/resources/server/assets/site/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /dist 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | !*.js 26 | cdk-outputs.json 27 | 28 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; 8 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "call-summarizer", 3 | "version": "1.0.0", 4 | "main": "index.tsx", 5 | "scripts": { 6 | "start": "webpack serve --mode development --open --hot", 7 | "prebuild": "rm -rf dist", 8 | "build": "webpack --mode production && rsync -av --delete dist/ /var/www/html/" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "@aws-amplify/ui-react": "^5.1.1", 13 | "@cloudscape-design/components": "^3.0.303", 14 | "@cloudscape-design/design-tokens": "^3.0.15", 15 | "@cloudscape-design/global-styles": "^1.0.9", 16 | "@types/react": "^18.2.5", 17 | "@types/react-dom": "^18.2.4", 18 | "aws-amplify": "^5.3.10", 19 | "dotenv-webpack": "^8.0.1", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "react-phone-number-input": "^3.3.0", 23 | "sip.js": "^0.21.2" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.22.9", 27 | "@babel/preset-env": "^7.22.9", 28 | "@babel/preset-react": "^7.22.5", 29 | "babel-loader": "^9.1.3", 30 | "css-loader": "^6.7.3", 31 | "html-webpack-plugin": "^5.5.1", 32 | "style-loader": "^3.3.2", 33 | "ts-loader": "^9.4.2", 34 | "typescript": "^5.0.4", 35 | "webpack": "^5.94.0", 36 | "webpack-cli": "^5.0.2", 37 | "webpack-dev-server": "^4.13.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Amazon Chime Voice Connector Demo 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/src/Amplify.js: -------------------------------------------------------------------------------- 1 | const USER_POOL_REGION = process.env.USER_POOL_REGION || ''; 2 | const USER_POOL_ID = process.env.USER_POOL_ID || ''; 3 | const WEB_CLIENT_ID = process.env.WEB_CLIENT_ID || ''; 4 | const IDENTITY_POOL = process.env.IDENTITY_POOL_ID || ''; 5 | 6 | export const AmplifyConfig = { 7 | Auth: { 8 | region: USER_POOL_REGION, 9 | userPoolId: USER_POOL_ID, 10 | userPoolWebClientId: WEB_CLIENT_ID, 11 | identityPool: IDENTITY_POOL, 12 | mandatorySignIn: true, 13 | cookieStorage: { 14 | domain: `${window.location.hostname}`, 15 | path: '/', 16 | expires: 365, 17 | secure: true, 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import SipProvider from './SipProvider'; 3 | import { AmplifyConfig } from './Amplify'; 4 | import { Auth, Amplify, API } from 'aws-amplify'; 5 | import { Authenticator } from '@aws-amplify/ui-react'; 6 | import '@aws-amplify/ui-react/styles.css'; 7 | Amplify.configure(AmplifyConfig); 8 | API.configure(AmplifyConfig); 9 | Amplify.Logger.LOG_LEVEL = 'DEBUG'; 10 | 11 | const App = () => { 12 | const [currentCredentials, setCurrentCredentials] = useState({}); 13 | const [currentSession, setCurrentSession] = useState({}); 14 | useEffect(() => { 15 | async function getAuth() { 16 | try { 17 | const session = await Auth.currentSession(); 18 | const credentials = await Auth.currentUserCredentials(); 19 | setCurrentSession(session); 20 | setCurrentCredentials(credentials); 21 | console.log(`authState: ${JSON.stringify(session)}`); 22 | console.log(`currentCredentials: ${JSON.stringify(credentials)}`); 23 | } catch (error) { 24 | // Handle the error here, for example: 25 | console.error('Error fetching authentication information:', error); 26 | setCurrentSession(null); // Set to null or some default value 27 | setCurrentCredentials(null); // Set to null or some default value 28 | } 29 | } 30 | getAuth(); 31 | }, []); 32 | const formFields = { 33 | signUp: { 34 | email: { 35 | order: 1, 36 | isRequired: true, 37 | }, 38 | password: { 39 | order: 4, 40 | }, 41 | confirm_password: { 42 | order: 5, 43 | }, 44 | }, 45 | }; 46 | 47 | return ( 48 | 49 | {({ signOut, user }) => } 50 | 51 | ); 52 | }; 53 | 54 | export default App; 55 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/src/CallControl.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SessionState } from 'sip.js'; 3 | import { Button, SpaceBetween } from '@cloudscape-design/components'; 4 | 5 | const CallControl = ({ incomingCall, sessionState, isRegistered, acceptCall, rejectCall, makeCall, hangUp }) => { 6 | return ( 7 | 8 | {incomingCall && ( 9 | <> 10 | 13 | 16 | 17 | )} 18 | {sessionState !== SessionState.Initial && 19 | sessionState !== SessionState.Established && 20 | sessionState !== SessionState.Establishing && 21 | sessionState !== SessionState.Terminating && 22 | !incomingCall && 23 | isRegistered && } 24 | {sessionState === SessionState.Established && } 25 | 26 | ); 27 | }; 28 | 29 | export default CallControl; 30 | -------------------------------------------------------------------------------- /src/resources/server/assets/site/src/MediaContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SessionState } from 'sip.js'; 3 | import SipUserAgent from './SipUserAgent'; 4 | import PhoneNumberInput from './PhoneNumberInput'; 5 | 6 | const MediaContainer = ({ 7 | setUserAgent, 8 | onInvite, 9 | sessionState, 10 | isRegistered, 11 | phoneNumber, 12 | setPhoneNumber, 13 | mediaElementRef, 14 | incomingCall, 15 | }) => { 16 | return ( 17 | <> 18 | 19 | {sessionState !== SessionState.Initial && 20 | sessionState !== SessionState.Established && 21 | sessionState !== SessionState.Establishing && 22 | sessionState !== SessionState.Terminating && 23 | !incomingCall && 24 | isRegistered && ( 25 | 30 | )} 31 |