diff --git a/.eslintrc.js b/.eslintrc.js index f8514a305..1735739e3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,9 @@ module.exports = { plugins: [ "matrix-org", ], - extends: ["plugin:matrix-org/javascript"], + extends: [ + "plugin:matrix-org/babel", + ], env: { browser: true, node: true, @@ -31,14 +33,26 @@ module.exports = { "no-console": "error", }, overrides: [{ - "files": ["src/**/*.ts"], - "extends": ["plugin:matrix-org/typescript"], - "rules": { + files: [ + "**/*.ts", + ], + extends: [ + "plugin:matrix-org/typescript", + ], + rules: { + // TypeScript has its own version of this + "@babel/no-invalid-this": "off", + // We're okay being explicit at the moment "@typescript-eslint/no-empty-interface": "off", - // While we're converting to ts we make heavy use of this + // We disable this while we're transitioning "@typescript-eslint/no-explicit-any": "off", + // We'd rather not do this but we do + "@typescript-eslint/ban-ts-comment": "off", + "quotes": "off", + // We use a `logger` intermediary module + "no-console": "error", }, }], }; diff --git a/spec/integ/matrix-client-crypto.spec.js b/spec/integ/matrix-client-crypto.spec.js index c86e0e60e..cfe85678b 100644 --- a/spec/integ/matrix-client-crypto.spec.js +++ b/spec/integ/matrix-client-crypto.spec.js @@ -28,10 +28,10 @@ limitations under the License. // load olm before the sdk if possible import '../olm-loader'; -import {logger} from '../../src/logger'; +import { logger } from '../../src/logger'; import * as testUtils from "../test-utils"; -import {TestClient} from "../TestClient"; -import {CRYPTO_ENABLED} from "../../src/client"; +import { TestClient } from "../TestClient"; +import { CRYPTO_ENABLED } from "../../src/client"; let aliTestClient; const roomId = "!room:localhost"; @@ -75,7 +75,7 @@ function expectAliQueryKeys() { ); const result = {}; result[bobUserId] = bobKeys; - return {device_keys: result}; + return { device_keys: result }; }); return aliTestClient.httpBackend.flush("/keys/query", 1); } @@ -103,7 +103,7 @@ function expectBobQueryKeys() { ); const result = {}; result[aliUserId] = aliKeys; - return {device_keys: result}; + return { device_keys: result }; }); return bobTestClient.httpBackend.flush("/keys/query", 1); } @@ -132,7 +132,7 @@ function expectAliClaimKeys() { result[bobUserId] = {}; result[bobUserId][bobDeviceId] = {}; result[bobUserId][bobDeviceId][keyId] = keys[keyId]; - return {one_time_keys: result}; + return { one_time_keys: result }; }); }).then(() => { // it can take a while to process the key query, so give it some extra @@ -144,7 +144,6 @@ function expectAliClaimKeys() { }); } - function aliDownloadsKeys() { // can't query keys before bob has uploaded them expect(bobTestClient.getSigningKey()).toBeTruthy(); @@ -269,7 +268,7 @@ function expectBobSendMessageRequest() { function sendMessage(client) { return client.sendMessage( - roomId, {msgtype: "m.text", body: "Hello, World"}, + roomId, { msgtype: "m.text", body: "Hello, World" }, ); } @@ -357,7 +356,6 @@ function recvMessage(httpBackend, client, sender, message) { }); } - /** * Send an initial sync response to the client (which just includes the member * list for our test room). @@ -395,7 +393,6 @@ function firstSync(testClient) { return testClient.flushSync(); } - describe("MatrixClient crypto", function() { if (!CRYPTO_ENABLED) { return; @@ -477,7 +474,7 @@ describe("MatrixClient crypto", function() { ).respond(200, function(path, content) { const result = {}; result[bobUserId] = bobKeys; - return {device_keys: result}; + return { device_keys: result }; }); return Promise.all([ @@ -519,7 +516,7 @@ describe("MatrixClient crypto", function() { ).respond(200, function(path, content) { const result = {}; result[bobUserId] = bobKeys; - return {device_keys: result}; + return { device_keys: result }; }); return Promise.all([ @@ -533,7 +530,6 @@ describe("MatrixClient crypto", function() { }); }); - it("Bob starts his client and uploads device keys and one-time keys", function() { return Promise.resolve() .then(() => bobTestClient.start()) @@ -545,7 +541,7 @@ describe("MatrixClient crypto", function() { }); it("Ali sends a message", function() { - aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}}); + aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } }); return Promise.resolve() .then(() => aliTestClient.start()) .then(() => bobTestClient.start()) @@ -555,7 +551,7 @@ describe("MatrixClient crypto", function() { }); it("Bob receives a message", function() { - aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}}); + aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } }); return Promise.resolve() .then(() => aliTestClient.start()) .then(() => bobTestClient.start()) @@ -566,7 +562,7 @@ describe("MatrixClient crypto", function() { }); it("Bob receives a message with a bogus sender", function() { - aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}}); + aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } }); return Promise.resolve() .then(() => aliTestClient.start()) .then(() => bobTestClient.start()) @@ -620,7 +616,7 @@ describe("MatrixClient crypto", function() { }); it("Ali blocks Bob's device", function() { - aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}}); + aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } }); return Promise.resolve() .then(() => aliTestClient.start()) .then(() => bobTestClient.start()) @@ -640,7 +636,7 @@ describe("MatrixClient crypto", function() { }); it("Bob receives two pre-key messages", function() { - aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}}); + aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } }); return Promise.resolve() .then(() => aliTestClient.start()) .then(() => bobTestClient.start()) @@ -653,8 +649,8 @@ describe("MatrixClient crypto", function() { }); it("Bob replies to the message", function() { - aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}}); - bobTestClient.expectKeyQuery({device_keys: {[bobUserId]: {}}}); + aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } }); + bobTestClient.expectKeyQuery({ device_keys: { [bobUserId]: {} } }); return Promise.resolve() .then(() => aliTestClient.start()) .then(() => bobTestClient.start()) @@ -672,7 +668,7 @@ describe("MatrixClient crypto", function() { it("Ali does a key query when encryption is enabled", function() { // enabling encryption in the room should make alice download devices // for both members. - aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}}); + aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } }); return Promise.resolve() .then(() => aliTestClient.start()) .then(() => firstSync(aliTestClient)) diff --git a/spec/unit/relations.spec.ts b/spec/unit/relations.spec.ts index 3be34f332..c8de24292 100644 --- a/spec/unit/relations.spec.ts +++ b/spec/unit/relations.spec.ts @@ -72,7 +72,7 @@ describe("Relations", function() { } }); - it("should emit created regardless of ordering", async function () { + it("should emit created regardless of ordering", async function() { const targetEvent = new MatrixEvent({ "sender": "@bob:example.com", "type": "m.room.message", diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index d1af1a770..885df977f 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TestClient} from '../../TestClient'; -import {MatrixCall, CallErrorCode, CallEvent} from '../../../src/webrtc/call'; +import { TestClient} from '../../TestClient'; +import { MatrixCall, CallErrorCode, CallEvent } from '../../../src/webrtc/call'; import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes'; const DUMMY_SDP = ( diff --git a/src/crypto/OutgoingRoomKeyRequestManager.js b/src/crypto/OutgoingRoomKeyRequestManager.js index 899c3ca96..7f64b5313 100644 --- a/src/crypto/OutgoingRoomKeyRequestManager.js +++ b/src/crypto/OutgoingRoomKeyRequestManager.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {logger} from '../logger'; +import { logger } from '../logger'; /** * Internal module. Management of outgoing room key requests. diff --git a/src/index.ts b/src/index.ts index e599e0065..93fca0a97 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ limitations under the License. import * as matrixcs from "./matrix"; import * as utils from "./utils"; +import { logger } from './logger'; import request from "request"; matrixcs.request(request); @@ -25,7 +26,7 @@ try { const crypto = require('crypto'); utils.setCrypto(crypto); } catch (err) { - console.log('nodejs was compiled without crypto support'); + logger.log('nodejs was compiled without crypto support'); } export * from "./matrix"; diff --git a/src/logger.ts b/src/logger.ts index 005d27665..bd1f98b25 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -36,11 +36,11 @@ const DEFAULT_NAMESPACE = "matrix"; // when logging so we always get the current value of console methods. log.methodFactory = function(methodName, logLevel, loggerName) { return function(...args) { - /* eslint-disable @babel/no-invalid-this */ + /* eslint-disable @typescript-eslint/no-invalid-this */ if (this.prefix) { args.unshift(this.prefix); } - /* eslint-enable @babel/no-invalid-this */ + /* eslint-enable @typescript-eslint/no-invalid-this */ const supportedByConsole = methodName === "error" || methodName === "warn" || methodName === "trace" || diff --git a/src/models/room.js b/src/models/room.js index 8273d0e8e..805043587 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -20,17 +20,17 @@ limitations under the License. * @module models/room */ -import {EventEmitter} from "events"; -import {EventTimelineSet} from "./event-timeline-set"; -import {EventTimeline} from "./event-timeline"; -import {getHttpUriForMxc} from "../content-repo"; +import { EventEmitter } from "events"; +import { EventTimelineSet } from "./event-timeline-set"; +import { EventTimeline } from "./event-timeline"; +import { getHttpUriForMxc } from "../content-repo"; import * as utils from "../utils"; -import {EventStatus, MatrixEvent} from "./event"; -import {RoomMember} from "./room-member"; -import {RoomSummary} from "./room-summary"; -import {logger} from '../logger'; -import {ReEmitter} from '../ReEmitter'; -import {EventType, RoomCreateTypeField, RoomType} from "../@types/event"; +import { EventStatus, MatrixEvent } from "./event"; +import { RoomMember } from "./room-member"; +import { RoomSummary } from "./room-summary"; +import { logger } from '../logger'; +import { ReEmitter } from '../ReEmitter'; +import { EventType, RoomCreateTypeField, RoomType } from "../@types/event"; import { normalize } from "../utils"; // These constants are used as sane defaults when the homeserver doesn't support @@ -59,7 +59,6 @@ function synthesizeReceipt(userId, event, receiptType) { return new MatrixEvent(fakeReceipt); } - /** * Construct a new Room. * @@ -234,7 +233,6 @@ function pendingEventsKey(roomId) { utils.inherits(Room, EventEmitter); - /** * Bulk decrypt critical events in a room * @@ -495,7 +493,6 @@ Room.prototype.getLiveTimeline = function() { return this.getUnfilteredTimelineSet().getLiveTimeline(); }; - /** * Get the timestamp of the last message in the room * @@ -634,13 +631,12 @@ Room.prototype._loadMembersFromServer = async function() { at: lastSyncToken, }); const path = utils.encodeUri("/rooms/$roomId/members?" + queryString, - {$roomId: this.roomId}); + { $roomId: this.roomId }); const http = this._client._http; const response = await http.authedRequest(undefined, "GET", path); return response.chunk; }; - Room.prototype._loadMembers = async function() { // were the members loaded from the server? let fromServer = false; @@ -653,7 +649,7 @@ Room.prototype._loadMembers = async function() { `members from server for room ${this.roomId}`); } const memberEvents = rawMembersEvents.map(this._client.getEventMapper()); - return {memberEvents, fromServer}; + return { memberEvents, fromServer }; }; /** @@ -1125,7 +1121,6 @@ Room.prototype.getInvitedAndJoinedMemberCount = function() { return calculateRoomName(this, userId, true); }; - /** * Check if the given user_id has the given membership state. * @param {string} userId The user ID to check. @@ -1282,7 +1277,6 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy, fromCache) { } }; - /** * Add a pending outgoing event to this room. * @@ -1693,7 +1687,6 @@ Room.prototype.removeEvent = function(eventId) { return removedAny; }; - /** * Recalculate various aspects of the room, including the room name and * room summary. Call this any time the room's current state is modified. @@ -1918,7 +1911,6 @@ Room.prototype._buildReceiptCache = function(receipts) { return receiptCacheByEventId; }; - /** * Add a temporary local-echo receipt to the room to reflect in the * client the fact that we've sent one. @@ -1976,7 +1968,6 @@ Room.prototype.getAccountData = function(type) { return this.accountData[type]; }; - /** * Returns whether the syncing user has permission to send a message in the room * @return {boolean} true if the user should be permitted to send diff --git a/src/models/search-result.js b/src/models/search-result.js index 2250e786f..fcf8bb43f 100644 --- a/src/models/search-result.js +++ b/src/models/search-result.js @@ -19,7 +19,7 @@ limitations under the License. * @module models/search-result */ -import {EventContext} from "./event-context"; +import { EventContext } from "./event-context"; /** * Construct a new SearchResult diff --git a/src/store/memory.js b/src/store/memory.js index 1b582ee43..809696b25 100644 --- a/src/store/memory.js +++ b/src/store/memory.js @@ -22,7 +22,7 @@ limitations under the License. * @module store/memory */ -import {User} from "../models/user"; +import { User } from "../models/user"; function isValidFilterId(filterId) { const isValidStr = typeof filterId === "string" && diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 821345cd4..7bf2f581a 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -190,6 +190,11 @@ export enum CallErrorCode { * Signalling for the call could not be sent (other than the initial invite) */ SignallingFailed = 'signalling_timeout', + + /** + * The remote party is busy + */ + UserBusy = 'user_busy' } enum ConstraintsType { @@ -732,7 +737,7 @@ export class MatrixCall extends EventEmitter { // Continue to send no reason for user hangups temporarily, until // clients understand the user_hangup reason (voip v1) if (reason !== CallErrorCode.UserHangup) content['reason'] = reason; - this.sendVoipEvent(EventType.CallHangup, {}); + this.sendVoipEvent(EventType.CallHangup, content); } /** @@ -1599,7 +1604,7 @@ export class MatrixCall extends EventEmitter { ); if (shouldTerminate) { - this.terminate(CallParty.Remote, CallErrorCode.UserHangup, true); + this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true); } else { logger.debug(`Call is in state: ${this.state}: ignoring reject`); } diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 5bc5cb4cc..6653d12a3 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -92,11 +92,7 @@ export class CallEventHandler { private onEvent = (event: MatrixEvent) => { this.client.decryptEventIfNeeded(event); // any call events or ones that might be once they're decrypted - if ( - event.getType().indexOf("m.call.") === 0 || - event.getType().indexOf("org.matrix.call.") === 0 - || event.isBeingDecrypted() - ) { + if (this.eventIsACall(event) || event.isBeingDecrypted()) { // queue up for processing once all events from this sync have been // processed (see above). this.callEventBuffer.push(event); @@ -105,7 +101,7 @@ export class CallEventHandler { if (event.isBeingDecrypted() || event.isDecryptionFailure()) { // add an event listener for once the event is decrypted. event.once("Event.decrypted", () => { - if (event.getType().indexOf("m.call.") === -1) return; + if (!this.eventIsACall(event)) return; if (this.callEventBuffer.includes(event)) { // we were waiting for that event to decrypt, so recheck the buffer @@ -123,6 +119,15 @@ export class CallEventHandler { } } + private eventIsACall(event: MatrixEvent): boolean { + const type = event.getType(); + /** + * Unstable prefixes: + * - org.matrix.call. : MSC3086 https://github.com/matrix-org/matrix-doc/pull/3086 + */ + return type.startsWith("m.call.") || type.startsWith("org.matrix.call."); + } + private handleCallEvent(event: MatrixEvent) { const content = event.getContent(); let call = content.call_id ? this.calls.get(content.call_id) : undefined; diff --git a/yarn.lock b/yarn.lock index f48a13d6b..1b414e505 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2034,15 +2034,15 @@ browserify@^17.0.0: xtend "^4.0.0" browserslist@^4.14.5, browserslist@^4.16.1: - version "4.16.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.1.tgz#bf757a2da376b3447b800a16f0f1c96358138766" - integrity sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA== + version "4.16.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== dependencies: - caniuse-lite "^1.0.30001173" - colorette "^1.2.1" - electron-to-chromium "^1.3.634" + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" escalade "^3.1.1" - node-releases "^1.1.69" + node-releases "^1.1.71" bs58@^4.0.1: version "4.0.1" @@ -2129,10 +2129,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001173: - version "1.0.30001178" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz#3ad813b2b2c7d585b0be0a2440e1e233c6eabdbc" - integrity sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ== +caniuse-lite@^1.0.30001219: + version "1.0.30001230" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" + integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== capture-exit@^2.0.0: version "2.0.0" @@ -2300,10 +2300,10 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" - integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== +colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== combine-source-map@^0.8.0, combine-source-map@~0.8.0: version "0.8.0" @@ -2715,10 +2715,10 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.634: - version "1.3.642" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz#8b884f50296c2ae2a9997f024d0e3e57facc2b94" - integrity sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ== +electron-to-chromium@^1.3.723: + version "1.3.738" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.738.tgz#aec24b091c82acbfabbdcce08076a703941d17ca" + integrity sha512-vCMf4gDOpEylPSLPLSwAEsz+R3ShP02Y3cAKMZvTqule3XcPp7tgc/0ESI7IS6ZeyBlGClE50N53fIOkcIVnpw== elliptic@^6.5.3: version "6.5.4" @@ -2845,8 +2845,8 @@ eslint-config-google@^0.14.0: integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw== "eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main": - version "0.2.0" - resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/0ae103fe9af97655be6039fc1e7ad6ea95da310b" + version "0.3.1" + resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/b55649a0f48ee27155c1968ed5050b6ddc5afdbe" eslint-rule-composer@^0.3.0: version "0.3.0" @@ -5037,10 +5037,10 @@ node-notifier@^8.0.0: uuid "^8.3.0" which "^2.0.2" -node-releases@^1.1.69: - version "1.1.70" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08" - integrity sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw== +node-releases@^1.1.71: + version "1.1.72" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" + integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== normalize-package-data@^2.5.0: version "2.5.0"