├── .github ├── ISSUE_TEMPLATE │ ├── enhancement.yml │ ├── ex-prep.yml │ ├── ex-story-standalone.yml │ ├── ex-story.yml │ └── story.yml ├── actions │ ├── forward-project-field.mjs │ ├── octonode.d.ts │ ├── score-triaged-defects.mjs │ └── sync-labels.ts ├── labels.yml └── workflows │ ├── score-triaged-defects.yml │ ├── static_analysis.yml │ ├── sync-labels.yml │ ├── triage-move-labelled.yml │ ├── triage-move-unlabelled.yml │ └── x-plorers-epic-forwarding.yml ├── .gitignore ├── README.md ├── docs ├── FTUE.md ├── auth │ └── enhanced_idm_integration.md ├── chat_effects.md ├── client_well_known.md ├── crypto │ ├── backup.md │ ├── trust_v1.md │ └── trust_v2.md ├── device_management.md ├── keyboard_shortcuts.md ├── notifications_defaultsettings.md ├── profile_signout.md ├── roomlist_messagePreviews.md ├── roomlist_sort.md ├── rooms_invitejoinleaveban.md └── text_effects.md ├── package.json ├── spec ├── functional_members.md └── matrix_client_information.md ├── tsconfig.json ├── wiki-images ├── test-cases.png ├── test-cases.svg ├── testing-issue-list.png ├── testing-issues.png ├── testing-issues.svg ├── testing-tabs.png └── testing-test-cases.png └── yarn.lock /.github/ISSUE_TEMPLATE/enhancement.yml: -------------------------------------------------------------------------------- 1 | name: Enhancement request 2 | description: Do you have a suggestion or feature request? 3 | labels: [T-Enhancement] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion](https://github.com/vector-im/element-meta/discussions/new?category=ideas). 9 | - type: textarea 10 | id: usecase 11 | attributes: 12 | label: Your use case 13 | description: Please feel welcome to include screenshots or mock ups. 14 | placeholder: Tell us what you would like to do! 15 | value: | 16 | #### What would you like to do? 17 | 18 | #### Why would you like to do it? 19 | 20 | #### How would you like to achieve it? 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Have you considered any alternatives? 27 | placeholder: A clear and concise description of any alternative solutions or features you've considered. 28 | validations: 29 | required: false 30 | - type: textarea 31 | id: additional-context 32 | attributes: 33 | label: Additional context 34 | placeholder: Is there anything else you'd like to add? 35 | validations: 36 | required: false 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ex-prep.yml: -------------------------------------------------------------------------------- 1 | name: EX – Prep task 2 | description: A preparatory task without direct user value 3 | title: "[Prep] " 4 | labels: ["App: ElementX Android", "App: ElementX iOS", "T-Prep", "Team: Element X Platform"] 5 | 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Description 10 | value: | 11 | Description of the task 12 | 13 | # Acceptance criteria 14 | - TBD 15 | 16 | # Size estimate 17 | S/M/L 18 | 19 | # Dependencies 20 | - None 21 | 22 | # Out of scope 23 | - 24 | 25 | # Subtasks 26 | ```[tasklist] 27 | ### Android 28 | ``` 29 | 30 | ```[tasklist] 31 | ### iOS 32 | ``` 33 | 34 | ```[tasklist] 35 | ### Rust 36 | ``` 37 | 38 | ```[tasklist] 39 | ### Other 40 | ``` 41 | validations: 42 | required: false 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ex-story-standalone.yml: -------------------------------------------------------------------------------- 1 | name: EX – Standalone User story 2 | description: Template for a standalone user story with no parent projet or epic. This a small "epic" with the appropriate process and labels that fit in the team's planning board. 3 | title: "[Story] <title>" 4 | labels: ["App: ElementX Android", "App: ElementX iOS", "T-User Story", "T-Epic", "Team: Element X Platform"] 5 | 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Description 10 | value: | 11 | * As a [beneficiary] 12 | * I want to [objective] 13 | * So that [value generated] 14 | 15 | ### Acceptance criteria 16 | - TBD 17 | 18 | ### Leads 19 | * Tech: <GitHub id> 20 | * Design: <GitHub id> 21 | 22 | ### Time sheeting 23 | <!--The project to report the time spent on the feature--> 24 | ? 25 | 26 | ### Documentation 27 | - 28 | 29 | ### Dependencies 30 | - None 31 | 32 | ### Out of scope 33 | - Nothing 34 | 35 | ### Open questions 36 | - [ ] 37 | 38 | ## Subtasks 39 | 40 | ### Android 41 | - 42 | 43 | ### iOS 44 | - 45 | 46 | ### Rust 47 | - 48 | 49 | ### Other 50 | - 51 | 52 | ## Sign-offs 53 | ### Android 54 | - [ ] Design sign-off on completion 55 | - [ ] QA sign-off on completion 56 | - [ ] Product sign-off on completion 57 | 58 | ### iOS 59 | - [ ] Design sign-off on completion 60 | - [ ] QA sign-off on completion 61 | - [ ] Product sign-off on completion 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ex-story.yml: -------------------------------------------------------------------------------- 1 | name: EX – User story 2 | description: Template for a second-level planning issue template. It must be part of an Epic. 3 | title: "[Story] <title>" 4 | labels: ["App: ElementX Android", "App: ElementX iOS", "T-User Story", "Team: Element X Platform"] 5 | 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Description 10 | value: | 11 | * As a [beneficiary] 12 | * I want to [objective] 13 | * So that [value generated] 14 | 15 | ### Acceptance criteria 16 | - TBD 17 | 18 | ### Dependencies 19 | - None 20 | 21 | ### Out of scope 22 | - Nothing 23 | 24 | ### Questions 25 | - [ ] 26 | 27 | ## Subtasks 28 | 29 | ### Android 30 | - 31 | 32 | ### iOS 33 | - 34 | 35 | ### Rust 36 | - 37 | 38 | ### Other 39 | - 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/story.yml: -------------------------------------------------------------------------------- 1 | name: User story 2 | description: Second-level planning issue template. A story should take about a week or a sprint to finish. It is normally one of a number of parts of an Epic 3 | title: "[Story] <title>" 4 | labels: [T-User Story] 5 | 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Story 10 | description: A story should take roughly a week or a sprint to finish. Each story is usually made up of a number of tasks that take half to a full day. 11 | value: | 12 | As a user… 13 | I want to… 14 | so that I can… 15 | - type: textarea 16 | attributes: 17 | label: Dependencies 18 | value: | 19 | - TBD 20 | - type: textarea 21 | attributes: 22 | label: Sign-offs 23 | value: | 24 | #### Android 25 | - [ ] Design sign-off on completion 26 | - [ ] QA sign-off on completion 27 | - [ ] Product sign-off on completion 28 | 29 | #### iOS 30 | - [ ] Design sign-off on completion 31 | - [ ] QA sign-off on completion 32 | - [ ] Product sign-off on completion 33 | 34 | #### Web 35 | - [ ] Design sign-off on completion 36 | - [ ] QA sign-off on completion 37 | - [ ] Product sign-off on completion 38 | - type: textarea 39 | attributes: 40 | label: Scope 41 | value: | 42 | <!--These should be a list of technical tasks which take ½-1 day to complete--> 43 | ```[tasklist] 44 | ### Android 45 | - [ ] Verified with build in screen reader 46 | ``` 47 | 48 | ```[tasklist] 49 | ### iOS 50 | - [ ] Verified with build in screen reader 51 | ``` 52 | 53 | ```[tasklist] 54 | ### Web 55 | - [ ] Verified with system in screen reader 56 | ``` 57 | 58 | ## Out of scope 59 | <!-- Include known issues here, as well as anything that has been acknowledged as not in the scope of this story --> 60 | - TBD 61 | 62 | validations: 63 | required: false 64 | -------------------------------------------------------------------------------- /.github/actions/forward-project-field.mjs: -------------------------------------------------------------------------------- 1 | import { Octokit } from "@octokit/action"; 2 | 3 | const octokit = new Octokit(); 4 | 5 | const headers = { "GraphQL-Features": "projects_next_graphql" } 6 | 7 | const REPO_OWNER = process.env.REPO_OWNER; 8 | const REPO_NAME = process.env.REPO_NAME; 9 | const ISSUE_URL = process.env.ISSUE_URL; 10 | const ISSUE_NUMBER = parseInt(process.env.ISSUE_NUMBER); 11 | const PROJECT_ID = process.env.PROJECT_ID; 12 | const FIELD_ID = process.env.FIELD_ID; 13 | const FIELD_NAME = process.env.FIELD_NAME; 14 | 15 | const visitedIssueUrls = new Set(); 16 | 17 | async function queryFieldValue(repoOwner, repoName, issueNumber, fieldName) { 18 | const query = `query ($owner: String!, $repo: String!, $issueNumber: Int!, $fieldName: String!) { 19 | repository(owner: $owner, name: $repo) { 20 | issue(number: $issueNumber) { 21 | projectItems(first: 100) { 22 | edges { 23 | node { 24 | id 25 | fieldValueByName(name: $fieldName) { 26 | ... on ProjectV2ItemFieldSingleSelectValue { 27 | optionId 28 | name 29 | field { 30 | ... on ProjectV2SingleSelectField { 31 | id 32 | } 33 | } 34 | } 35 | } 36 | project { 37 | id 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | }`; 45 | 46 | const parameters = { 47 | owner: repoOwner, 48 | repo: repoName, 49 | issueNumber: issueNumber, 50 | fieldName: fieldName, 51 | headers 52 | }; 53 | 54 | const result = await octokit.graphql(query, parameters); 55 | 56 | return result.repository.issue.projectItems.edges; 57 | } 58 | 59 | function determineFieldValue(projectItems, projectId, fieldId) { 60 | for (const item of projectItems) { 61 | if (item.node.project.id == projectId && item.node.fieldValueByName && item.node.fieldValueByName.field.id == fieldId) { 62 | return { name: item.node.fieldValueByName.name, id: item.node.fieldValueByName.optionId }; 63 | } 64 | } 65 | 66 | return {} 67 | } 68 | 69 | async function setFieldValueOnTrackedIssues(repoOwner, repoName, issueUrl, issueNumber, projectId, fieldId, fieldName, fieldValue) { 70 | // Avoid infinite loop 71 | 72 | if (visitedIssueUrls.has(issueUrl)) { 73 | return; 74 | } 75 | visitedIssueUrls.add(issueUrl); 76 | 77 | // Make sure this is actually an issue 78 | 79 | if (issueUrl.indexOf("/issues/") < 0) { 80 | return; 81 | } 82 | 83 | // Get tracked issues 84 | 85 | console.log(`Querying tracked issues of ${issueUrl}`); 86 | const trackedIssues = await queryTrackedIssues(repoOwner, repoName, issueNumber); 87 | 88 | if (!trackedIssues || trackedIssues.length == 0) { 89 | console.log("Aborting because issue has no tracked issues"); 90 | return; 91 | } 92 | 93 | console.log(trackedIssues.map(issue => issue.node.url)); 94 | 95 | // Set field values 96 | 97 | for (const issue of trackedIssues) { 98 | const trackedIssueId = issue.node.id; 99 | const trackedIssueUrl = issue.node.url; 100 | const trackedIssueNumber = issue.node.number; 101 | const trackedRepoOwner = issue.node.repository.owner.login; 102 | const trackedRepoName = issue.node.repository.name; 103 | 104 | // Get project item or add one if needed 105 | 106 | let itemId = issue.node.projectItems.edges.find(item => item.node.project.id == projectId)?.node?.id; 107 | 108 | if (!itemId) { 109 | console.log(`Adding ${trackedIssueUrl} to project`); 110 | itemId = await addItemToProject(projectId, trackedIssueId); 111 | } 112 | 113 | // Set field value 114 | 115 | console.log(`Setting value "${fieldValue.name}" for field "${fieldName}" of ${trackedIssueUrl}`); 116 | mutateFieldValue(projectId, itemId, fieldId, fieldValue.id); 117 | 118 | // Recurse 119 | 120 | await setFieldValueOnTrackedIssues(trackedRepoOwner, trackedRepoName, trackedIssueUrl, trackedIssueNumber, projectId, fieldId, fieldName, fieldValue); 121 | } 122 | } 123 | 124 | async function queryTrackedIssues(repoOwner, repoName, issueNumber) { 125 | const query = `query ($owner: String!, $repo: String!, $issueNumber: Int!) { 126 | repository(owner: $owner, name: $repo) { 127 | issue(number: $issueNumber) { 128 | trackedIssues(first: 100) { 129 | edges { 130 | node { 131 | projectItems(first: 100) { 132 | edges { 133 | node { 134 | id 135 | project { 136 | id 137 | } 138 | } 139 | } 140 | } 141 | id 142 | url 143 | number 144 | repository { 145 | name 146 | owner { 147 | login 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | }`; 156 | 157 | const parameters = { 158 | owner: repoOwner, 159 | repo: repoName, 160 | issueNumber: issueNumber, 161 | headers 162 | }; 163 | 164 | const result = await octokit.graphql(query, parameters); 165 | 166 | return result.repository.issue.trackedIssues.edges; 167 | } 168 | 169 | async function addItemToProject(projectId, contentId) { 170 | const mutation = `mutation ($projectId: ID!, $contentId: ID!) { 171 | addProjectV2ItemById(input: {projectId: $projectId contentId: $contentId}) { 172 | item { 173 | id 174 | } 175 | } 176 | }`; 177 | 178 | const parameters = { 179 | projectId: projectId, 180 | contentId: contentId, 181 | headers 182 | }; 183 | 184 | const result = await octokit.graphql(mutation, parameters); 185 | 186 | return result.addProjectV2ItemById.item.id; 187 | } 188 | 189 | async function mutateFieldValue(projectId, itemId, fieldId, fieldValueId) { 190 | const mutation = `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { 191 | updateProjectV2ItemFieldValue( 192 | input: { 193 | projectId: $projectId 194 | itemId: $itemId 195 | fieldId: $fieldId 196 | value: { 197 | singleSelectOptionId: $optionId 198 | } 199 | } 200 | ) { 201 | projectV2Item { 202 | id 203 | } 204 | } 205 | }`; 206 | 207 | const parameters = { 208 | projectId: projectId, 209 | itemId: itemId, 210 | fieldId: fieldId, 211 | optionId: fieldValueId, 212 | headers 213 | }; 214 | 215 | await octokit.graphql(mutation, parameters); 216 | } 217 | 218 | (async function main() { 219 | // Get project items 220 | 221 | console.log(`Querying value for field "${FIELD_NAME}" of ${ISSUE_URL}`); 222 | const projectItems = await queryFieldValue(REPO_OWNER, REPO_NAME, ISSUE_NUMBER, FIELD_NAME); 223 | 224 | if (!projectItems) { 225 | console.log("Aborting because issue is not part of any projects"); 226 | return; 227 | } 228 | 229 | // Determine field value 230 | 231 | fieldValue = determineFieldValue(projectItems, PROJECT_ID, FIELD_ID); 232 | 233 | if (!fieldValue.name || !fieldValue.id) { 234 | console.log("Aborting because issue is not part of the correct project"); 235 | return; 236 | } 237 | 238 | console.log(`Determined field value "${fieldValue.name}" (${fieldValue.id})`); 239 | 240 | // Set field value on tracked issues 241 | 242 | await setFieldValueOnTrackedIssues(REPO_OWNER, REPO_NAME, ISSUE_URL, ISSUE_NUMBER, PROJECT_ID, FIELD_ID, FIELD_NAME, fieldValue); 243 | 244 | })(); 245 | -------------------------------------------------------------------------------- /.github/actions/octonode.d.ts: -------------------------------------------------------------------------------- 1 | declare module "octonode" { 2 | 3 | export function client(accessToken: string): Client 4 | 5 | export class Client { 6 | public getAsync(path: `/repos/${string}/labels`, params: { page: number, per_page: number }): Promise<[number, [Label]]> 7 | } 8 | 9 | export interface Label { 10 | name: string; 11 | description: string; 12 | color: string; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.github/actions/score-triaged-defects.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 New Vector Ltd. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 18 | // For running in an action 19 | import { Octokit } from "@octokit/action"; 20 | const octokit = new Octokit(); 21 | 22 | // For running locally 23 | // import { Octokit } from "@octokit/core"; 24 | // const octokit = new Octokit({ auth: process.env.GH_TOKEN }); 25 | 26 | 27 | // List all the defects with their GH project "Score" field 28 | async function listDefects(repoOwner, repoName, projectFieldName = "Score", label = "T-Defect") { 29 | const query = ` 30 | query ($repoOwner: String!, $repoName: String!, $label: String!, $projectFieldName: String!, $after: String) { 31 | repository(owner: $repoOwner, name: $repoName) { 32 | issues(labels: [$label], states: OPEN, first: 10, after: $after) { 33 | nodes { 34 | number 35 | title 36 | labels(first: 10) { 37 | nodes { 38 | name 39 | } 40 | } 41 | projectItems(first: 10) { 42 | nodes { 43 | id 44 | project { 45 | id 46 | } 47 | score: fieldValueByName(name: $projectFieldName) { 48 | ... on ProjectV2ItemFieldNumberValue { 49 | id 50 | number 51 | } 52 | } 53 | } 54 | } 55 | } 56 | pageInfo { 57 | endCursor 58 | hasNextPage 59 | } 60 | } 61 | } 62 | } 63 | `; 64 | 65 | var issues = []; 66 | var hasNextPage = true; 67 | var after = null; 68 | 69 | while (hasNextPage) { 70 | const parameters = { 71 | repoOwner, 72 | repoName, 73 | label, 74 | projectFieldName, 75 | after 76 | }; 77 | 78 | const result = await octokit.graphql(query, parameters); 79 | issues = issues.concat(result.repository.issues.nodes); 80 | hasNextPage = result.repository.issues.pageInfo.hasNextPage; 81 | after = result.repository.issues.pageInfo.endCursor; 82 | } 83 | 84 | return issues; 85 | } 86 | 87 | 88 | // Extract the score from the GraphQL response. 89 | // scoreItem is { id, project { id }, score: { id, number }} 90 | function getScoreItem(issue, projectId) { 91 | var scoreItem = 0; 92 | issue.projectItems.nodes.forEach(item => { 93 | if (item.project.id === projectId) { 94 | scoreItem = item; 95 | } 96 | }); 97 | 98 | if (scoreItem == null) { 99 | console.log("No score found for issue " + issue.number); 100 | } 101 | 102 | return scoreItem; 103 | } 104 | 105 | // Compute the score of a defect based on the labels as per: 106 | // https://github.com/element-hq/element-meta/wiki/triage-process#prioritisation 107 | function computeIssueScore(issue) { 108 | var severity = 0; 109 | var occurence = 0; 110 | issue.labels.nodes.forEach(label => { 111 | switch (String(label.name)) { 112 | case "O-Uncommon": 113 | occurence = 1; 114 | break; 115 | case "O-Occasional": 116 | occurence = 2; 117 | break; 118 | case "O-Frequent": 119 | occurence = 3; 120 | break; 121 | case "S-Tolerable": 122 | severity = 1; 123 | break; 124 | case "S-Minor": 125 | severity = 2; 126 | break; 127 | case "S-Major": 128 | severity = 3; 129 | break; 130 | case "S-Critical": 131 | severity = 4; 132 | break; 133 | default: 134 | break; 135 | } 136 | }); 137 | 138 | return severity * occurence; 139 | } 140 | 141 | // Update a score in the GH project 142 | async function setNewScore(scoreItem, projectFieldId, fieldValue) { 143 | const mutation = ` 144 | mutation($projectId: ID!, $itemId: ID!, $projectFieldId: ID!, $value: Float!) { 145 | updateProjectV2ItemFieldValue( 146 | input: { 147 | projectId: $projectId 148 | itemId: $itemId 149 | fieldId: $projectFieldId 150 | value: { 151 | number: $value 152 | } 153 | } 154 | ) { 155 | projectV2Item { 156 | id 157 | } 158 | } 159 | } 160 | `; 161 | 162 | const parameters = { 163 | projectId: scoreItem.project.id, 164 | itemId: scoreItem.id, 165 | projectFieldId: projectFieldId, 166 | value: fieldValue 167 | }; 168 | 169 | await octokit.graphql(mutation, parameters); 170 | } 171 | 172 | 173 | (async function main() { 174 | const repoOwner = process.env.REPO_OWNER; 175 | const repoName = process.env.REPO_NAME; 176 | const projectId = process.env.PROJECT_ID; 177 | const projectFieldId = process.env.PROJECT_FIELD_ID; 178 | const projectFieldName = process.env.PROJECT_FIELD_NAME; 179 | 180 | const issues = await listDefects(repoOwner, repoName, projectFieldName); 181 | console.log("Found " + issues.length + " T-Defect issues"); 182 | 183 | issues.filter(issue => { 184 | // Check if it is part of the GH project 185 | const ok = issue.projectItems.nodes.some(item => { return item.project.id === projectId; }); 186 | if (!ok) { 187 | console.log("Issue " + issue.number + " is not part of the project"); 188 | } 189 | return ok; 190 | }) 191 | .filter(issue => { 192 | // Check if it has the triaging labels, ie a label that starts with "S-" and a label that starts with "O-" 193 | const ok = issue.labels.nodes.some(label => { return label.name.startsWith("S-"); }) && issue.labels.nodes.some(label => { return label.name.startsWith("O-"); }); 194 | if (!ok) { 195 | console.log("Issue " + issue.number + " is not labeled correctly. Labels: " + issue.labels.nodes.map(label => label.name)); 196 | } 197 | return ok; 198 | }) 199 | .forEach(issue => { 200 | const scoreItem = getScoreItem(issue, projectId); 201 | 202 | // Ignore issues with a score manually set higher than 100. This is a way to fine control the priority of issues 203 | if (scoreItem.score && scoreItem.score.number >= 100) { 204 | return; 205 | } 206 | 207 | // Update the score if it is different 208 | var computedScore = computeIssueScore(issue); 209 | if (scoreItem.score == null || scoreItem.score.number != computedScore) { 210 | console.log(issue.number + " - " + " Updating score from " + (scoreItem.score ? scoreItem.score.number : "null") + " to " + computedScore + " - " + issue.title); 211 | 212 | setNewScore(scoreItem, projectFieldId, computedScore).catch(error => { 213 | console.error("Error updating score for issue " + issue.number + ": " + error); 214 | }); 215 | } 216 | }); 217 | 218 | })(); 219 | 220 | 221 | 222 | // The query to use in https://docs.github.com/en/graphql/overview/explorer to find ids for the GH project id and the Score field 223 | /* 224 | query($owner: String!, $repo: String!) { 225 | repository(owner: $owner, name: $repo) { 226 | issues(labels: ["T-Defect"], states: OPEN, first:3) { 227 | nodes { 228 | title 229 | projectItems(first: 2) { 230 | nodes { 231 | project { 232 | id 233 | score: field(name: "Score") { 234 | ... on ProjectV2Field { 235 | id 236 | } 237 | } 238 | } 239 | } 240 | } 241 | } 242 | } 243 | } 244 | } 245 | 246 | Variables 247 | {"owner": "element-hq","repo": "element-x-ios"} 248 | */ 249 | -------------------------------------------------------------------------------- /.github/actions/sync-labels.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 New Vector Ltd. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import fs from "fs"; 18 | import { Command } from "commander"; 19 | import githubLabelSync, { LabelDiff, LabelInfo } from "github-label-sync"; 20 | import octonode from "octonode"; 21 | import YAML from "yaml"; 22 | 23 | main(); 24 | 25 | async function main() { 26 | const accessToken = process.env.GITHUB_TOKEN!; 27 | const repo = process.env.GITHUB_REPOSITORY!; 28 | console.log(`Repository: ${repo}`); 29 | 30 | const cmd = new Command("sync-labels.ts") 31 | .option("--delete", "Removes labels that exist in the repository but are missing from all sources", false) 32 | .option("--dir <path>", "Working directory to run in") 33 | .option("--labels <path...>", "Label source as GitHub repository slug or path to local YAML file. Can be specified multiple times.") 34 | .option("--wet", "Write changes, *don't* run in dry mode", false) 35 | .parse(); 36 | const opts = cmd.opts(); 37 | console.log(`Options: ${JSON.stringify(opts)}`); 38 | 39 | if (opts.dir) { 40 | process.chdir(opts.dir); 41 | } 42 | 43 | try { 44 | const merged = await readAndMergeLabels(opts.labels, accessToken); 45 | await syncLabels(merged, repo, accessToken, opts.delete, opts.wet); 46 | } catch (e) { 47 | console.error("Error syncing labels", e); 48 | process.exit(1); 49 | } 50 | } 51 | 52 | async function readAndMergeLabels(srcs: string[], accessToken: string): Promise<Map<string, LabelInfo>> { 53 | const merged = new Map<string, LabelInfo>(); 54 | 55 | for (const src of srcs) { 56 | console.log(); 57 | console.log(`Loading labels from ${src}`); 58 | const labels = fs.existsSync(src) ? readLabelsFromFile(src) : await readLabelsFromRepo(src, accessToken); 59 | if (!labels) { 60 | continue; 61 | } 62 | 63 | for (const label of labels) { 64 | if (!label.name) { 65 | console.warn(`Ignoring invalid label without a name: ${label}`); 66 | continue; 67 | } 68 | if (merged.has(label.name)) { 69 | console.log(`Overriding label ${label.name}`); 70 | } else { 71 | console.log(`Adding label ${label.name}`); 72 | } 73 | merged.set(label.name, label); 74 | } 75 | } 76 | 77 | return merged; 78 | } 79 | 80 | function readLabelsFromFile(file: string): LabelInfo[] { 81 | const content = fs.readFileSync(file, "utf-8"); 82 | return YAML.parse(content) as LabelInfo[]; 83 | } 84 | 85 | async function readLabelsFromRepo(slug: string, accessToken: string): Promise<LabelInfo[]> { 86 | const client = octonode.client(accessToken); 87 | 88 | const labels: LabelInfo[] = []; 89 | const params = { page: 1, per_page: 100 }; 90 | 91 | while (true) { 92 | const [status, chunk] = await client.getAsync(`/repos/${slug}/labels`, params); 93 | if (status !== 200) { 94 | throw `Fetching labels failed: ${status}`; 95 | } 96 | 97 | for (const item of chunk) { 98 | labels.push({ 99 | name: item.name, 100 | description: item.description ?? "", 101 | color: item.color, 102 | }) 103 | } 104 | 105 | if (chunk.length !== params.per_page) { 106 | break; 107 | } 108 | 109 | params.page += 1; 110 | } 111 | 112 | return labels; 113 | } 114 | 115 | async function syncLabels(labels: Map<string, LabelInfo>, repo: string, accessToken: string, deleteAddedLabels: boolean, writeChanges: boolean) { 116 | console.log(); 117 | console.log("Syncing labels"); 118 | 119 | const diffs = await githubLabelSync({ 120 | accessToken, 121 | repo, 122 | labels: [...labels.values()], 123 | dryRun: !writeChanges, 124 | allowAddedLabels: !deleteAddedLabels 125 | }); 126 | 127 | if (!diffs.length) { 128 | console.log(`Label sync completed without changes`); 129 | return; 130 | } 131 | 132 | logDiffs(diffs, repo); 133 | } 134 | 135 | function logDiffs(diffs: LabelDiff[], repo: string) { 136 | const missing = diffs.filter(d => d.type == "missing"); 137 | if (missing.length > 0) { 138 | console.log(); 139 | console.log(`The following labels are missing in ${repo} but exist in one of the sources. They will be created.`) 140 | for (const diff of missing) { 141 | console.log(`- name: "${diff.expected?.name}"`); 142 | } 143 | } 144 | 145 | const changed = diffs.filter(d => d.type == "changed"); 146 | if (changed.length > 0) { 147 | console.log() 148 | console.log(`The following labels exist in ${repo} but differ in one of the sources. They will be modified.`) 149 | for (const diff of changed) { 150 | console.log(`- name: "${diff.actual?.name}"`); 151 | if (diff.expected?.description != diff.actual?.description) { 152 | console.log(` description (actual): ${diff.actual?.description}`); 153 | console.log(` description (expected): ${diff.expected?.description}`); 154 | } 155 | if (diff.expected?.color != diff.actual?.color) { 156 | console.log(` color (actual): ${diff.actual?.color}`); 157 | console.log(` color (expected): ${diff.expected?.color}`); 158 | } 159 | } 160 | } 161 | 162 | const added = diffs.filter(d => d.type == "added"); 163 | if (added.length > 0) { 164 | console.log(); 165 | console.log(`The following labels exist in ${repo} but are missing in all sources. They will be deleted.`) 166 | for (const diff of added) { 167 | console.log(`- name: "${diff.actual?.name}"`); 168 | if (diff.actual?.description) { 169 | console.log(` description: "${diff.actual?.description}"`); 170 | } 171 | if (diff.actual?.color.length) { 172 | console.log(` color: "${diff.actual?.color}"`); 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/workflows/score-triaged-defects.yml: -------------------------------------------------------------------------------- 1 | name: Score triaged defects 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 6,12,18 * * 1-5" 7 | 8 | jobs: 9 | score-triaged-issue-ios: 10 | name: Score iOS triaged defects 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | - run: npm install @octokit/action 18 | - run: node .github/actions/score-triaged-defects.mjs 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} 21 | REPO_OWNER: element-hq 22 | REPO_NAME: element-x-ios 23 | PROJECT_ID: PVT_kwDOAM0swc4ABTXY 24 | PROJECT_FIELD_ID: PVTF_lADOAM0swc4ABTXYzgQaNqw 25 | 26 | score-triaged-issue-android: 27 | name: Score Android triaged defects 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | with: 33 | node-version: 20 34 | - run: npm install @octokit/action 35 | - run: node .github/actions/score-triaged-defects.mjs 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} 38 | REPO_OWNER: element-hq 39 | REPO_NAME: element-x-android 40 | PROJECT_ID: PVT_kwDOAM0swc4ABTXY 41 | PROJECT_FIELD_ID: PVTF_lADOAM0swc4ABTXYzgQaNqw 42 | 43 | score-triaged-issue-web: 44 | name: Score Web triaged defects 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version: 20 51 | - run: npm install @octokit/action 52 | - run: node .github/actions/score-triaged-defects.mjs 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} 55 | REPO_OWNER: element-hq 56 | REPO_NAME: element-web 57 | PROJECT_ID: PVT_kwDOAM0swc4AcrZs 58 | PROJECT_FIELD_ID: PVTF_lADOAM0swc4AcrZszgSjyX4 59 | -------------------------------------------------------------------------------- /.github/workflows/static_analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static Analysis 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: [develop] 6 | jobs: 7 | workflow_lint: 8 | name: "Workflow Lint" 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - uses: actions/setup-node@v4 14 | with: 15 | cache: "yarn" 16 | 17 | - name: Install Deps 18 | run: "yarn install --frozen-lockfile" 19 | 20 | - name: Run Linter 21 | run: "yarn lint:workflows" 22 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync labels 2 | on: 3 | workflow_dispatch: {} 4 | # Disabled for now while we're testing 5 | # push: 6 | # branches: 7 | # - develop 8 | # paths: 9 | # - .github/labels.yml 10 | workflow_call: 11 | inputs: 12 | LABELS: 13 | description: | 14 | List of label sources, one per line. Sources can either be GitHub repository 15 | slugs or paths to local YAML files in the format specified in 16 | https://github.com/Financial-Times/github-label-sync#label-config-file". 17 | The sources will merge/override each other in the order they were specified in. 18 | type: string 19 | default: | 20 | .github/labels.yml 21 | required: true 22 | DELETE: 23 | description: | 24 | Remove labels that exist in the repository but are missing from all sources 25 | type: boolean 26 | default: false 27 | WET: 28 | description: | 29 | Write changes, *don't* run in dry mode 30 | type: boolean 31 | default: false 32 | secrets: 33 | ELEMENT_BOT_TOKEN: 34 | description: Sufficiently privileged GitHub token 35 | required: true 36 | jobs: 37 | sync: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Check out calling repository 41 | uses: actions/checkout@v4 42 | if: github.repository != 'element-hq/element-meta' 43 | - name: Check out element-hq/element-meta 44 | uses: actions/checkout@v4 45 | with: 46 | path: element-meta 47 | repository: element-hq/element-meta 48 | - name: "Set up Node.js" 49 | uses: actions/setup-node@v4 50 | with: 51 | cache: yarn 52 | cache-dependency-path: element-meta/yarn.lock 53 | - name: Install Deps 54 | run: "yarn install --frozen-lockfile" 55 | working-directory: element-meta 56 | - name: "Sync labels" 57 | run: | 58 | yarn sync-labels $(echo -e "${{ inputs.LABELS }}" | xargs -r printf -- '--labels \"%s\"\n' | xargs echo) \ 59 | ${{ github.repository != 'element-hq/element-meta' && '--dir ..' }} \ 60 | ${{ inputs.DELETE && '--delete' }} \ 61 | ${{ inputs.WET && '--wet' }} 62 | working-directory: element-meta 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} 65 | GITHUB_REPOSITORY: ${{ github.repository }} 66 | -------------------------------------------------------------------------------- /.github/workflows/triage-move-labelled.yml: -------------------------------------------------------------------------------- 1 | name: Move labelled issues to correct boards and columns 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | apply_Z-Labs_label: 9 | name: Add Z-Labs label for features behind labs flags 10 | runs-on: ubuntu-latest 11 | if: > 12 | contains(github.event.issue.labels.*.name, 'A-Maths') || 13 | contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || 14 | contains(github.event.issue.labels.*.name, 'A-Threads') || 15 | contains(github.event.issue.labels.*.name, 'A-Polls') || 16 | contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || 17 | contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') || 18 | contains(github.event.issue.labels.*.name, 'Z-IA') || 19 | contains(github.event.issue.labels.*.name, 'A-Themes-Custom') || 20 | contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || 21 | contains(github.event.issue.labels.*.name, 'A-Tags') 22 | steps: 23 | - uses: actions/github-script@v5 24 | with: 25 | script: | 26 | github.rest.issues.addLabels({ 27 | issue_number: context.issue.number, 28 | owner: context.repo.owner, 29 | repo: context.repo.repo, 30 | labels: ['Z-Labs'] 31 | }) 32 | 33 | add_design_issues_to_project: 34 | name: X-Needs-Design to Design project board 35 | runs-on: ubuntu-latest 36 | if: > 37 | contains(github.event.issue.labels.*.name, 'X-Needs-Design') && 38 | (contains(github.event.issue.labels.*.name, 'S-Critical') && 39 | (contains(github.event.issue.labels.*.name, 'O-Frequent') || 40 | contains(github.event.issue.labels.*.name, 'O-Occasional')) || 41 | contains(github.event.issue.labels.*.name, 'S-Major') && 42 | contains(github.event.issue.labels.*.name, 'O-Frequent') || 43 | contains(github.event.issue.labels.*.name, 'A11y')) 44 | steps: 45 | - uses: actions/add-to-project@main 46 | with: 47 | project-url: https://github.com/orgs/element-hq/projects/18 48 | github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} 49 | 50 | add_product_issues: 51 | name: X-Needs-Product to Design project board 52 | runs-on: ubuntu-latest 53 | if: > 54 | contains(github.event.issue.labels.*.name, 'X-Needs-Product') 55 | steps: 56 | - uses: actions/add-to-project@main 57 | with: 58 | project-url: https://github.com/orgs/element-hq/projects/28 59 | github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} 60 | 61 | ex_platform: 62 | name: Add labelled issues to EX platform project 63 | runs-on: ubuntu-latest 64 | if: > 65 | contains(github.event.issue.labels.*.name, 'Team: Element X Platform') 66 | steps: 67 | - uses: actions/add-to-project@main 68 | with: 69 | project-url: https://github.com/orgs/element-hq/projects/43 70 | github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} 71 | 72 | voip: 73 | name: Add labelled issues to VoIP project board 74 | runs-on: ubuntu-latest 75 | if: > 76 | contains(github.event.issue.labels.*.name, 'Team: VoIP') 77 | steps: 78 | - uses: actions/add-to-project@main 79 | with: 80 | project-url: https://github.com/orgs/element-hq/projects/41 81 | github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} 82 | 83 | qa: 84 | name: Add labelled issues to QA project 85 | runs-on: ubuntu-latest 86 | if: > 87 | contains(github.event.issue.labels.*.name, 'Team: QA') || 88 | contains(github.event.issue.labels.*.name, 'X-Needs-Signoff') 89 | steps: 90 | - uses: actions/add-to-project@main 91 | with: 92 | project-url: https://github.com/orgs/element-hq/projects/69 93 | github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} 94 | 95 | signoff: 96 | name: Add labelled issues to signoff project 97 | runs-on: ubuntu-latest 98 | if: > 99 | contains(github.event.issue.labels.*.name, 'X-Needs-Signoff') 100 | steps: 101 | - uses: actions/add-to-project@main 102 | with: 103 | project-url: https://github.com/orgs/element-hq/projects/89 104 | github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} 105 | -------------------------------------------------------------------------------- /.github/workflows/triage-move-unlabelled.yml: -------------------------------------------------------------------------------- 1 | name: Move unlabelled from needs info columns to triaged 2 | 3 | on: 4 | issues: 5 | types: [unlabeled] 6 | 7 | jobs: 8 | remove_Z-Labs_label: 9 | name: Remove Z-Labs label when features behind labs flags are removed 10 | runs-on: ubuntu-latest 11 | if: > 12 | !(contains(github.event.issue.labels.*.name, 'A-Maths') || 13 | contains(github.event.issue.labels.*.name, 'A-Message-Pinning') || 14 | contains(github.event.issue.labels.*.name, 'A-Threads') || 15 | contains(github.event.issue.labels.*.name, 'A-Polls') || 16 | contains(github.event.issue.labels.*.name, 'A-Location-Sharing') || 17 | contains(github.event.issue.labels.*.name, 'A-Message-Bubbles') || 18 | contains(github.event.issue.labels.*.name, 'Z-IA') || 19 | contains(github.event.issue.labels.*.name, 'A-Themes-Custom') || 20 | contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') || 21 | contains(github.event.issue.labels.*.name, 'A-Tags')) && 22 | contains(github.event.issue.labels.*.name, 'Z-Labs') 23 | steps: 24 | - uses: actions/github-script@v5 25 | with: 26 | script: | 27 | github.rest.issues.removeLabel({ 28 | issue_number: context.issue.number, 29 | owner: context.repo.owner, 30 | repo: context.repo.repo, 31 | name: ['Z-Labs'] 32 | }) 33 | -------------------------------------------------------------------------------- /.github/workflows/x-plorers-epic-forwarding.yml: -------------------------------------------------------------------------------- 1 | name: Forward epic field value into tracked issues 2 | 3 | on: 4 | issues: 5 | types: [edited] 6 | 7 | jobs: 8 | forward_epic_field_value: 9 | name: Forward epic field value into tracked issues 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: 18 16 | - run: npm install @octokit/action 17 | - run: node .github/actions/forward-project-field.mjs 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} 20 | REPO_OWNER: ${{ github.event.repository.owner.login }} 21 | REPO_NAME: ${{ github.event.repository.name }} 22 | ISSUE_URL: ${{ github.event.issue.html_url }} 23 | ISSUE_NUMBER: ${{ github.event.issue.number }} 24 | PROJECT_ID: "PVT_kwDOAM0swc4ALoFY" 25 | FIELD_ID: "PVTSSF_lADOAM0swc4ALoFYzgJAimw" 26 | FIELD_NAME: Epic 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /*.log 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Element meta 2 | 3 | This is the home of shared Element app documentation and artefacts for the element [web, desktop](https://github.com/vector-im/element-web), [Android](https://github.com/vector-im/element-android) and [iOS](https://github.com/vector-im/element-ios) apps. 4 | 5 | Each project will link to the [wiki](https://github.com/vector-im/element-meta/wiki) directly to reference processes that it has adopted. 6 | 7 | ## Setting up label sync in another repository 8 | 9 | This repository includes a reusable workflow for synchronising labels across repositories and from YAML files. To set up label synchronisation in another repository, go to that repository and create an empty file `.github/labels.yml` as well as a new workflow `.github/workflows/sync-labels.yml`. 10 | 11 | ``` 12 | name: Sync labels 13 | on: 14 | workflow_dispatch: {} 15 | schedule: 16 | - cron: "0 2 * * *" # 2am every day 17 | push: 18 | branches: 19 | - develop 20 | paths: 21 | - .github/labels.yml 22 | jobs: 23 | sync-labels: 24 | uses: vector-im/element-meta/.github/workflows/sync-labels.yml@develop 25 | with: 26 | LABELS: | 27 | vector-im/element-meta 28 | .github/labels.yml 29 | DELETE: true 30 | WET: false 31 | secrets: 32 | ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} 33 | ``` 34 | 35 | This will sync labels from vector-im/element-meta as well as the local `labels.yml` file. Note that `WET: false` ensures that the workflow runs in dry mode without actually changing any labels. 36 | 37 | Manually execute the workflow once. The workflow run's logs will include a summary of the label changes that would have been applied. Take particular note of the section that lists additional labels. 38 | 39 | ``` 40 | The following labels exist in matrix-org/matrix-react-sdk-module-api but are missing in all sources. They will be deleted. 41 | - name: "A-Timesheet-1" 42 | description: "Log any time spent on this into the A-Timesheet-1 project" 43 | color: "5319E7" 44 | - name: "bug" 45 | description: "Something isn't working" 46 | color: "d73a4a" 47 | - name: "documentation" 48 | ... 49 | ``` 50 | 51 | If you want to retain these labels, either copy them to `labels.yml` or set `DELETE: false`. 52 | 53 | Afterwards set `WET: true` in the workflow and execute it. You may hit GitHub's rate limits on the initial run. 54 | 55 | ``` 56 | Syncing labels 57 | [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Error: You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID 9892:05D0:25DA3A:4F0839:6564EE53.".] { 58 | code: 'ERR_UNHANDLED_REJECTION' 59 | } 60 | ``` 61 | 62 | If so, just do as the message says and execute the workflow again after a few minutes to let it apply the leftover changes. From here on labels should be kept in sync automatically. 63 | -------------------------------------------------------------------------------- /docs/FTUE.md: -------------------------------------------------------------------------------- 1 | # First Time User Experience 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | March 15, 2023 | 6 | 7 | This document aims to make first time user experience as simple as possible. FTUE refers to either the first registration/login of a user or to linking additional devices. All FTUE scenarios need to be covered within the initial setup of Element clients. 8 | 9 | **Table of contents** 10 | 11 | - [General guidelines](#general-guidelines) 12 | - [Use cases / scenarios](#use-cases--scenarios) 13 | * [1. A new employee of an organization is onboarded to Element](#1-a-new-employee-of-an-organization-is-onboarded-to-element) 14 | * [2. An existing regular user wants to onboard another regular user](#2-an-existing-regular-user-wants-to-onboard-another-regular-user) 15 | * [3. A regular user just downloads the app or opens the web app](#3-a-regular-user-just-downloads-the-app-or-opens-the-web-app) 16 | - [Flows](#flows) 17 | * [0. MDM (not in scope)](#0-mdm-not-in-scope) 18 | * [1. Invitation via link](#1-invitation-via-link) 19 | + [1a) Enterprise / organizational invitation](#1a-enterprise--organizational-invitation) 20 | + [1b) Regular user invitation](#1b-regular-user-invitation) 21 | * [2. Manual login / registration](#2-manual-login--registration) 22 | - [Related solution concepts](#related-solution-concepts) 23 | * [Design for MAS-served components as part of OIDC flow](#design-for-mas-served-components-as-part-of-oidc-flow) 24 | + [Login](#login) 25 | + [Registration](#registration) 26 | + [Branding](#branding) 27 | * [Additional user attributes](#additional-user-attributes) 28 | * [How do you want others to find you?](#how-do-you-want-others-to-find-you) 29 | * [Homeserver settings to consider](#homeserver-settings-to-consider) 30 | 31 | **Figma prototypes** 32 | 33 | - [Initial prototyping](https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?node-id=1-1496&t=fGZ56cXrNTbvyRyD-0) 34 | - [Compound Web](https://www.figma.com/file/rTaQE2nIUSLav4Tg3nozq7/Compound-Web-Components?type=design&node-id=3929-144022&mode=design&t=XrmkcI0ZA55ayrh1-4) 35 | - [Compound iOS](https://www.figma.com/file/jvZ7TIyd7V9Vn8sIDB9vMd/Compound-iOS-Templates?type=design&node-id=1-3720&mode=design&t=RhQEVkskYGTdfIJW-4) 36 | - [QR code login](https://www.figma.com/file/EbUDdINXYZqeB7TisFJhoN/OIDC-Sign-in-with-QR-flow?type=design&node-id=110-33582&mode=design&t=iPP7N9GrifBjdbdx-0) 37 | 38 | ## General guidelines 39 | 40 | 1. The user onboarding process should be as **simple** and **require as few steps as possible** so that users can start using the app and reach their goals quickly, preventing churn. 41 | 2. Many end-users do not understand or know about **federation and other technical topics**. Therefore the app should **not bother the end-user with it** but make them reach their goals at least as easily as with a centralized service. It must not be necessary to educate users about technical backgrounds in order to allow them to use the app. 42 | 3. **Technical wording** should be avoided wherever possible. 43 | 4. In order to simplify FTUE, the app should prominently advertise **invitation-based onboarding flows** that improve UX by providing information the user might not know or could be confusing (e.g., homeserver choice). See [Use cases / scenarios](#use-cases--scenarios). 44 | 5. The invitation flows should automatically **assist the user to reach their goals**. An invitation from a regular user should therefore end with the new user having a conversation with the inviting user. An invitation to a particular room should end with the new user joining that room. 45 | 6. Users should never end up having **unverified devices** as these are a threat to integrity/security and the user needs to follow a couple of steps to recover from this situation. Therefore FTUE flows should ensure that additionally linked devices will be verified. 46 | 7. **User discovery** is not trivial in a federated environment. The app should therefore allow the user to make a conscious decision on which identifiers they want to share for other users to find them by. This way the user has choice over which data to share with the provider and simultaneously gets awareness on how others can find them. 47 | 8. Homeserver deployments will move fully to **native OIDC**. This needs to be respected in the FTUE flows. 48 | 49 | ## Use cases / scenarios 50 | 51 | ### 1. A new employee of an organization is onboarded to Element 52 | 53 | #### Conditions 54 | - User account exists in IDM 55 | - Homeserver is known 56 | - User attributes can be obtained from IDM and should not be changed by users in most cases 57 | 58 | ### 2. An existing regular user wants to onboard another regular person 59 | 60 | #### Conditions 61 | - User account does not exist 62 | - Existing user can make a homeserver proposal 63 | - Same homeserver 64 | - Propose another one ("Element Connect") 65 | - User attributes are unknown 66 | 67 | ### 3. A regular person just downloads the app or opens the web app 68 | 69 | #### Conditions 70 | - User account is unknown 71 | - Homeserver preference is unknown 72 | - User attributes are unknown 73 | 74 | ## Flows 75 | 76 | ### 0. MDM (not in scope) 77 | 78 | ### 1. Invitation via link 79 | 80 | ### 1a) Enterprise / organizational invitation 81 | 82 | 1. User receives an invite link and clicks it 83 | 2. Browser opens and loads Element Web or the mobile app is opened (platform-dependent) 84 | 1. User doesn't have the app => Open app store and allow to install, then launch it 85 | 2. User has the app => Launch it 86 | 3. [homeserver and/or other information are imported via clipboard in the background ] 87 | 4. Welcome screen 88 | 5. Open web view overlay for login (or redirect to IdP on Web/Desktop; OIDC flow; requires consent on iOS; see [Login](#login) for more details) 89 | 6. User authenticates, web view closes (or redirect back to Web/Desktop app), user is back in the app 90 | 7. [user is logged in] 91 | 8. [ask server if single device or additional device] Secure Messaging 92 | 1. If no encryption or secure backup enabled => skip this step or set up secure messaging if first login 93 | 2. Single device (and not first login) => Ask for recovery method to obtain 4S and offer to reset encryption keys => can't be skipped 94 | 3. Additional device => Ask for cross-signing with another device (QR code or 6-digit code comparison) or recovery method and offer to reset encryption keys => can't be skipped 95 | 9. [message history and key backup are fetched from server, device is cross-signed (if applicable)] 96 | 10. [user attributes are pulled from the server, if possible] 97 | 11. [only on first login] How do you want others to find you? (which user identifiers to associate with MXID and upload to identity server; potentially ask for consent / accept T&Cs; see [How do you want others to find you?](#how-do-you-want-others-to-find-you) for more details) 98 | 12. Ask to allow notifications 99 | 13. Ask for consent to analytics 100 | 14. [only on first login] User account summary (your name, avatar, MXID, etc.) 101 | 15. Element is set up, user sees their 'All chats' list 102 | 16. User gets hints on how to get started (start a conversation, join a public room, etc.) 103 | 104 | 105 | ### 1b) Regular user invitation 106 | 107 | 1. User receives an invite link and clicks it 108 | 2. Browser opens and loads Element Web or the mobile app is opened (platform-dependent) 109 | 1. User doesn't have the app => Open app store and allow to install, then launch it 110 | 2. User has the app => Launch it 111 | 3. [homeserver, inviting user MXID and/or other information are imported via clipboard in the background ] 112 | 4. Welcome screen 113 | 5. Simplified homeserver choice ("You are about to register on homeserver.tld"; continue/change) 114 | 6. Open web view overlay for registration (or redirect to IDM registration on Web/Desktop; OIDC flow; requires consent on iOS; see [Registration](#registration) for more details) 115 | 7. User creates account (including optional additional attributes; see [Additional user attributes](#additional-user-attributes) for more details) 116 | 8. Web view closes (or redirect back to Web/Desktop app), user is back in the app 117 | 9. [user is logged in] 118 | 10. [only on first login] How do you want others to find you? (which user identifiers to associate with MXID and upload to identity server; potentially ask for consent / accept T&Cs; see [How do you want others to find you?](#how-do-you-want-others-to-find-you) for more details) 119 | 11. Ask to allow notifications 120 | 12. Ask for consent to analytics 121 | 13. User account summary (your name, avatar, MXID?, etc.) 122 | 14. Element is set up, user sees their 'All chats' list 123 | 15. User gets hints on how to get started (start a conversation, join a public room, etc.) 124 | 16. A DM room with the inviting user (or a join for the room/space invitation) is automatically set up 125 | 126 | ### 2. Manual login / registration 127 | 128 | 1. Welcome screen 129 | 2. Let's get you set up (options) 130 | 1. Log in with another device (highlighted prominently; A) 131 | 2. Log in manually (email / username; B) 132 | 3. Register new account (C) 133 | 3. Different flows depending on user choice 134 | 135 | **A) Log in with another device (QR code flow)** 136 | 137 | 1. ‘Scan QR code’ view is shown with camera view and advice => "open Element on another logged-in device and click ‘Link additional device’"; QR code is shown 138 | 2. User scans QR code 139 | 3. [user/homeserver information and recovery key are imported in the background] 140 | 4. [user is logged in] 141 | 5. [message history and key backup are fetched from server, device is cross-signed] 142 | 6. Ask to allow notifications 143 | 7. Ask for consent to analytics 144 | 8. Element is fully set up, user sees their 'All chats' list 145 | 146 | **B) Log in manually (email / username)** 147 | 148 | 1. Simplified homeserver choice ("You are about to sign in to your account on matrix.org"; continue/change) 149 | 2. Open web view overlay for login (or redirect to IdP on Web/Desktop; OIDC flow; requires consent on iOS; see [Login](#login) for more details) 150 | 3. User authenticates, web view closes (or redirect back to Web/Desktop app), user is back in the app 151 | 4. [user is logged in] 152 | 5. [ask server if single device or additional device] Secure Messaging 153 | 1. If no encryption or secure backup enabled => skip this step or set up secure messaging if first login 154 | 2. Single device => Ask for recovery method to obtain 4S and offer to reset encryption keys => can't be skipped 155 | 3. Additional device => Ask for cross-signing with another device (QR code or 6-digit code comparison) or recovery method and offer to reset encryption keys => can't be skipped 156 | 6. [message history and key backup are fetched from server, device is cross-signed (if applicable)] 157 | 7. [only on first login] How do you want others to find you? (which user identifiers to associate with MXID and upload to identity server; potentially ask for consent / accept T&Cs; see [How do you want others to find you?](#how-do-you-want-others-to-find-you) for more details) 158 | 8. Ask to allow notifications 159 | 9. Ask for consent to analytics 160 | 10. [only on first login] User account summary (your name, avatar, MXID?, etc.) 161 | 11. Element is fully set up, user sees their 'All chats' list 162 | 163 | **C) Register new account** 164 | 165 | 1. Simplified homeserver choice ("You are about to register on matrix.org"; continue/change) 166 | 2. Open web view overlay for registration (or redirect to IDM registration on Web/Desktop; OIDC flow; requires consent on iOS; see [Registration](#registration) for more details) 167 | 3. User creates account (including optional additional attributes; see [Additional user attributes](#additional-user-attributes) for more details) 168 | 4. Web view closes (or redirect back to Web/Desktop app), user is back in the app 169 | 5. [user is logged in] 170 | 6. How do you want others to find you? (which user identifiers to associate with MXID and upload to identity server; potentially ask for consent / accept T&Cs; see [How do you want others to find you?](#how-do-you-want-others-to-find-you) for more details) 171 | 7. Ask to allow notifications 172 | 8. Ask for consent to analytics 173 | 9. User account summary (your name, avatar, MXID?, etc.) 174 | 10. Element is fully set up, user sees their (empty) 'All chats' list 175 | 11. User gets hints on how to get started (start a conversation, join a public room, etc.) 176 | 177 | ## Related solution concepts 178 | 179 | ### Design for MAS-served components as part of OIDC flow 180 | #### Login 181 | 182 | For all login flows, the first entry point for a user is a web page served by MAS. There are different scenarios depending on the type of deployment: 183 | 184 | - Using **integrated MAS-based login provider** (IdP) => users authenticate directly on the web page served by MAS 185 | - Providing a **single upstream login provider** (IdP; e.g., Google) => MAS will ask for the user's mail address to determine the right login provider automatically and will redirect accordingly. If there is no internal user directory, MAS will transparently redirect the user to the upstream login provider to authenticate there (most common case for this scenario). 186 | - Providing **multiple upstream login providers** (IdP; e.g., Google and Keycloak) => MAS will ask for the user's mail address to determine the right login provider automatically and will redirect accordingly. Additionally, MAS will provide a list of login providers and redirects depending on user choice to the respective upstream login provider to authenticate there. 187 | 188 | As part of the flows involving upstream login providers, more sophisticated authentication security measures like 2FA, MFA, Brute-force protection, etc., can be employed, depending on the capabilities and configuration of the upstream login provider. 189 | 190 | #### Registration 191 | 192 | The options for registering a new user account depend on the respective user backend and the homeserver configuration. 193 | 194 | - For a **MAS-backed** deployment (only using the internal user directory), the user creates their account directly on a web page served by MAS. 195 | - UserID 196 | - Password 197 | - E-Mail 198 | - E-Mail verification 199 | - Optional additional user attributes (see [Additional user attributes](#additional-user-attributes) for more details) 200 | - Captcha (configurable) 201 | - Accept T&Cs (configurable) 202 | - Consent to share account data with the client 203 | 204 | - For a deployment **backed by LDAP or another external user backend**, we don't have direct access to account creation. We can provide a configurable link to a web page served by the external user backend which allows account creation. 205 | - The homeserver can be configured to disallow registration. In this case Element should inform the user after the homeserver choice. 206 | 207 | #### Branding 208 | 209 | **Client-based branding** 210 | 211 | MAS should offer different branding capabilities based on the branding of the respective client connecting to it. This capability is part of the OIDC specification and will be used by different clients to give users identification and recognition during login/registration. This mainly refers to the respective client logo. 212 | 213 | **Server-side branding** 214 | 215 | There will be means to control the overall branding of MAS for different types of deployments (e.g., Matrix branding, Element branding, custom branding). 216 | This refers to logo and colors that can be changed by server-side configuration. 217 | 218 | ### Additional user attributes 219 | 220 | - Display name 221 | - Mail address 222 | - Avatar 223 | - Phone number 224 | - etc. 225 | 226 | The availability of additional user attributes depend on the deployment scenario and/or whether the user has supplied them. When asking for optional user attributes, the app should clarify what they can be used for. 227 | 228 | ### How do you want others to find you? 229 | 230 | We can make a difference by giving the user choice over which identifiers they want to associate with their MXID to allow others to find them by. **For enterprise use cases** there should be a way to pre-configure/enforce this so that the user does not have the choice and does not see the screen during FTUE. 231 | 232 | - Name 233 | - Mail address 234 | - Phone number 235 | - etc. 236 | 237 | As part of this process they might also need to accept T&C's for identity servers. 238 | 239 | The identifiers to choose have to be available either by obtaining them automatically from IDM or by the user supplying them in a prior step. If the user didn't supply certain identifiers, those will be listed but disabled. The user can later supply them in their settings and 'manage account' views, respectively. Furthermore, the user can change the available identifiers for user discovery at any point in time. 240 | 241 | ### Homeserver settings to consider 242 | - User registration enabled/disabled 243 | - Restrict user invitations to administrators 244 | - Allow/disallow users to change user attributes 245 | - Force user attribute sharing for user discovery 246 | - Link to external user management registration (see [Registration](#registration)) 247 | - Link to external 'manage account' view 248 | - MAS registration options (T&Cs, privacy policy, captcha, etc.) 249 | -------------------------------------------------------------------------------- /docs/auth/enhanced_idm_integration.md: -------------------------------------------------------------------------------- 1 | # Enhanced IDM integration 2 | 3 | This document aims to describe the product specification of the 'Enhanced IDM integration'. This commercial module should deliver advanced IDM integration functionalities targeted for larger organizations which enrich the basic FOSS feature set for user and identity management and make administration of larger deployments easier, more cost-efficient and user-friendly. 4 | 5 | | Status | Last updated | 6 | |--|--| 7 | | Draft | March 8, 2023 | 8 | 9 | ## Story, context & use cases 10 | 11 | 1. As an administrator I want to manage users centrally in the IDM tool(s) of choice, for that I can control them and impose policies independent of the application. 12 | 2. As an organization I want that users can easily find each other via certain, known and usable identifiers and start communicating. As I have all these identifiers available in the IDM, I want applications to pick those up automatically to reduce maintenance cost and to prevent human error. 13 | 3. As an organization I want to make user onboarding and deboarding an automated process for that user adoption and satisfaction is high and for that the organization's policies are respected. 14 | 15 | IDM is defined as the customer's user directory and/or identity management system. These can be one system or a combination of systems (LDAP / IdP / SCIM / etc.). 16 | 17 | ## Prerequisites 18 | 19 | - Pull additional (arbitrary) user attributes from IDM with dynamic mappings (user state, avatar, phone number, mail address etc.) 20 | 21 | ## Features 22 | 23 | - Regularly sync configured/mapped user attributes from IDM 24 | - Synapse (legacy) user DB 25 | - with MAS DB (Matrix Authentication Service) 26 | - with Identity Server DB (Sydent) 27 | - IDM-backed user lifecycle management 28 | - User pre-provisioning 29 | - Automatically populate users in Synapse and Identity Servers (if applicable) once they become available in IDM 30 | - Allow to find and contact new users before they have logged-in for the first time (might require dehydrated devices features for encryption) 31 | - Automatically generate and send invitation links to new users to facilitate onboarding on the clients 32 | - User de-provisioning 33 | - Automatically disable user accounts if their state (e.g., disable/delete/remove from group) changes in IDM (block login or read-only access) 34 | - Configurable grace period before login gets blocked or the account is automatically deleted 35 | 36 | ## Side requirements 37 | 38 | - The 'Enhanced IDM integration' should be delivered as a separate, commercial module. 39 | - The 'Enhanced IDM integration' should be deployable together with (or on top of) MAS which delivers the basic IDM connectivity. 40 | - Customers should only configure their IDM settings once and in one location. This should be used by advanced IDM components like 'Group Sync' or the 'Enhanced IDM integration'. 41 | - The attribute sync interval should be configurable to cover different requirements towards performance and currency. 42 | 43 | ## Delivery models? 44 | - on-premises 45 | - cloud-hosted 46 | - cloud-hosted + VPN link to an on-prem IDM 47 | -------------------------------------------------------------------------------- /docs/chat_effects.md: -------------------------------------------------------------------------------- 1 | # Chat Effects 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | March 2, 2022 | 6 | 7 | <hr /> 8 | 9 | ## Objective 10 | 11 | Chat effects provide a delightful experience to the user. They add personality and flair to the timeline. 12 | 13 | ### Complication 14 | 15 | We are not currently looking to add any more chat effects to Element. 16 | Too many chat effects, and too many triggers could cause users to turn chat effects off. If timeline take-overs happen too often we may frustrate our users instead of delight them. This is likely to occur in large rooms but may also happen in DMs of one or two people. 17 | 18 | Backslash commands for chat effects are handy as these are known by power users and less common. The use of Emojis as triggers lowers the barrier to usage, and increases the chances of users happening across this delight feature by accident but the 'shadow' of that is it may occur too often - or on emoji's that users send for other reasons. 19 | 20 | ## What are chat effects? 21 | 22 | Chat effects are full timeline take-overs in the Element clients. Each chat effect has 2 associated triggers; a backslash command, and an emoji. 23 | 24 | To turn chat effects on: 25 | - Head to Settings > Preferences 26 | - In the Timeline section find: "Show chat effects (animations when receiving e.g. confetti)" 27 | - Ensure the toggle is "on" 28 | 29 | 30 | ### How do I access chat effects? 31 | 32 | | Command | Emoji | Effect | Platform 33 | |---|---|---|---| 34 | | /snowfall | :snowflake: | light-grey balls fall slowly from the top of the screen to the bottom | Web & Android (backslash only) 35 | | /rainfall | :raincloud: | blue lines fall from the top of the screen to the bottom, looking like rain | Web only 36 | | /confetti | :tada: | multi-colour squares fall slowly across the screen, from top to bottom | Web & Android 37 | | /spaceinvaders | :space_invader: | the space invader emoji falls from the top of the screen to the bottom | Web only 38 | | /heart | :gift_heart: | love hearts float across the screen | Web only 39 | | /fireworks | :fireworks: | fireworks burst across the screen | Web only 40 | 41 | ## The future of chat effects 42 | 43 | | Now | Next | Future | 44 | |--|--|--| 45 | | - We are no longer accepting new chat effect contributions. <br /> - Our list of 6 is plenty and we run the risk of having so many we frustrate users with unexpected timeline takeovers. | - We need to consider the animation over platform. This is a delightful experience and brings fun and joy to our apps, but how does it translate cross-platform? Can we animate the emoji rather than the timeline? This may be a better experience for those users on smaller screens or devices. <br /> - We do not have any chat effects on iOS <br /> - We also need to consider chat effects in Threads. | - How might we offer both emoji animation and full-screen takeover? Apple do this well; It's always in the senders hands. Emojis can be sent with or without their animation effect. <br /> - How else might chat effects bring delight to our users for example, New Years Eve 24h animation or others. | 46 | 47 | ### Other considerations 48 | Chat effects are great for our personal messaging or community members however, in the workplace they may not be appropriate. Therefore, users must turn on chat effects in order to see the timeline take over. In the future we may offer options such as; Enabling by room, space, or meta-space. 49 | 50 | ### Relevant links 51 | - https://github.com/matrix-org/matrix-react-sdk/tree/develop/src/effects 52 | -------------------------------------------------------------------------------- /docs/client_well_known.md: -------------------------------------------------------------------------------- 1 | # Client Well-Known 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | October 2, 2024 | 6 | 7 | <hr /> 8 | 9 | 10 | ## Matrix VoIP Configuration 11 | 12 | Matrix (1:1) VoIP configuration is keyed by `io.element.voip`. The following options are supported: 13 | 14 | | Key | Type | 🤖 | 🍎 | 🕸️ | Description | 15 | | - | - | - | - | - | - | 16 | | `disable_fallback_ice` | `Bool` | ✅ | ✅ | ✅ | Disable fallback ICE server, e.g. matrix.org, will require functioning TURN/STUN server to be configured. | 17 | 18 | ## Jitsi Configuration 19 | 20 | Jitsi configuration is keyed by `io.element.jitsi`. The following options are supported: 21 | 22 | | Key | Type | 🤖 | 🍎 | 🕸️ | Description | 23 | | - | - | - | - | - | - | 24 | | `preferredDomain` | `String` | ✅ | ✅ | ✅ | Use the specified server for Jitsi calls. | 25 | | `useFor1To1Calls` | `Bool` | ❌ | ✅ | ✅ | Use Jitsi for 1:1 calls (by default we use native Matrix calls). | 26 | 27 | ## Encryption Configuration 28 | 29 | Encryption configuration is keyed by `io.element.e2ee`. The following options are supported: 30 | 31 | | Key | Type | 🤖 | 🍎 | 🕸️ | Description | 32 | | - | - | - | - | - | - | 33 | | `default` | `Bool` | ✅ | ✅ | ✅ | Indicate if E2EE is enabled by default. | 34 | | `force_disable` | `Bool` | ❌ | ❌ | ✅ | Overrides `default` when true, removing the option to enable encryption throughout the UI (existing encrypted rooms are unaffected). | 35 | | `secure_backup_required` | `Bool` | ✅ | ✅ | ✅ | Indicate if secure backup (SSSS) is mandatory. | 36 | | `secure_backup_setup_methods` | `BackupSetupMethod` | ✅ | ✅ | ✅ | Methods to use to setup secure backup (SSSS). | 37 | | `outbound_keys_pre_sharing_mode` | `KeyPreSharingStrategy` | ✅ | ✅ | ❌ | Outbound keys pre sharing strategy. | 38 | 39 | ### Encryption Configuration Types 40 | 41 | ``` 42 | enum BackupSetupMethod: Int { 43 | case passphrase = 0 44 | case key = 1 45 | } 46 | ``` 47 | 48 | ``` 49 | enum KeyPreSharingStrategy: Int { 50 | case none = 0, 51 | case whenEnteringRoom = 1, 52 | case whenTyping = 2 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/crypto/backup.md: -------------------------------------------------------------------------------- 1 | # Key backup and recovery in EX/EW 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | Oct 02, 2023 | 6 | 7 | This document aims to describe product requirements for key backup and recovery and its user-facing components in EX/EW. 8 | 9 | - **Key backup** refers to the server-side encryption key backup. Devices can upload encrypted copies of their megolm keys (room history decryption keys) to the server. This is not exposed individually to users. 10 | - **Recovery** refers to secret storage (4S) that allows the user to securely store a remote encrypted backup of their devices' local secrets to their homeserver. The recovery restores the key backup secret as well as the cross-signing secrets. 11 | - **Chat backup** refers to key backup and recovery (4S) in a combined fashion. This is exposed to users in a way that they can backup their message history to the server (secret storage / 4S is implicit). Users can opt-out of it. 12 | 13 | **Table of contents** 14 | 15 | - [General guidelines, context, use cases](#general-guidelines-context-use-cases) 16 | - [Concept](#concept) 17 | * [General requirements](#general-requirements) 18 | * [Recovery setup](#recovery-setup) 19 | + [Recovery setup steps](#recovery-setup-steps) 20 | * [Recovery settings](#recovery-settings) 21 | * [Preventing loss of message history](#preventing-loss-of-message-history) 22 | * [Corner cases and unhappy paths](#corner-cases-and-unhappy-paths) 23 | * [Recovery](#recovery) 24 | 25 | **Figma designs** 26 | 27 | https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=12124-116601&mode=design&t=RLlU4wtjLjUt2xrO-0 28 | 29 | ## General guidelines, context, use cases 30 | 31 | 1. As a user I want to have message history across all my devices for that I can continue communication and use the history independent of the device. 32 | 2. As a user/organization I want to have key backup enabled automatically if my server has it enabled for that I cannot accidentally lose history. 33 | 3. As a user I want that key backup is enabled right away and independent of the recovery for that all keys are being backed-up and I can set up recovery at a later point in time when it suits me. 34 | 4. As a user I want to opt-out from key backup for that I have control over my data and what the server is allowed to store. 35 | 5. As a user I will use classic clients and new clients. The key backup mechanism needs to compatible between them. 36 | 6. As a user I want to be able to change my recovery when I still have signed-in devices for that I can use it in case of a loss. 37 | 38 | ## Concept 39 | 40 | ### General requirements 41 | 42 | - The user can retrieve their encrypted message history (if the device has been verified). 43 | - Message keys will be stored in the backup (if the device has been verified). 44 | - Key backup will always be enabled by default. On a new device, key backup will be enabled even if the recovery isn't yet set up. 45 | - If the user attempts to sign out their last device there should be a warning asking them whether they have a recovery key available. This should be independent of whether recovery was set up and guide the user to the secure backup settings. (see [Preventing loss of message history](#preventing-loss-of-message-history)) 46 | - Key backup on EX/EW must be compatible with backup implementations on classic / 3rd party client implementation such that users have a seamless experience when they use both in parallel. 47 | - Key backup must only contain safe keys such that the server cannot inject keys and the client can verify the keys' authenticity. 48 | - Key backup should only be used to retrieve message history (instead of using it to cover up missing keys that should actually be available to the device). 49 | 50 | ### Recovery setup 51 | When a user has no signed-in devices, it is necessary to use recovery in order to regain access to their crypto identity (4S and key backup). The recovery key can also be used to verify devices (in a single device use case it's mandatory). This section describes the requirements to set up recovery. 52 | 53 | - Recovery setup should not be part of [FTUE](https://github.com/vector-im/element-meta/blob/develop/docs/FTUE.md) to keep the experience slick and reduce the amount of steps. 54 | - If recovery isn't yet set up, there should be a notice to the user to inform and guide to recovery setup. This notice should be visible until the user has completed it. 55 | 56 | #### Recovery setup steps 57 | - As a foundation, the app generates a recovery key for the user (there can be other mechanisms in the future) 58 | - The user should be informed that they 59 | - need this recovery key in order to get access to their encryption identity. This allows to verify new devices and to get access to message history. If they logout (or lose) all of their devices, this key is mandatory. 60 | - should store the recovery key in a safe location (e.g., password manager or a safe) 61 | 62 | **Generate a recovery key** 63 | - A suitable recovery key will be generated for the user 64 | - The user can copy or save the key as file 65 | 66 | ### Recovery settings 67 | - Set up recovery 68 | - Allows the user to set up recovery (see [Recovery setup](#recovery-setup)) 69 | - Only shown if recovery isn't yet set up 70 | - Change recovery key 71 | - Requires the device to be trusted. Otherwise an existing recovery key is required. 72 | - If a user has lost their recovery key (or has other reasons to change it) but still has logged-in devices, they can create a new recovery key 73 | - Invalidates the previous recovery key(s) 74 | - Starts a new recovery set up 75 | - Disable backup 76 | - Removes existing key backup and 4S 77 | - Disables further key backup 78 | - Informs the user about consequences in a confirmation dialogue 79 | - On a new device, the user will not be able to retrieve their message history from the server (there might still be other clients forwarding keys for message history). 80 | - If the user signs out (or loses) all of their devices, they'll not be able to get access to their crypto identity. 81 | - They will need to rotate it which will be noticeable by other users they communicate with 82 | - They will lose access to their message history 83 | - They will need to re-verify users they have verified before to establish trust again 84 | - Sync backup (troubleshooting) 85 | - Only shown if something is wrong (some keys not available, etc.) 86 | - As this is important to fix, it should also be shown as a banner above the room list to make the user aware 87 | - Asks the user to enter the recovery key to re-initialize the backup 88 | - Guides the user how to obtain a new recovery key from another device if they have lost it 89 | - Guides the user to reset their crypto identity (last resort!) if they do not have access to another device and have lost their recovery key 90 | 91 | ### Preventing loss of message history 92 | There are a couple of circumstances where a user will lose access to their message history if they sign out their last device. For these cases, a warning should be shown to inform/remind the user and guide them to do the right thing. 93 | - Backup is disabled 94 | - The user should see a prompt telling them `You have turned off backup`, explaining that they will lose message history if they continue and guiding them to chat backup settings 95 | - Recovery wasn't set up 96 | - The user should see a prompt telling them `Recovery not set up`, explaining that they will lose message history if they continue and guiding them to chat backup settings 97 | - The client hasn't finished backing up keys 98 | - The user should see a prompt telling them `Backup is still in progress`, explaining that they will lose message history if they continue and giving guidance 99 | - Everything is good but the user lost their recovery key 100 | - The user should see a prompt telling them `Do you have your recovery key?`, explaining that they will lose message history if they don't have it and guiding them to settings to create a new one 101 | 102 | It should always be possible to force-logout the app. 103 | 104 | ### Corner cases and unhappy paths 105 | - User is signed-in but the device isn't verified [this case will be eliminated with the new [FTUE](https://github.com/vector-im/element-meta/blob/develop/docs/FTUE.md) concept] 106 | - The user should be prompted to verify the device via a banner at the top of the room list 107 | - The user should see a hint at the top of the timeline of each room telling them that `Message history is unavailable in this room. Verify this device to see your message history.` 108 | - User is signed-in but the device was verified before the chat backup feature was enabled/available [this is a theoretical case for the transition period where chat backup is being introduced] 109 | - In this case the client does not have the backup key as it only gets transferred during the verification process 110 | - The client should try again to get the backup key from another device 111 | - If that doesn't work, the client should prompt the user for the recovery key 112 | - User is signed-in, device is verified but key backup isn't enabled 113 | - The user should see a hint at the top of the timeline of each room telling them that `Message history is unavailable as chat backup was turned off. Please turn on chat backup to avoid this in the future.` 114 | - Message history is disabled in a particular room 115 | - The user should see a hint at the top of the timeline of each room telling them that `Message history is unavailable` 116 | 117 | ### Recovery 118 | The recovery process is handled in the [FTUE](https://github.com/vector-im/element-meta/blob/develop/docs/FTUE.md) concept. 119 | 120 | ### Indicators for message history 121 | -------------------------------------------------------------------------------- /docs/crypto/trust_v1.md: -------------------------------------------------------------------------------- 1 | # Trust & decorations v1 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | April 13, 2023 | 6 | 7 | This document aims to describe the user-facing changes on the classic Element clients that will be made with the introduction of TOFU. 8 | 9 | **Table of contents** 10 | - [General guidelines, context, use cases](#general-guidelines-context-use-cases) 11 | - [Indicators](#indicators) 12 | * [Room](#room) 13 | - [Behavior for identity mismatch](#behavior-for-identity-mismatch) 14 | * [TOFU-trusted user](#tofu-trusted-user) 15 | * [Verified user](#verified-user) 16 | - [Behavior for untrusted devices](#behavior-for-untrusted-devices) 17 | * [Isolation of untrusted devices](#isolation-of-untrusted-devices) 18 | - [Settings](#settings) 19 | 20 | **Figma prototypes** 21 | https://www.figma.com/file/PJTG8NndqQOSFVOtXJUAoK/TOFU?node-id=12-4684&t=wDqe8xNQhSUz0x77-0 22 | 23 | ## General guidelines, context, use cases 24 | 25 | 1. As a security-aware user I want to notice when the identity of users I'm communication with changes so that I can make sure that the authenticity is still intact via other means. 26 | 2. As a user I want to know when an identity change has happened to be able to determine impact based on the messages before and after the change. 27 | 3. As a user I want a suitable behavior for identity changes depending on how sensitive I am towards security to make a sensible trade-off between security and usability. Therefore an identity change of a verified user should get more attention than an identity change of an automatically trusted user (TOFU). 28 | 4. As an organization/user, untrusted devices of other users always pose a threat to information security as it is impossible to determine the authenticity of the logged-in user. 29 | 30 | ## Indicators 31 | 32 | For the first iteration in the classic Element clients, only the room indicators are relevant. 33 | 34 | ### Room 35 | 36 | - Indicator for room encryption (on/off) 37 | - Indicator for room integrity (currently part of the 'room encryption' indicator) 38 | - Shows information on an individual user's view 39 | - Use cases 40 | - Do I trust the identities of all other users in the room? Yes/No 41 | - Is there an identity mismatch with a verified user? 42 | - States (**currently**) 43 | - Room is encrypted but not all users have been verified, and those that have (if any) still have the verified identity (grey shield) 44 | - Room is encrypted but at least one user violates trust (identity mismatch; red shield with exclamation mark) 45 | - Room is encrypted and the user trusts all users (green shield with checkmark) 46 | - The set of “all users” depends on the type of room: 47 | - For regular / topic rooms, all users including yourself, are considered when decorating a room 48 | - For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room 49 | - States (**with TOFU**) 50 | - Room is encrypted and other users are TOFU-trusted or manually verified (grey shield) 51 | - Room is encrypted but at least one user violates its **verified** identity (identity mismatch; red shield with exclamation mark) 52 | - Room is encrypted and the user has **verified** the identity of all other users in the room (green shield with checkmark) 53 | 54 | ## Behavior for identity mismatch 55 | 56 | An identity mismatch can occur due to key reset or malicious activity. This scenario **can't be fixed by the affected user**. 57 | The default behavior should be different depending on how trust has been established in the first place. 58 | 59 | ### TOFU-trusted user 60 | 61 | - Other users will see a room notice informing them about the identity change and giving some further information on possible reasons and what to do 62 | - The new identity is automatically trusted via TOFU again 63 | - Users can communicate as before 64 | 65 | ### Verified user 66 | 67 | - Other users will see the indicator for bad room integrity is shown (red shield) => lists the users who are violating it and allows to resolve it 68 | - Other users will be asked to verify the new identity to resolve the situation (manual verification) 69 | - Messages from an untrusted identity show a warning until the identity is verified manually again 70 | - Users can communicate as before (unless they use the setting _Never send encrypted messages to unverified sessions from this session_) 71 | 72 | ## Behavior for untrusted devices 73 | 74 | When a user has a logged-in device that has not been verified with the identity of the user (using another device or a recovery method + key backup), the device will be untrusted and it is not possible to ensure that the device belongs to the actual user. This scenario **can be fixed by the affected user** by verifying the device. 75 | 76 | To keep security and integrity intact, the following measures will be taken 77 | - Isolate untrusted devices in encrypted rooms (can't send/receive messages) so that no information is accidentally leaked 78 | 79 | ### Isolation of untrusted devices 80 | 81 | If users deviate from the regular processes or there is a malicious homeserver inserting new devices, unverified devices can appear. 82 | 83 | To cover for the risk of information leakage, users on untrusted devices cannot send or receive messages in encrypted rooms. 84 | 85 | - For now, the application will **block user interaction** for users on untrusted devices in encrypted rooms. 86 | - Public rooms will still be accessible and usable from unverified devices 87 | - Users on untrusted devices are informed about this state and guided to device verification to resolve it 88 | - Key/secret exchange should be prevented until the situation is resolved 89 | - Other users do not explicitly have to be informed about this (since there is no bad impact for them and they anyway have no means to resolve the situation) 90 | 91 | ## Settings 92 | 93 | - `Never send encrypted messages to unverified sessions from this session (global / per room)` should be changed to `Never send encrypted messages to unverified users from this session (global / per room)` to reflect the new behavior of isolating unverified devices 94 | -------------------------------------------------------------------------------- /docs/crypto/trust_v2.md: -------------------------------------------------------------------------------- 1 | # Trust & decorations v2 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | May 17, 2023 | 6 | 7 | This document aims to describe the concepts for user-facing crypto-trust components and related decorations in the Element X and Element Web clients. 8 | 9 | **Table of contents** 10 | 11 | - [General guidelines, context, use cases](#general-guidelines-context-use-cases) 12 | - [Indicator analysis & background](#indicator-analysis--background) 13 | * [Timeline](#timeline) 14 | * [Room details (right sidebar)](#room-details-right-sidebar) 15 | * [Room (top-level)](#room-top-level) 16 | - [Indicators and indications in EX/EW](#indicators-and-indications-in-exew) 17 | * [Timeline](#timeline-1) 18 | * [Room (top-level)](#room-top-level-1) 19 | * [Room details (right sidebar)](#room-details-right-sidebar-1) 20 | - [Behavior for identity mismatch](#behavior-for-identity-mismatch) 21 | * [TOFU-trusted user](#tofu-trusted-user) 22 | * [Verified user](#verified-user) 23 | - [Behavior for untrusted devices](#behavior-for-untrusted-devices) 24 | * [Isolation of untrusted devices](#isolation-of-untrusted-devices) 25 | 26 | **Figma designs** 27 | 28 | https://www.figma.com/file/wqgXVb7RVaIybfRsXdWEiL/Trust-%26-Decoration 29 | 30 | ## General guidelines, context, use cases 31 | 32 | 1. As a security-aware user I want to notice **if** the identity of users I'm communication with changes so that I can make sure that the authenticity is still intact via other means. 33 | 2. As a user I want to know **when** an identity change has happened to be able to determine impact based on the messages before and after the change. 34 | 3. As a user I want a suitable behavior for identity changes depending on how sensitive I am towards security to make a sensible trade-off between security and usability. Therefore an identity change of a verified user should get more attention than an identity change of an automatically trusted user (TOFU). 35 | 3. As a user I want to know whether a room is public/encrypted for that I can use the room accordingly. 36 | 4. As a security-aware organization/user I want to ensure user authenticity/trust (make sure that users actually are who they appear to be) in order to prevent information leakage to unauthorized third parties. 37 | 5. As a user I want to know when a message has been sent in clear text in an encrypted room for that I can identify potential information leakage. 38 | 6. As an organization I want to be able to decide which level of trust is suited for users/rooms (TOFU vs. manual trust) 39 | 7. As an organization I want that the options to trust a new identity depend on desired trust level (simple confirmation vs. manual verification) 40 | 8. As a user I don't want to be bothered with technical information I do not need to understand in order to securely use the app. The app should only show crypto-related indications when it is necessary for the user to understand or for cases where they are able to act upon it. 41 | 9. As an organization/user, unverified devices of other users always pose a threat to information security as it is impossible to determine the authenticity of the logged-in user. 42 | 43 | ## Indicator analysis & background 44 | Gathering and analysis: https://docs.google.com/spreadsheets/d/19z9NQtfehwTETIOqA7SbXgaNqXR8xHE54ryUy2YOAzc/edit#gid=0 45 | 46 | The following sections outline the encryption-related indicators from an Element Classic point of view. Quite a couple of them can be dropped in EX as we either do not need them anymore for UX reasons or can find solutions on the protocol level that make them obsolete. 47 | 48 | ### Timeline 49 | 50 | The following refers to different states of the 'shield' indicator that can appear next to each message in the timeline. 51 | 52 | - Message sent in clear text in an encrypted room => required for certain cases, might change iconography/color (needs design input) 53 | - ~~Message sent by an unverified device of a verified user~~ => legacy and not required anymore as 54 | - a) users will not have unverified devices anymore in normal operations (we force verification during FTUE) 55 | - b) unverified devices will be isolated in encrypted rooms (can't send/receive messages) 56 | - ~~Message sent by a deleted device~~ => legacy and not required anymore as we'll find a solution for this case on the protocol level 57 | - ~~Authenticity of a message cannot be guaranteed~~ => legacy and not required anymore as unsafe keys will disappear (solved by symmetric backup and removal of key forwards) 58 | 59 | ### Room details (right sidebar) 60 | 61 | The following refers to the room details/information in the right sidebar of a room. 62 | 63 | - ~~Indicator for device verification~~ => legacy and not required anymore as device verification will be forced during FTUE and there will be other handling for unverified devices, making this information redundant for other users 64 | - ~~One of the user's devices is unverified~~ (red shield with exclamation mark) 65 | - ~~All devices of the user are verified~~ (green shield with checkmark) 66 | - Indicator for room encryption/trust (in DM / room details) 67 | - behaves like the room encryption indicator 68 | 69 | ### Room (top-level) 70 | 71 | The following refers to the main view, composer and header of a room 72 | 73 | - Indicator for unencrypted rooms 74 | - Indicator for room encryption (on/off) 75 | - Indicator for room integrity (currently part of the 'room encryption' indicator) 76 | - Shows information on an individual user's view 77 | - Shown in the header and the composer 78 | - Use cases 79 | - Do I trust the identities of all other users in the room? Yes/No 80 | - Is there an identity mismatch with a verified user? 81 | - States (Element Classic) 82 | - Room is encrypted but not all users have been verified, and those that have (if any) still have the verified identity (grey shield) 83 | - Room is encrypted but at least one user violates trust (identity mismatch; red shield with exclamation mark) 84 | - Room is encrypted and the user trusts all users (green shield with checkmark) 85 | - The set of “all users” depends on the type of room: 86 | - For regular / topic rooms, all users including yourself, are considered when decorating a room 87 | - For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room 88 | - States (**with TOFU**) 89 | - Room is encrypted and other users are TOFU-trusted or partly manually verified (grey shield) 90 | - Room is encrypted but at least one user violates its **verified** identity (identity mismatch; red shield with exclamation mark) 91 | - Room is encrypted and the user has **verified** the identity of all other users in the room (green shield with checkmark) 92 | 93 | ## Indicators and indications in EX/EW 94 | 95 | ### Timeline 96 | 97 | - Message sent in clear text in an encrypted room 98 | 99 | ### Room (top-level) 100 | 101 | - Default 102 | - Indicator for room encryption (on/off) 103 | - No indicator for room encryption/trust if trust is TOFU-based or mixed 104 | - Indicator for trust when the user has (manual) verified all other users in a room 105 | - Indicator for trust violation / identity mismatch of a previously (manually) verified user (at least one user violates its verified identity) 106 | - Later: Policy-based room encryption/trust indicator? (e.g., for verified-only rooms) 107 | 108 | ### Room details (right sidebar) 109 | 110 | - Room encryption/trust indicator 111 | - States (same as 'with TOFU') 112 | - Room is encrypted and other users are TOFU-trusted or partly manually verified (default encryption indicator) 113 | - Room is encrypted but at least one user violates its **verified** identity (identity mismatch; red indicator) 114 | - Room is encrypted and the user has **verified** the identity of all other users in the room (checkmark indicator) 115 | - No device list or device verification indicator for other users 116 | 117 | ## Behavior for identity mismatch 118 | 119 | An identity mismatch can occur due to key reset or malicious activity. This scenario **can't be fixed by the affected user**. 120 | The default behavior should be different depending on how trust has been established in the first place. 121 | 122 | ### TOFU-trusted user 123 | 124 | - Other users will see a room notice informing them about the identity change and giving some further information on possible reasons and what to do 125 | - The new identity is automatically trusted via TOFU again 126 | - Users can communicate as before 127 | 128 | ### Verified user 129 | 130 | - Other users will see a room notice informing them about the identity change and giving some further information on possible reasons and what to do 131 | - The indicator for bad room integrity is shown (red shield) => lists the users who are violating it and allows to resolve it 132 | - Other users will be asked to verify the new identity to resolve the situation (manual verification) 133 | - Messages from an untrusted identity show a warning until the identity is verified manually again 134 | - Users can communicate as before (unless they use the setting _Never send encrypted messages to unverified sessions from this session_) 135 | 136 | ## Behavior for untrusted devices 137 | 138 | When a user has a logged-in device that has not been verified with the identity of the user (using another device or a recovery method + key backup), the device will be untrusted and it is not possible to ensure that the device belongs to the actual user. This scenario **can be fixed by the affected user** by verifying the device. 139 | 140 | To keep security and integrity intact, the following measures will be taken 141 | 1. Force device verification during FTUE to prevent this scenario from occuring in normal operations (see FTUE product spec) 142 | 2. Isolate untrusted devices in encrypted rooms (can't send/receive messages) so that no information is accidentally leaked 143 | 144 | ### Isolation of untrusted devices 145 | 146 | If users deviate from the regular processes or there is a malicious homeserver inserting new devices, unverified devices can still appear. 147 | 148 | To cover for the risk of information leakage, users on untrusted devices cannot send nor receive messages in encrypted rooms. 149 | 150 | - The [new FTUE product spec](https://github.com/vector-im/element-meta/blob/develop/docs/FTUE.md) will force users to verify additional devices during the initial set up 151 | - Key/secret exchange will be prevented for untrusted devices (isolation of untrusted devices) 152 | - Other users do not explicitly have to be informed about a user having an untrusted device (since there is no bad impact for them and they anyway have no means to resolve the situation) 153 | -------------------------------------------------------------------------------- /docs/device_management.md: -------------------------------------------------------------------------------- 1 | # Device management (OIDC) 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | July 11, 2023 | 6 | 7 | This document outlines the use cases and concept for a device manager in EX/EW that is compatible with OpenID Connect (OIDC). 8 | 9 | **Table of contents** 10 | 11 | * [Use cases / scenarios](#use-cases--scenarios) 12 | * [Concept](#concept) 13 | + [Device authentication management in Matrix Authentication Service (web view)](#device-authentication-management-in-matrix-authentication-service-web-view) 14 | + [Device management entry points in the Element clients (EW/EX)](#device-management-entry-points-in-the-element-clients-ewex) 15 | 16 | **Figma designs** 17 | 18 | https://www.figma.com/file/40ucfibvabgbB1nXvAqxxz/Device-Management-for-EX-%26-MAS?type=design&node-id=183-10284&mode=design&t=OpxkRa0wtbfZKAeM-0 19 | 20 | ## Use cases / scenarios 21 | 22 | 1. As a user I want to have overview of my logged-in/authenticated devices for that I can manage access security of my account. 23 | 2. As a user I want to have the ability to disconnect remote devices selectively for that I can disconnect unused/lost devices or for that I can prevent harm from unknown devices (attackers), e.g., if my credentials have been compromised. 24 | 3. As a user I want to be explicitly notified if I have a new login/device for that I can take an appropriate action (verify/disconnect). 25 | 4. As a user I want to be able to rename a device to give it a meaningful name (e.g., "iPad"). 26 | 27 | ## Concept 28 | 29 | In essence, device management used to consist of two areas, device authentication and device verification (cross-signing; crypto). In an OIDC world, device authentication is handled by the OIDC protocol in a dedicated IdP / OIDC provider (OP). Device verification generally must be handled in the Element clients as only those have the required encryption keys to determine trust reliably. With the introduction of the [Trust & decorations v2 / TOFU](https://github.com/vector-im/element-meta/blob/develop/docs/crypto/trust_v2.md) concept, [untrusted devices will be isolated](https://github.com/vector-im/element-meta/blob/develop/docs/crypto/trust_v2.md#behavior-for-untrusted-devices). For this reason, device verification state in the device manager has become less important and will be **dropped for simplicity and usability**. 30 | 31 | The concept for OIDC-based device management in EX/EW is split in two parts: 32 | 33 | **1. Device authentication management in the OIDC provider** (web-based; Matrix Authentication Service or third party upstream IdP) 34 | - If Matrix Authentication Service is used without an upstream IdP, there is an integrated, web-based device authentication manager 35 | - If an upstream IdP is used in addition, device authentication management relies on its capabilities 36 | 37 | **2. Device management entry points in the Element clients (EX/EW)** 38 | - Element clients provide a settings entry _Devices_ which shows information about the current device and guides users to 1. via a link _Manage devices_ 39 | - Element clients will notify users about new logins/devices (based on device verification state as a proxy for whether the device belongs to the user) guiding them to 1. to allow disconnecting potentially malicious devices 40 | 41 | ### Device authentication management in Matrix Authentication Service (web view) 42 | 43 | This section outlines the requirements/features for a web-based device authentication manager in the Matrix Authentication Service (MAS). If a deployment uses an external upstream IdP in addition, this will be replaced by the upstream IdP's capabilities. 44 | 45 | - List all devices logged-in to a user’s account 46 | - Mark inactive sessions (no activity since 90+ days) 47 | - Display information about the devices (name, app version, session ID) 48 | - Display IP address of devices 49 | - Display last seen time (when was the device last online) 50 | - Rename a device 51 | - Logout single existing devices 52 | - Logout multiple existing devices at once 53 | - Change push notifications behavior for devices 54 | 55 | ### Device management entry points in the Element clients (EW/EX) 56 | 57 | This section outlines the requirements/features around device management entry points in the Element clients (EW/EX). 58 | 59 | - The user must be informed about new logins/devices in their account as long as they are untrusted (via push notification / when opening the app) 60 | - Display information about the current device (name, session ID?) 61 | - Button _Manage devices_ that guides the user to 'Device authentication management in the OIDC Provider' (web view) 62 | -------------------------------------------------------------------------------- /docs/keyboard_shortcuts.md: -------------------------------------------------------------------------------- 1 | ## Keyboard shortcuts 2 | 3 | When adding or removing keyboard shortcuts, the following should be considered: 4 | * Does the shortcut clash with any OS shortcuts? (Windows, macOS or Linux) 5 | * Does the shortcut clash with any browser shortcuts? (Firefox, Chrome, Edge and Safari) 6 | * Is the shortcut accessible? 7 | * Is the shortcut familiar to users? 8 | 9 | New shortcuts must bring added value to most users and, where possible, be familiar to users from other apps. 10 | -------------------------------------------------------------------------------- /docs/notifications_defaultsettings.md: -------------------------------------------------------------------------------- 1 | # Default Notification Settings 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | In Progress | April 12, 2023 | 6 | 7 | <hr /> 8 | 9 | ## Objective 10 | 11 | New Users should have default notification settings that make sense. They should be easy to grok, and easy to change. 12 | 13 | ## Notifications 14 | 15 | _Notification documentation is currently in progress. [Track along here (Internal only)](https://docs.google.com/document/d/1J6-XyVQsthkylffD20UrCFbWF_g9mao8JS2bhn5z1SU/edit?usp=sharing)_ 16 | 17 | ## Global Settings 18 | 19 | The Global Settings screen for Notifications has been redesigned. [The issue regarding these changes can be found here.](https://github.com/vector-im/element-web/issues/24567) 20 | 21 | ## Default Settings for new Users 22 | 23 | | Web Setting | Default/ Enabled | What does it mean | Push rules (see [predefined rules](https://spec.matrix.org/v1.6/client-server-api/#predefined-rules) | 24 | |---|---|---|---| 25 | | Enable Notifications for this account | Disabled (when enabled, push notifications are off) | Setting previously existed. No change. https://spec.matrix.org/v1.6/client-server-api/#default-override-rules | `.m.rule.master` | 26 | | Enable desktop notifications | Off | We have to ask permission before turning this on. The setting previously existed, there's no change. | N/A | 27 | | Show message preview in desktop notification | Off | Setting previously existed. No change. | N/A | 28 | | Enable audible notifications | On | Setting previously existed. No change. | N/A | 29 | | Default settings for all rooms | All messages | All messages in all rooms are set to "On" by default. There's more information on this decision [here](https://docs.google.com/document/d/1J6-XyVQsthkylffD20UrCFbWF_g9mao8JS2bhn5z1SU/edit#bookmark=id.ayc00ag4dkdj). | `.m.rule.encrypted_room_one_to_one`, `.m.rule.room_one_to_one`, `.m.rule.message`, `.m.rule.encrypted` | 30 | | Play a sound for | All messages | This is the equivalent of "Noisy". | Customise actions of `.m.rule.encrypted_room_one_to_one`, `.m.rule.room_one_to_one`, `.m.rule.message`, `.m.rule.encrypted` | 31 | | Invited to a room | On | Setting previously existed. Its default is “On” and the “Noisy” aspect is determined by the user's Sound settings. | `.m.rule.invite_for_me` | 32 | | New room activity, upgrades, and status messages | Off | This is a catch-all bucket. Better explanation pending discussion. | `.m.rule.tombstone` + new ones | 33 | | Messages sent by a bot | On | Setting previously existed. No change. | `.m.rule.suppress_notices` | 34 | | Mentions when someone uses "@room" | On | Setting previously existed. Its default is “On” and the “Noisy” aspect is determined by the user's Sound settings. | `.m.rule.roomnotif` ([MCS3952](https://github.com/matrix-org/matrix-spec-proposals/pull/3952) defines `.m.rule.is_room_mention`) | 35 | | Mentions when someone uses "@displayname" | On | Setting previously existed. Its default is “On” and the “Noisy” aspect is determined by the user's Sound settings. | `.m.rule.contains_display_name` and `.m.rule.constains_user_name` (maybe?) ([MCS3952](https://github.com/matrix-org/matrix-spec-proposals/pull/3952) defines `.m.rule.is_user_mention`) | 36 | | Notify when someone users a keyword | On | There are no preset keywords configured. Setting previously existed. No change. | A custom rule is created for each keyword | 37 | | Email summary | Off | Setting previously existed. We should improve our emails before subjecting all users to them. | N/A | 38 | | This line is blank intentionally | N/A | N/A | N/A | 39 | 40 | ## How this looks on Mobile 41 | 42 | The Element X team is currently working on which settings they will show to users. The above list has been collaboratively designed by both the Web and Element X Mobile teams and therefore we will have platform parity at launch. 43 | When Element X Notifications Settings screens have been designed and built, this document should be updated and a new table added to the above section. 44 | 45 | ## The Work In Progress Design 46 | While this work is still being built, we'll include the design here for reference: 47 | | WIP | 48 | |---| 49 | | ![Web](https://user-images.githubusercontent.com/89144281/231469501-7ab7b530-e0e2-4e87-8caa-ebc62ec8f609.png) | 50 | 51 | -------------------------------------------------------------------------------- /docs/profile_signout.md: -------------------------------------------------------------------------------- 1 | ## Sign Out 2 | Signing out of an account removes a users account from the device they are logged in to. Users will not be able to access their account on that device until they log in again. 3 | 4 | ### Auto-logout 5 | Automated, time-based sign out is likely to lead to users losing decryption keys so we do not want to consider introducing automated signout at this time. 6 | 7 | ### Placement 8 | Sign out is a destructive action and as such should not be easy to do. However, if a user has made the decision to sign out of their app, it should not be hard to do. 9 | * Users should be able to find Sign Out from Settings. 10 | * Users should not be able to access Sign Out directly from the home screen. 11 | 12 | **September 29, 2022** 13 | Currently, all but our Android app have Sign Out in a place that does not follow this Prodcut Strategy. We will need to consider and update the designs of our product when makes sense - not as a matter of urgency. 14 | There are open issues in all our repo's that request moving this button: These will be closed, quoting this strategy once it has been reviewed internally. 15 | -------------------------------------------------------------------------------- /docs/roomlist_messagePreviews.md: -------------------------------------------------------------------------------- 1 | # Message Previews 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | In progress | April 12, 2023 | 6 | 7 | <hr /> 8 | 9 | ## Objective 10 | 11 | Message previews appear in the room list and show users the latest activity in a room. 12 | This document outlines how they are used, and any complications or extensions to message previews. 13 | 14 | ## Eligible Events 15 | Certain events should not appear in the message previews 16 | 17 | | Event type | Included in message preview? | Notes | Status on Web | Status on Mobile | 18 | |---|---|---|---|---| 19 | | New messages in the timeline | Yes | | ✅ | | 20 | | Reactions | Yes | In order for users to be able to determine whether a reaction is a message event emoji or a reaction event emoji we should prepend the text, ie: "Reacted with [emoji] | https://github.com/vector-im/element-web/issues/25083 | | 21 | | Edits | Yes, if the last message in the room | | Currently displays all message edits, regardless of place in timeline (needs GH issue) | | 22 | | Message is deleted | Yes | | https://github.com/vector-im/element-web/issues/22334 | | 23 | | Polls | Awaiting input from Polls PM | | | | 24 | | Audio and Video calls | Yes | Calls should show up in previews, either incoming or missed | Needs testing | | 25 | | Someone leaves the room | Yes | | Needs testing | | 26 | | A new users or bot joins the room | Yes | | Needs testing | | 27 | | Room change relating to the user | No | EG: Display name changes | Needs testing | | 28 | | Room changes relating to the room itself | No | EG: Upgrade | Needs testing | | 29 | | Threaded messages | Yes | They should be clearly in a thread | https://github.com/vector-im/element-web/issues/23920 | | 30 | | Spoilers | Yes, but with hidden content | Needs a design | https://github.com/vector-im/element-web/issues/14447 | | 31 | | Ignored user content | No | The user has been ignored, their messages should not show at all | https://github.com/vector-im/element-web/issues/16161 | | 32 | | Draft message available in composer | Yes | This is an enhancement that has been raised by the community but not yet considered by teams | https://github.com/vector-im/element-web/issues/16769 | | 33 | 34 | 35 | ## Web 36 | On Web, Message Previews may be toggled on or off from the room list. User's may have different message preview preferences by room tag (favourites, people, rooms, low priority, etc.) 37 | Many of the Element X ideals are not yet implemented on web due to time restrictions and lack of resources. Where parity does not exist, a GH issue should be available as work on the backlog. 38 | 39 | ## Threads 40 | The Threads List panel and Thread Summaries in the timeline also show message previews. 41 | It should be our goal to use the same components and logic for all message previews throughout the app. 42 | -------------------------------------------------------------------------------- /docs/roomlist_sort.md: -------------------------------------------------------------------------------- 1 | # Room list sorting on Web 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | In progress | April 12, 2023 | 6 | 7 | <hr /> 8 | 9 | ## Objective 10 | 11 | The way the Web product sorts the room list is complicated and may be confusing. This document outlines the desired user experience of the room list and how it sorts depending on settings available. 12 | The Element X team is changing the sorting on Mobile - when finalised, we will need to address how to bring the two platforms to parity. For the moment we're doing the best we can given the information and time available to us. 13 | 14 | ## Sorting algorithms 15 | 16 | | Setting | What happens | Muted rooms | 17 | |---|---|---| 18 | | Sort by: A-Z | The room list is sorted alphabetically, regardless of notification settings or counts/activity | Muted rooms are mixed in | 19 | | Sort by: A-Z and Show rooms with unread messages first | The room list is sorted alphabetically. Rooms with a highlight or notification count (red or grey) are A-Z, followed by all other rooms A-Z | Muted rooms are mixed in depending on their notification state | 20 | | Sort by: Activity | Regardless of the count, the room list has the latest activity at the top of the list (where activity is [defined here](https://docs.google.com/document/d/1ryZjVuYBSfsu4e6fqYHIC9hJhY-aEWukcWMig1ewRA4/edit?usp=sharing)). | Muted rooms are not included in the sort, and they are at the bottom of each list. | 21 | | Sort by: Activity and Show rooms with unread messages first | Rooms with a highlight count or notification count (red or grey) are at the top of the list, sorted by latest activity (where activity that impacts sorting from table above). These are followed by all other rooms. | Muted rooms are not included in the sort, and they are at the bottom of each list. | 22 | 23 | ## Muted Rooms 24 | [More information can be found here](https://docs.google.com/document/d/1J6-XyVQsthkylffD20UrCFbWF_g9mao8JS2bhn5z1SU/edit#bookmark=id.4zqm2egzql0b) 25 | When a user has muted a room they have actively told Element that they are not interested in seeing the updates available in that room. They do not wish to receive notifications from this muted room. In that case, it may be confusing to users to see that room always at the top of their room list (if it's a high activity room). Therefore, the decision was made to remove Muted rooms from the room sort. 26 | 27 | When a user puts a room on mute, the timestamp of that action should be considered as the 'lastest activity' in the activity sort and therefore, as other rooms have newer activity the muted room moves further down. If a user posts to the muted room, this latest message event is now the timestamp that should be considered for the activity. 28 | - The Element X team is building the room list from scratch and will be implementing this desired state. The Web team do not have this same advantage and will not be committing to this method immediately. [Track the Room Sort issues here](https://github.com/vector-im/element-web/issues/25032) 29 | -------------------------------------------------------------------------------- /docs/rooms_invitejoinleaveban.md: -------------------------------------------------------------------------------- 1 | # Moderating Rooms 2 | 3 | | Status | Last updated | 4 | |--|--| 5 | | Draft | May 2, 2022 | 6 | 7 | <hr /> 8 | 9 | This document is an extension of the Matrix.org docs, [found here](https://matrix.org/docs/guides/moderation#power-levels) 10 | 11 | ## Room commands for joining, leaving, or kicking 12 | 13 | | Action | Command | Description | 14 | |---|---|---| 15 | | Invite | /invite <user_id> | Invite a user to join the room you're in. Users can join public rooms without an invite, though sending them an invite will work too.| 16 | | Remove | /remove <user_id> | Remove a user from the room you're in. User's can re-join if the room is public, or if you send them another Invite. This was previously known as "kick" | 17 | | Ban | /ban <user_id> | The user is banned from the room you're in. The user cannot re-join unless "unbanned"| 18 | | Unban | /unban <user_id> | The user is no longer banned and may join the room if invited (or if it's a public room, they can join without an invite) | 19 | | Join | /join <room_address> | You'll join the room specified in the room address. | 20 | | Part | /part | You'll leave the room specified in the room address. If the room is public, you may rejoin. If the room is invite only, you will need to be invited to join again. | 21 | 22 | <hr /> 23 | From the Matrix.org website: 24 | "The act of kicking a user temporarily removes them from the room. If the room is publicly joinable, the user can immediately rejoin it. If the room is invite-only, the user will need to be re-invited to join. 25 | The act of banning a user stops them from joining a room until the ban is removed. Banning automatically kicks the user from the room" 26 | 27 | Scenarios that may impact this decision: 28 | - Public rooms are public and, unless a user is banned, can be joined and left at will. 29 | - Invite-only rooms are invite only. The invite should 'count' once. 30 | - Imagine if a user is invited as they're joining a new team at work: That team's internal chat room is priavte and relevant to that team. If that user leaves the team and are removed, they should not be able to rejoin later. "Ban" does not work in this scenario as the user's mental model is that benning a user is a punishment for behaviour rather than a removal due to irrelevance and a bias for privacy. 31 | <hr /> 32 | 33 | This document is a result of the [issue here](https://github.com/vector-im/element-web/issues/3093) and may be extended or updated later. 34 | -------------------------------------------------------------------------------- /docs/text_effects.md: -------------------------------------------------------------------------------- 1 | ## List of text effects 2 | 3 | Text effects are small delighters that don't get in the way of user tasks, therefore we're happy to accept any further additions to this list. 4 | 5 | | Command | What happens | 6 | |---|---| 7 | | /spoiler \<message\> | Sends the given message as a spoiler | 8 | | /shrug \<message\> | Prepends ¯\\_(ツ)_/¯ to a plain-text message| 9 | | /tableflip \<message\> | Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message| 10 | | /unflip \<message\> | Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message | 11 | | /lenny \<message\> | Prepends ( ͡° ͜ʖ ͡°) to a plain-text message| 12 | | /plain \<message\> | Sends a message as plain text, without interpreting it as markdown| 13 | | /html \<message\> | Sends a message as html, without interpreting it as markdown| 14 | | /rainbow \<message\> |Sends the given message coloured as a rainbow | 15 | | /rainbowme \<message\> |Sends the given emote coloured as a rainbow| 16 | | /me \<message\> | Displays action by appending your display name to the message| 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint": "yarn lint:workflows", 4 | "lint:workflows": "find .github/workflows -type f \\( -iname '*.yaml' -o -iname '*.yml' \\) | xargs -I {} sh -c 'echo \"Linting {}\"; action-validator \"{}\"'", 5 | "sync-labels": "yarn ts-node .github/actions/sync-labels.ts" 6 | }, 7 | "devDependencies": { 8 | "@action-validator/cli": "^0.5.3", 9 | "@action-validator/core": "^0.5.3", 10 | "@types/github-label-sync": "^2.3.3", 11 | "@types/node": "^20.9.2", 12 | "commander": "^11.1.0", 13 | "github-label-sync": "^2.3.1", 14 | "octonode": "^0.10.2", 15 | "ts-node": "^10.9.1", 16 | "typescript": "^5.2.2", 17 | "yaml": "^2.3.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spec/functional_members.md: -------------------------------------------------------------------------------- 1 | # io.element.functional_members: Marking room members as unimportant functional members 2 | 3 | ## Problem 4 | 5 | Bots and bridges can be helpful assistants when chatting. Like a home assistant, they can provide information from the Internet, log events or teach room members to avoid inappropriate words. These bots listen to a conversations but are not a full participant. They are functional members helping out. 6 | 7 | Direct Message rooms and those of small groups may not set a room name (`m.room.name`) so clients use the names of other room members to generate a dynamic title. When Alice and Bob are chatting, she sees the room as "Bob" and he sees the room as "Alice". When they add a bot, the room name becomes "Bob and Weather Bot" or "Alice and Weather Bot" respectively. 8 | 9 | A key use case is the addition of audit bots, which an organisation may add because of regulatory requirements of having auditable log of their employee's conversations. This is a requirement 10 | 11 | ## Proposal 12 | 13 | This MSC adds the ability to exclude functional members from being present in room summaries like the dynamically generated name. 14 | 15 | We introduce the state event `io.element.functional_members`. It has no state key. 16 | 17 | An example event is: 18 | 19 | ```json5 20 | { 21 | "type": "io.element.functional_members", 22 | "state_key": "", 23 | "content": { 24 | "service_members": [ 25 | "@slackbot:matrix.org" 26 | ] 27 | } 28 | } 29 | ``` 30 | 31 | `service_members` is an optional array of strings. All strings should be Matrix user IDs. If the Matrix user ID of a room member is included in this list, the client should exclude the Matrix user from room summaries. 32 | 33 | ## Client advisory 34 | 35 | When calculating a room name: 36 | 37 | 1. Read the room state of type `io.element.functional_members`. (no state key) 38 | 2. In the content, check if there's an array of strings called `service_members`. 39 | 3. Exclude those user ids from the name generation. 40 | 41 | ## Security considerations 42 | 43 | A malicious actor could hide their room membership by setting this state event, therefore being less visible to other users. Mitigating this threat is the opposed goal of what this is trying to achieve and a balance has to be found. 44 | 45 | At Element we decided to exclude functional users from the dynamic room name. This does not exclude them from the list of room members, making their presence visible to people investigating the room. Other events like invites, power level changes and messages of functional members are also not obscured in any way. 46 | 47 | State events can commonly only be edited by room moderators, limiting the threat of a stranger hiding themselves. In Direct Message rooms, both room members are Administrators by default which would allow them to invite and hide a third party. However, the threat is not any higher than one member leaking the conversation otherwise. In contrast, by providing a way to declare functional members, people are encouraged to add bots as a room member visible to the other person. 48 | 49 | ## Related MSCs 50 | 51 | [MSC2199](https://github.com/matrix-org/matrix-spec-proposals/pull/2199) describes Canonical DMs and adds a definition of `unimportant` users, which are likely going to accomplish the same. 52 | However, it's still uncertain where MSC2199 is going. 53 | 54 | ## Implementations 55 | 56 | * The `matrix-js-sdk` which powers Element Web and Desktop implemented this in July 2021. 57 | * https://github.com/matrix-org/matrix-js-sdk/pull/1771 58 | * Element iOS implemented this in August 2021. 59 | * https://github.com/vector-im/element-ios/issues/4609 60 | * Element Android is expected to implement this. 61 | * https://github.com/vector-im/element-android/issues/3736 62 | -------------------------------------------------------------------------------- /spec/matrix_client_information.md: -------------------------------------------------------------------------------- 1 | # io.element.matrix_client_information.<device_id>: Storing additional client information per device 2 | 3 | ## Problem 4 | 5 | Currently, sessions are only easily recognisable by their `display_name`. Depending on client implementation, this may 6 | include some stringified information about the session. (For example, Element Web uses `'%(appName)s (%(browserName)s, 7 | %(osName)s)'`). This information can become stale, and if edited by the user any device detail is lost. 8 | 9 | By saving structured and up to date session information, users will be able to more easily recognise their sessions. 10 | This gives users more confidence in removing stale or suspicious sessions. 11 | 12 | 13 | ## Proposal 14 | 15 | We introduce the account_data event `io.element.matrix_client_information.<device_id>`. 16 | 17 | An example event for a device with `device_id` of `abc123`: 18 | 19 | ```json5 20 | { 21 | "type": "io.element.matrix_client_information.abc123", 22 | "content": { 23 | "name": "Element Web", 24 | "version": "1.2.3", 25 | "url": "app.element.io" 26 | } 27 | } 28 | ``` 29 | 30 | All properties are strings. `url` property is optional. 31 | 32 | ## Client advisory 33 | 34 | When starting the client after login or update: 35 | 1. Upsert an account data event with the type `io.element.matrix_client_information.<device_id>` for the current device 36 | 37 | When rendering device application information: 38 | 39 | 1. Read account data event of type `io.element.matrix_client_information.<device_id>` for each given device. 40 | 41 | These events should be pruned periodically. 42 | 43 | ## MSC 44 | Pruning of events depends on implementation of [MSC3391: Removing account 45 | data](https://github.com/matrix-org/matrix-spec-proposals/pull/3391) 46 | 47 | ## Security considerations 48 | N/A 49 | 50 | ## Implementations 51 | 52 | * The `matrix-react-sdk` which powers Element Web and Desktop implemented this in September 2022. 53 | * https://github.com/matrix-org/matrix-react-sdk/pull/9314 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "target": "es2022", 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /wiki-images/test-cases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/element-meta/1a93f254dcb1b55435fb29a99d83f9b8f3a411c8/wiki-images/test-cases.png -------------------------------------------------------------------------------- /wiki-images/testing-issue-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/element-meta/1a93f254dcb1b55435fb29a99d83f9b8f3a411c8/wiki-images/testing-issue-list.png -------------------------------------------------------------------------------- /wiki-images/testing-issues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/element-meta/1a93f254dcb1b55435fb29a99d83f9b8f3a411c8/wiki-images/testing-issues.png -------------------------------------------------------------------------------- /wiki-images/testing-tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/element-meta/1a93f254dcb1b55435fb29a99d83f9b8f3a411c8/wiki-images/testing-tabs.png -------------------------------------------------------------------------------- /wiki-images/testing-test-cases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/element-hq/element-meta/1a93f254dcb1b55435fb29a99d83f9b8f3a411c8/wiki-images/testing-test-cases.png -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@action-validator/cli@^0.5.3": 6 | version "0.5.3" 7 | resolved "https://registry.yarnpkg.com/@action-validator/cli/-/cli-0.5.3.tgz#2d4fe473058f6ef17530b9bb5929f0eade4e8672" 8 | integrity sha512-u/kv77ZC55PfAc9RQeP76xV1GysTisEJjO+b5TgCrBBcaKtGLt5Y7ki2GSdc7CDzncNc1oeoGcwaLMW6JSdQAw== 9 | dependencies: 10 | chalk "5.2.0" 11 | 12 | "@action-validator/core@^0.5.3": 13 | version "0.5.3" 14 | resolved "https://registry.yarnpkg.com/@action-validator/core/-/core-0.5.3.tgz#493b850ef7a2801830069d78f60cbefe0697423f" 15 | integrity sha512-0ABelaY7nmpvV5q0z8Vl1cDeq2OZ1HyNXjXS54fBadLaCssZLbDvTa7M2uUaNMcEWV+Xl48WWbnqJWKePt9qHQ== 16 | 17 | "@cspotcode/source-map-support@^0.8.0": 18 | version "0.8.1" 19 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" 20 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== 21 | dependencies: 22 | "@jridgewell/trace-mapping" "0.3.9" 23 | 24 | "@financial-times/origami-service-makefile@^7.0.3": 25 | version "7.0.3" 26 | resolved "https://registry.yarnpkg.com/@financial-times/origami-service-makefile/-/origami-service-makefile-7.0.3.tgz#96a9913a2e79d7fc4c02b1a6c359d20d94b46a37" 27 | integrity sha512-aKe65sZ3XgZ/0Sm0MDLbGrcO3G4DRv/bVW4Gpmw68cRZV9IBE7h/pwfR3Rs7njNSZMFkjS4rPG/YySv9brQByA== 28 | 29 | "@jridgewell/resolve-uri@^3.0.3": 30 | version "3.1.1" 31 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" 32 | integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== 33 | 34 | "@jridgewell/sourcemap-codec@^1.4.10": 35 | version "1.4.15" 36 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" 37 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== 38 | 39 | "@jridgewell/trace-mapping@0.3.9": 40 | version "0.3.9" 41 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" 42 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== 43 | dependencies: 44 | "@jridgewell/resolve-uri" "^3.0.3" 45 | "@jridgewell/sourcemap-codec" "^1.4.10" 46 | 47 | "@sindresorhus/is@^5.2.0": 48 | version "5.6.0" 49 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668" 50 | integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g== 51 | 52 | "@szmarczak/http-timer@^5.0.1": 53 | version "5.0.1" 54 | resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" 55 | integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== 56 | dependencies: 57 | defer-to-connect "^2.0.1" 58 | 59 | "@tsconfig/node10@^1.0.7": 60 | version "1.0.9" 61 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" 62 | integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== 63 | 64 | "@tsconfig/node12@^1.0.7": 65 | version "1.0.11" 66 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" 67 | integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== 68 | 69 | "@tsconfig/node14@^1.0.0": 70 | version "1.0.3" 71 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" 72 | integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== 73 | 74 | "@tsconfig/node16@^1.0.2": 75 | version "1.0.4" 76 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" 77 | integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== 78 | 79 | "@types/github-label-sync@^2.3.3": 80 | version "2.3.3" 81 | resolved "https://registry.yarnpkg.com/@types/github-label-sync/-/github-label-sync-2.3.3.tgz#eca539125a8c12975b2d4444bfed1d292bbecd61" 82 | integrity sha512-dyKwb9Tgkb5gBF2dnh7nahzXTYrcKX4VVpp81HyD0qirYZ16lf+ogIme8EuT5S8D+mlAVtCvZ7pvCluHIcY5/A== 83 | 84 | "@types/http-cache-semantics@^4.0.2": 85 | version "4.0.4" 86 | resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" 87 | integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== 88 | 89 | "@types/node@^20.9.2": 90 | version "20.9.2" 91 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.2.tgz#002815c8e87fe0c9369121c78b52e800fadc0ac6" 92 | integrity sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg== 93 | dependencies: 94 | undici-types "~5.26.4" 95 | 96 | acorn-walk@^8.1.1: 97 | version "8.3.0" 98 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" 99 | integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== 100 | 101 | acorn@^8.4.1: 102 | version "8.11.2" 103 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" 104 | integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== 105 | 106 | ajv@^6.12.3: 107 | version "6.12.6" 108 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" 109 | integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 110 | dependencies: 111 | fast-deep-equal "^3.1.1" 112 | fast-json-stable-stringify "^2.0.0" 113 | json-schema-traverse "^0.4.1" 114 | uri-js "^4.2.2" 115 | 116 | ajv@^8.6.3: 117 | version "8.12.0" 118 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" 119 | integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== 120 | dependencies: 121 | fast-deep-equal "^3.1.1" 122 | json-schema-traverse "^1.0.0" 123 | require-from-string "^2.0.2" 124 | uri-js "^4.2.2" 125 | 126 | ansi-styles@^4.1.0: 127 | version "4.3.0" 128 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 129 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 130 | dependencies: 131 | color-convert "^2.0.1" 132 | 133 | arg@^4.1.0: 134 | version "4.1.3" 135 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 136 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 137 | 138 | argparse@^1.0.7: 139 | version "1.0.10" 140 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 141 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 142 | dependencies: 143 | sprintf-js "~1.0.2" 144 | 145 | asn1@~0.2.3: 146 | version "0.2.6" 147 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" 148 | integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== 149 | dependencies: 150 | safer-buffer "~2.1.0" 151 | 152 | assert-plus@1.0.0, assert-plus@^1.0.0: 153 | version "1.0.0" 154 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 155 | integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== 156 | 157 | asynckit@^0.4.0: 158 | version "0.4.0" 159 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 160 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 161 | 162 | aws-sign2@~0.7.0: 163 | version "0.7.0" 164 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" 165 | integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== 166 | 167 | aws4@^1.8.0: 168 | version "1.12.0" 169 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" 170 | integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== 171 | 172 | bcrypt-pbkdf@^1.0.0: 173 | version "1.0.2" 174 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 175 | integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== 176 | dependencies: 177 | tweetnacl "^0.14.3" 178 | 179 | bluebird@^3.5.0: 180 | version "3.7.2" 181 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" 182 | integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== 183 | 184 | cacheable-lookup@^7.0.0: 185 | version "7.0.0" 186 | resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" 187 | integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w== 188 | 189 | cacheable-request@^10.2.8: 190 | version "10.2.14" 191 | resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-10.2.14.tgz#eb915b665fda41b79652782df3f553449c406b9d" 192 | integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ== 193 | dependencies: 194 | "@types/http-cache-semantics" "^4.0.2" 195 | get-stream "^6.0.1" 196 | http-cache-semantics "^4.1.1" 197 | keyv "^4.5.3" 198 | mimic-response "^4.0.0" 199 | normalize-url "^8.0.0" 200 | responselike "^3.0.0" 201 | 202 | caseless@~0.12.0: 203 | version "0.12.0" 204 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 205 | integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== 206 | 207 | chalk@5.2.0: 208 | version "5.2.0" 209 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" 210 | integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== 211 | 212 | chalk@^4.1.2: 213 | version "4.1.2" 214 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 215 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 216 | dependencies: 217 | ansi-styles "^4.1.0" 218 | supports-color "^7.1.0" 219 | 220 | color-convert@^2.0.1: 221 | version "2.0.1" 222 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 223 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 224 | dependencies: 225 | color-name "~1.1.4" 226 | 227 | color-name@~1.1.4: 228 | version "1.1.4" 229 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 230 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 231 | 232 | combined-stream@^1.0.6, combined-stream@~1.0.6: 233 | version "1.0.8" 234 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 235 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 236 | dependencies: 237 | delayed-stream "~1.0.0" 238 | 239 | commander@^11.1.0: 240 | version "11.1.0" 241 | resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" 242 | integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== 243 | 244 | commander@^6.2.1: 245 | version "6.2.1" 246 | resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" 247 | integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== 248 | 249 | core-util-is@1.0.2: 250 | version "1.0.2" 251 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 252 | integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== 253 | 254 | create-require@^1.1.0: 255 | version "1.1.1" 256 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 257 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 258 | 259 | dashdash@^1.12.0: 260 | version "1.14.1" 261 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 262 | integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== 263 | dependencies: 264 | assert-plus "^1.0.0" 265 | 266 | decompress-response@^6.0.0: 267 | version "6.0.0" 268 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" 269 | integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== 270 | dependencies: 271 | mimic-response "^3.1.0" 272 | 273 | deep-extend@^0.6.0: 274 | version "0.6.0" 275 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 276 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 277 | 278 | defer-to-connect@^2.0.1: 279 | version "2.0.1" 280 | resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" 281 | integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== 282 | 283 | delayed-stream@~1.0.0: 284 | version "1.0.0" 285 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 286 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 287 | 288 | diff@^4.0.1: 289 | version "4.0.2" 290 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 291 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 292 | 293 | ecc-jsbn@~0.1.1: 294 | version "0.1.2" 295 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 296 | integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== 297 | dependencies: 298 | jsbn "~0.1.0" 299 | safer-buffer "^2.1.0" 300 | 301 | esprima@^4.0.0: 302 | version "4.0.1" 303 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 304 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 305 | 306 | extend@~3.0.2: 307 | version "3.0.2" 308 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 309 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 310 | 311 | extsprintf@1.3.0: 312 | version "1.3.0" 313 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 314 | integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== 315 | 316 | extsprintf@^1.2.0: 317 | version "1.4.1" 318 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" 319 | integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== 320 | 321 | fast-deep-equal@^3.1.1: 322 | version "3.1.3" 323 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 324 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 325 | 326 | fast-json-stable-stringify@^2.0.0: 327 | version "2.1.0" 328 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 329 | integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 330 | 331 | forever-agent@~0.6.1: 332 | version "0.6.1" 333 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 334 | integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== 335 | 336 | form-data-encoder@^2.1.2: 337 | version "2.1.4" 338 | resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" 339 | integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== 340 | 341 | form-data@~2.3.2: 342 | version "2.3.3" 343 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" 344 | integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== 345 | dependencies: 346 | asynckit "^0.4.0" 347 | combined-stream "^1.0.6" 348 | mime-types "^2.1.12" 349 | 350 | function-bind@^1.1.2: 351 | version "1.1.2" 352 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" 353 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 354 | 355 | get-stream@^6.0.1: 356 | version "6.0.1" 357 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" 358 | integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== 359 | 360 | getpass@^0.1.1: 361 | version "0.1.7" 362 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 363 | integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== 364 | dependencies: 365 | assert-plus "^1.0.0" 366 | 367 | github-label-sync@^2.3.1: 368 | version "2.3.1" 369 | resolved "https://registry.yarnpkg.com/github-label-sync/-/github-label-sync-2.3.1.tgz#f9629958148348979c6a7a3547791022d61f276b" 370 | integrity sha512-3gGNc+y9OtwzR1aTlAOZKJmQ1QUzufxUG6c7rVTFLtNJvqTwyd80bOUxXuwyk2jIq7tWa0fx+Xep78BXxAU2WQ== 371 | dependencies: 372 | "@financial-times/origami-service-makefile" "^7.0.3" 373 | ajv "^8.6.3" 374 | chalk "^4.1.2" 375 | commander "^6.2.1" 376 | got "^12.5.3" 377 | js-yaml "^3.14.1" 378 | node.extend "^2.0.2" 379 | octonode "^0.10.2" 380 | 381 | got@^12.5.3: 382 | version "12.6.1" 383 | resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549" 384 | integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ== 385 | dependencies: 386 | "@sindresorhus/is" "^5.2.0" 387 | "@szmarczak/http-timer" "^5.0.1" 388 | cacheable-lookup "^7.0.0" 389 | cacheable-request "^10.2.8" 390 | decompress-response "^6.0.0" 391 | form-data-encoder "^2.1.2" 392 | get-stream "^6.0.1" 393 | http2-wrapper "^2.1.10" 394 | lowercase-keys "^3.0.0" 395 | p-cancelable "^3.0.0" 396 | responselike "^3.0.0" 397 | 398 | har-schema@^2.0.0: 399 | version "2.0.0" 400 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" 401 | integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== 402 | 403 | har-validator@~5.1.3: 404 | version "5.1.5" 405 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" 406 | integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== 407 | dependencies: 408 | ajv "^6.12.3" 409 | har-schema "^2.0.0" 410 | 411 | has-flag@^4.0.0: 412 | version "4.0.0" 413 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 414 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 415 | 416 | hasown@^2.0.0: 417 | version "2.0.0" 418 | resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" 419 | integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== 420 | dependencies: 421 | function-bind "^1.1.2" 422 | 423 | http-cache-semantics@^4.1.1: 424 | version "4.1.1" 425 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" 426 | integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== 427 | 428 | http-signature@~1.2.0: 429 | version "1.2.0" 430 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" 431 | integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== 432 | dependencies: 433 | assert-plus "^1.0.0" 434 | jsprim "^1.2.2" 435 | sshpk "^1.7.0" 436 | 437 | http2-wrapper@^2.1.10: 438 | version "2.2.1" 439 | resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" 440 | integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== 441 | dependencies: 442 | quick-lru "^5.1.1" 443 | resolve-alpn "^1.2.0" 444 | 445 | is-typedarray@~1.0.0: 446 | version "1.0.0" 447 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 448 | integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== 449 | 450 | is@^3.3.0: 451 | version "3.3.0" 452 | resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" 453 | integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== 454 | 455 | isstream@~0.1.2: 456 | version "0.1.2" 457 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 458 | integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== 459 | 460 | js-yaml@^3.14.1: 461 | version "3.14.1" 462 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" 463 | integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== 464 | dependencies: 465 | argparse "^1.0.7" 466 | esprima "^4.0.0" 467 | 468 | jsbn@~0.1.0: 469 | version "0.1.1" 470 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 471 | integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== 472 | 473 | json-buffer@3.0.1: 474 | version "3.0.1" 475 | resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" 476 | integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== 477 | 478 | json-schema-traverse@^0.4.1: 479 | version "0.4.1" 480 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 481 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 482 | 483 | json-schema-traverse@^1.0.0: 484 | version "1.0.0" 485 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" 486 | integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== 487 | 488 | json-schema@0.4.0: 489 | version "0.4.0" 490 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" 491 | integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== 492 | 493 | json-stringify-safe@~5.0.1: 494 | version "5.0.1" 495 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 496 | integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== 497 | 498 | jsprim@^1.2.2: 499 | version "1.4.2" 500 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" 501 | integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== 502 | dependencies: 503 | assert-plus "1.0.0" 504 | extsprintf "1.3.0" 505 | json-schema "0.4.0" 506 | verror "1.10.0" 507 | 508 | keyv@^4.5.3: 509 | version "4.5.4" 510 | resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" 511 | integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== 512 | dependencies: 513 | json-buffer "3.0.1" 514 | 515 | lowercase-keys@^3.0.0: 516 | version "3.0.0" 517 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" 518 | integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== 519 | 520 | make-error@^1.1.1: 521 | version "1.3.6" 522 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 523 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 524 | 525 | mime-db@1.52.0: 526 | version "1.52.0" 527 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 528 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 529 | 530 | mime-types@^2.1.12, mime-types@~2.1.19: 531 | version "2.1.35" 532 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 533 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 534 | dependencies: 535 | mime-db "1.52.0" 536 | 537 | mimic-response@^3.1.0: 538 | version "3.1.0" 539 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" 540 | integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== 541 | 542 | mimic-response@^4.0.0: 543 | version "4.0.0" 544 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" 545 | integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== 546 | 547 | node.extend@^2.0.2: 548 | version "2.0.3" 549 | resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.3.tgz#01cff7d142996aee6bb6bf506d065405ecd4371d" 550 | integrity sha512-xwADg/okH48PvBmRZyoX8i8GJaKuJ1CqlqotlZOhUio8egD1P5trJupHKBzcPjSF9ifK2gPcEICRBnkfPqQXZw== 551 | dependencies: 552 | hasown "^2.0.0" 553 | is "^3.3.0" 554 | 555 | normalize-url@^8.0.0: 556 | version "8.0.0" 557 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.0.tgz#593dbd284f743e8dcf6a5ddf8fadff149c82701a" 558 | integrity sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw== 559 | 560 | oauth-sign@~0.9.0: 561 | version "0.9.0" 562 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" 563 | integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== 564 | 565 | octonode@^0.10.2: 566 | version "0.10.2" 567 | resolved "https://registry.yarnpkg.com/octonode/-/octonode-0.10.2.tgz#2b773866f44f03a2c544741d9b0df2d2c1b4f0cf" 568 | integrity sha512-lxKJxAvrw3BuM0Wu3A/TRyFkYxMFWbMm8p7fDO3EoG9KDgOy53d91bjlGR1mmNk1EoF5LjGBx7BmIB+PfmMKLQ== 569 | dependencies: 570 | bluebird "^3.5.0" 571 | deep-extend "^0.6.0" 572 | randomstring "^1.1.5" 573 | request "^2.72.0" 574 | 575 | p-cancelable@^3.0.0: 576 | version "3.0.0" 577 | resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" 578 | integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== 579 | 580 | performance-now@^2.1.0: 581 | version "2.1.0" 582 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 583 | integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== 584 | 585 | psl@^1.1.28: 586 | version "1.9.0" 587 | resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" 588 | integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== 589 | 590 | punycode@^2.1.0, punycode@^2.1.1: 591 | version "2.3.1" 592 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" 593 | integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== 594 | 595 | qs@~6.5.2: 596 | version "6.5.3" 597 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" 598 | integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== 599 | 600 | quick-lru@^5.1.1: 601 | version "5.1.1" 602 | resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" 603 | integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== 604 | 605 | randombytes@2.0.3: 606 | version "2.0.3" 607 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" 608 | integrity sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg== 609 | 610 | randomstring@^1.1.5: 611 | version "1.3.0" 612 | resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.3.0.tgz#1bf9d730066899e70aee3285573f84708278683d" 613 | integrity sha512-gY7aQ4i1BgwZ8I1Op4YseITAyiDiajeZOPQUbIq9TPGPhUm5FX59izIaOpmKbME1nmnEiABf28d9K2VSii6BBg== 614 | dependencies: 615 | randombytes "2.0.3" 616 | 617 | request@^2.72.0: 618 | version "2.88.2" 619 | resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" 620 | integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== 621 | dependencies: 622 | aws-sign2 "~0.7.0" 623 | aws4 "^1.8.0" 624 | caseless "~0.12.0" 625 | combined-stream "~1.0.6" 626 | extend "~3.0.2" 627 | forever-agent "~0.6.1" 628 | form-data "~2.3.2" 629 | har-validator "~5.1.3" 630 | http-signature "~1.2.0" 631 | is-typedarray "~1.0.0" 632 | isstream "~0.1.2" 633 | json-stringify-safe "~5.0.1" 634 | mime-types "~2.1.19" 635 | oauth-sign "~0.9.0" 636 | performance-now "^2.1.0" 637 | qs "~6.5.2" 638 | safe-buffer "^5.1.2" 639 | tough-cookie "~2.5.0" 640 | tunnel-agent "^0.6.0" 641 | uuid "^3.3.2" 642 | 643 | require-from-string@^2.0.2: 644 | version "2.0.2" 645 | resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" 646 | integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== 647 | 648 | resolve-alpn@^1.2.0: 649 | version "1.2.1" 650 | resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" 651 | integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== 652 | 653 | responselike@^3.0.0: 654 | version "3.0.0" 655 | resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" 656 | integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg== 657 | dependencies: 658 | lowercase-keys "^3.0.0" 659 | 660 | safe-buffer@^5.0.1, safe-buffer@^5.1.2: 661 | version "5.2.1" 662 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 663 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 664 | 665 | safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 666 | version "2.1.2" 667 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 668 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 669 | 670 | sprintf-js@~1.0.2: 671 | version "1.0.3" 672 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 673 | integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== 674 | 675 | sshpk@^1.7.0: 676 | version "1.18.0" 677 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" 678 | integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== 679 | dependencies: 680 | asn1 "~0.2.3" 681 | assert-plus "^1.0.0" 682 | bcrypt-pbkdf "^1.0.0" 683 | dashdash "^1.12.0" 684 | ecc-jsbn "~0.1.1" 685 | getpass "^0.1.1" 686 | jsbn "~0.1.0" 687 | safer-buffer "^2.0.2" 688 | tweetnacl "~0.14.0" 689 | 690 | supports-color@^7.1.0: 691 | version "7.2.0" 692 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 693 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 694 | dependencies: 695 | has-flag "^4.0.0" 696 | 697 | tough-cookie@~2.5.0: 698 | version "2.5.0" 699 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" 700 | integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== 701 | dependencies: 702 | psl "^1.1.28" 703 | punycode "^2.1.1" 704 | 705 | ts-node@^10.9.1: 706 | version "10.9.1" 707 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" 708 | integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== 709 | dependencies: 710 | "@cspotcode/source-map-support" "^0.8.0" 711 | "@tsconfig/node10" "^1.0.7" 712 | "@tsconfig/node12" "^1.0.7" 713 | "@tsconfig/node14" "^1.0.0" 714 | "@tsconfig/node16" "^1.0.2" 715 | acorn "^8.4.1" 716 | acorn-walk "^8.1.1" 717 | arg "^4.1.0" 718 | create-require "^1.1.0" 719 | diff "^4.0.1" 720 | make-error "^1.1.1" 721 | v8-compile-cache-lib "^3.0.1" 722 | yn "3.1.1" 723 | 724 | tunnel-agent@^0.6.0: 725 | version "0.6.0" 726 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 727 | integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== 728 | dependencies: 729 | safe-buffer "^5.0.1" 730 | 731 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 732 | version "0.14.5" 733 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 734 | integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== 735 | 736 | typescript@^5.2.2: 737 | version "5.2.2" 738 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" 739 | integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== 740 | 741 | undici-types@~5.26.4: 742 | version "5.26.5" 743 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" 744 | integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== 745 | 746 | uri-js@^4.2.2: 747 | version "4.4.1" 748 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" 749 | integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 750 | dependencies: 751 | punycode "^2.1.0" 752 | 753 | uuid@^3.3.2: 754 | version "3.4.0" 755 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 756 | integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== 757 | 758 | v8-compile-cache-lib@^3.0.1: 759 | version "3.0.1" 760 | resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" 761 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 762 | 763 | verror@1.10.0: 764 | version "1.10.0" 765 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" 766 | integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== 767 | dependencies: 768 | assert-plus "^1.0.0" 769 | core-util-is "1.0.2" 770 | extsprintf "^1.2.0" 771 | 772 | yaml@^2.3.4: 773 | version "2.3.4" 774 | resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" 775 | integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== 776 | 777 | yn@3.1.1: 778 | version "3.1.1" 779 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 780 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 781 | --------------------------------------------------------------------------------