From 2d672cead167187cb714cd89b638c0884ba58f03 Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Mon, 14 Mar 2022 12:58:57 -0700 Subject: [PATCH] Add a stopgap idb replacement (#6061) --- .changeset/empty-falcons-sit.md | 9 + packages/app/package.json | 1 - packages/app/src/indexeddb.ts | 14 +- packages/installations-compat/package.json | 1 - packages/installations/package.json | 1 - .../installations/src/helpers/idb-manager.ts | 20 +- packages/messaging/package.json | 1 - .../src/helpers/migrate-old-database.test.ts | 15 +- .../src/helpers/migrate-old-database.ts | 138 +++++++------- .../messaging/src/internals/idb-manager.ts | 34 ++-- packages/messaging/src/testing/setup.ts | 4 +- packages/util/index.node.ts | 3 + packages/util/index.ts | 1 + packages/util/src/indexeddb.ts | 174 ++++++++++++++++++ renovate.json | 1 - yarn.lock | 5 - 16 files changed, 303 insertions(+), 119 deletions(-) create mode 100644 .changeset/empty-falcons-sit.md create mode 100644 packages/util/src/indexeddb.ts diff --git a/.changeset/empty-falcons-sit.md b/.changeset/empty-falcons-sit.md new file mode 100644 index 00000000000..776b835a93d --- /dev/null +++ b/.changeset/empty-falcons-sit.md @@ -0,0 +1,9 @@ +--- +'@firebase/app': patch +'@firebase/installations': patch +'@firebase/installations-compat': patch +'@firebase/messaging': patch +'@firebase/util': minor +--- + +Remove idb dependency and replace with our own code. diff --git a/packages/app/package.json b/packages/app/package.json index 7132579586e..4ed4b1d3566 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -40,7 +40,6 @@ "@firebase/util": "1.4.3", "@firebase/logger": "0.3.2", "@firebase/component": "0.5.10", - "idb": "3.0.2", "tslib": "^2.1.0" }, "license": "Apache-2.0", diff --git a/packages/app/src/indexeddb.ts b/packages/app/src/indexeddb.ts index 96c1b208bd9..e136d895f1f 100644 --- a/packages/app/src/indexeddb.ts +++ b/packages/app/src/indexeddb.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { DB, openDb } from 'idb'; +import { DBWrapper, openDB } from '@firebase/util'; import { AppError, ERROR_FACTORY } from './errors'; import { FirebaseApp } from './public-types'; import { HeartbeatsInIndexedDB } from './types'; @@ -23,18 +23,18 @@ const DB_NAME = 'firebase-heartbeat-database'; const DB_VERSION = 1; const STORE_NAME = 'firebase-heartbeat-store'; -let dbPromise: Promise | null = null; -function getDbPromise(): Promise { +let dbPromise: Promise | null = null; +function getDbPromise(): Promise { if (!dbPromise) { - dbPromise = openDb(DB_NAME, DB_VERSION, upgradeDB => { + dbPromise = openDB(DB_NAME, DB_VERSION, (db, oldVersion) => { // We don't use 'break' in this switch statement, the fall-through // behavior is what we want, because if there are multiple versions between // the old version and the current version, we want ALL the migrations // that correspond to those versions to run, not only the last one. // eslint-disable-next-line default-case - switch (upgradeDB.oldVersion) { + switch (oldVersion) { case 0: - upgradeDB.createObjectStore(STORE_NAME); + db.createObjectStore(STORE_NAME); } }).catch(e => { throw ERROR_FACTORY.create(AppError.STORAGE_OPEN, { @@ -53,7 +53,7 @@ export async function readHeartbeatsFromIndexedDB( return db .transaction(STORE_NAME) .objectStore(STORE_NAME) - .get(computeKey(app)); + .get(computeKey(app)) as Promise; } catch (e) { throw ERROR_FACTORY.create(AppError.STORAGE_GET, { originalErrorMessage: e.message diff --git a/packages/installations-compat/package.json b/packages/installations-compat/package.json index 2453c54fb3c..7930d322d3c 100644 --- a/packages/installations-compat/package.json +++ b/packages/installations-compat/package.json @@ -61,7 +61,6 @@ "@firebase/installations-types": "0.4.0", "@firebase/util": "1.4.3", "@firebase/component": "0.5.10", - "idb": "3.0.2", "tslib": "^2.1.0" } } \ No newline at end of file diff --git a/packages/installations/package.json b/packages/installations/package.json index 5280332f31a..6f5ae7b3245 100644 --- a/packages/installations/package.json +++ b/packages/installations/package.json @@ -64,7 +64,6 @@ "dependencies": { "@firebase/util": "1.4.3", "@firebase/component": "0.5.10", - "idb": "3.0.2", "tslib": "^2.1.0" } } \ No newline at end of file diff --git a/packages/installations/src/helpers/idb-manager.ts b/packages/installations/src/helpers/idb-manager.ts index bc30563fa06..6c502ae4bb9 100644 --- a/packages/installations/src/helpers/idb-manager.ts +++ b/packages/installations/src/helpers/idb-manager.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { DB, openDb } from 'idb'; +import { DBWrapper, openDB } from '@firebase/util'; import { AppConfig } from '../interfaces/installation-impl'; import { InstallationEntry } from '../interfaces/installation-entry'; import { getKey } from '../util/get-key'; @@ -25,18 +25,18 @@ const DATABASE_NAME = 'firebase-installations-database'; const DATABASE_VERSION = 1; const OBJECT_STORE_NAME = 'firebase-installations-store'; -let dbPromise: Promise | null = null; -function getDbPromise(): Promise { +let dbPromise: Promise | null = null; +function getDbPromise(): Promise { if (!dbPromise) { - dbPromise = openDb(DATABASE_NAME, DATABASE_VERSION, upgradeDB => { + dbPromise = openDB(DATABASE_NAME, DATABASE_VERSION, (db, oldVersion) => { // We don't use 'break' in this switch statement, the fall-through // behavior is what we want, because if there are multiple versions between // the old version and the current version, we want ALL the migrations // that correspond to those versions to run, not only the last one. // eslint-disable-next-line default-case - switch (upgradeDB.oldVersion) { + switch (oldVersion) { case 0: - upgradeDB.createObjectStore(OBJECT_STORE_NAME); + db.createObjectStore(OBJECT_STORE_NAME); } }); } @@ -52,7 +52,7 @@ export async function get( return db .transaction(OBJECT_STORE_NAME) .objectStore(OBJECT_STORE_NAME) - .get(key); + .get(key) as Promise; } /** Assigns or overwrites the record for the given key with the given value. */ @@ -64,7 +64,7 @@ export async function set( const db = await getDbPromise(); const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); const objectStore = tx.objectStore(OBJECT_STORE_NAME); - const oldValue = await objectStore.get(key); + const oldValue = (await objectStore.get(key)) as InstallationEntry; await objectStore.put(value, key); await tx.complete; @@ -98,7 +98,9 @@ export async function update( const db = await getDbPromise(); const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); const store = tx.objectStore(OBJECT_STORE_NAME); - const oldValue: InstallationEntry | undefined = await store.get(key); + const oldValue: InstallationEntry | undefined = (await store.get( + key + )) as InstallationEntry; const newValue = updateFn(oldValue); if (newValue === undefined) { diff --git a/packages/messaging/package.json b/packages/messaging/package.json index 5e67b8cc2f0..31539754701 100644 --- a/packages/messaging/package.json +++ b/packages/messaging/package.json @@ -56,7 +56,6 @@ "@firebase/messaging-interop-types": "0.1.0", "@firebase/util": "1.4.3", "@firebase/component": "0.5.10", - "idb": "3.0.2", "tslib": "^2.1.0" }, "devDependencies": { diff --git a/packages/messaging/src/helpers/migrate-old-database.test.ts b/packages/messaging/src/helpers/migrate-old-database.test.ts index 020295ca2fd..2ae03c7545d 100644 --- a/packages/messaging/src/helpers/migrate-old-database.test.ts +++ b/packages/messaging/src/helpers/migrate-old-database.test.ts @@ -28,7 +28,7 @@ import { FakePushSubscription } from '../testing/fakes/service-worker'; import { base64ToArray } from './array-base64-translator'; import { expect } from 'chai'; import { getFakeTokenDetails } from '../testing/fakes/token-details'; -import { openDb } from 'idb'; +import { openDB } from '@firebase/util'; describe('migrateOldDb', () => { it("does nothing if old DB didn't exist", async () => { @@ -179,14 +179,11 @@ describe('migrateOldDb', () => { }); async function put(version: number, value: object): Promise { - const db = await openDb('fcm_token_details_db', version, upgradeDb => { - if (upgradeDb.oldVersion === 0) { - const objectStore = upgradeDb.createObjectStore( - 'fcm_token_object_Store', - { - keyPath: 'swScope' - } - ); + const db = await openDB('fcm_token_details_db', version, (db, oldVersion) => { + if (oldVersion === 0) { + const objectStore = db.createObjectStore('fcm_token_object_Store', { + keyPath: 'swScope' + }); objectStore.createIndex('fcmSenderId', 'fcmSenderId', { unique: false }); diff --git a/packages/messaging/src/helpers/migrate-old-database.ts b/packages/messaging/src/helpers/migrate-old-database.ts index f7a5977502e..0117faca131 100644 --- a/packages/messaging/src/helpers/migrate-old-database.ts +++ b/packages/messaging/src/helpers/migrate-old-database.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { deleteDb, openDb } from 'idb'; +import { deleteDB, openDB } from '@firebase/util'; import { TokenDetails } from '../interfaces/token-details'; import { arrayToBase64 } from './array-base64-translator'; @@ -88,83 +88,87 @@ export async function migrateOldDatabase( let tokenDetails: TokenDetails | null = null; - const db = await openDb(OLD_DB_NAME, OLD_DB_VERSION, async db => { - if (db.oldVersion < 2) { - // Database too old, skip migration. - return; - } - - if (!db.objectStoreNames.contains(OLD_OBJECT_STORE_NAME)) { - // Database did not exist. Nothing to do. - return; - } - - const objectStore = db.transaction.objectStore(OLD_OBJECT_STORE_NAME); - const value = await objectStore.index('fcmSenderId').get(senderId); - await objectStore.clear(); + const db = await openDB( + OLD_DB_NAME, + OLD_DB_VERSION, + async (db, oldVersion, newVersion, upgradeTransaction) => { + if (oldVersion < 2) { + // Database too old, skip migration. + return; + } - if (!value) { - // No entry in the database, nothing to migrate. - return; - } + if (!db.objectStoreNames.contains(OLD_OBJECT_STORE_NAME)) { + // Database did not exist. Nothing to do. + return; + } - if (db.oldVersion === 2) { - const oldDetails = value as V2TokenDetails; + const objectStore = upgradeTransaction.objectStore(OLD_OBJECT_STORE_NAME); + const value = await objectStore.index('fcmSenderId').get(senderId); + await objectStore.clear(); - if (!oldDetails.auth || !oldDetails.p256dh || !oldDetails.endpoint) { + if (!value) { + // No entry in the database, nothing to migrate. return; } - tokenDetails = { - token: oldDetails.fcmToken, - createTime: oldDetails.createTime ?? Date.now(), - subscriptionOptions: { - auth: oldDetails.auth, - p256dh: oldDetails.p256dh, - endpoint: oldDetails.endpoint, - swScope: oldDetails.swScope, - vapidKey: - typeof oldDetails.vapidKey === 'string' - ? oldDetails.vapidKey - : arrayToBase64(oldDetails.vapidKey) - } - }; - } else if (db.oldVersion === 3) { - const oldDetails = value as V3TokenDetails; - - tokenDetails = { - token: oldDetails.fcmToken, - createTime: oldDetails.createTime, - subscriptionOptions: { - auth: arrayToBase64(oldDetails.auth), - p256dh: arrayToBase64(oldDetails.p256dh), - endpoint: oldDetails.endpoint, - swScope: oldDetails.swScope, - vapidKey: arrayToBase64(oldDetails.vapidKey) - } - }; - } else if (db.oldVersion === 4) { - const oldDetails = value as V4TokenDetails; - - tokenDetails = { - token: oldDetails.fcmToken, - createTime: oldDetails.createTime, - subscriptionOptions: { - auth: arrayToBase64(oldDetails.auth), - p256dh: arrayToBase64(oldDetails.p256dh), - endpoint: oldDetails.endpoint, - swScope: oldDetails.swScope, - vapidKey: arrayToBase64(oldDetails.vapidKey) + if (oldVersion === 2) { + const oldDetails = value as V2TokenDetails; + + if (!oldDetails.auth || !oldDetails.p256dh || !oldDetails.endpoint) { + return; } - }; + + tokenDetails = { + token: oldDetails.fcmToken, + createTime: oldDetails.createTime ?? Date.now(), + subscriptionOptions: { + auth: oldDetails.auth, + p256dh: oldDetails.p256dh, + endpoint: oldDetails.endpoint, + swScope: oldDetails.swScope, + vapidKey: + typeof oldDetails.vapidKey === 'string' + ? oldDetails.vapidKey + : arrayToBase64(oldDetails.vapidKey) + } + }; + } else if (oldVersion === 3) { + const oldDetails = value as V3TokenDetails; + + tokenDetails = { + token: oldDetails.fcmToken, + createTime: oldDetails.createTime, + subscriptionOptions: { + auth: arrayToBase64(oldDetails.auth), + p256dh: arrayToBase64(oldDetails.p256dh), + endpoint: oldDetails.endpoint, + swScope: oldDetails.swScope, + vapidKey: arrayToBase64(oldDetails.vapidKey) + } + }; + } else if (oldVersion === 4) { + const oldDetails = value as V4TokenDetails; + + tokenDetails = { + token: oldDetails.fcmToken, + createTime: oldDetails.createTime, + subscriptionOptions: { + auth: arrayToBase64(oldDetails.auth), + p256dh: arrayToBase64(oldDetails.p256dh), + endpoint: oldDetails.endpoint, + swScope: oldDetails.swScope, + vapidKey: arrayToBase64(oldDetails.vapidKey) + } + }; + } } - }); + ); db.close(); // Delete all old databases. - await deleteDb(OLD_DB_NAME); - await deleteDb('fcm_vapid_details_db'); - await deleteDb('undefined'); + await deleteDB(OLD_DB_NAME); + await deleteDB('fcm_vapid_details_db'); + await deleteDB('undefined'); return checkTokenDetails(tokenDetails) ? tokenDetails : null; } diff --git a/packages/messaging/src/internals/idb-manager.ts b/packages/messaging/src/internals/idb-manager.ts index 4ddebf5ae96..1aa82734757 100644 --- a/packages/messaging/src/internals/idb-manager.ts +++ b/packages/messaging/src/internals/idb-manager.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { DB, deleteDb, openDb } from 'idb'; +import { DBWrapper, deleteDB, openDB } from '@firebase/util'; import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies'; import { TokenDetails } from '../interfaces/token-details'; @@ -26,19 +26,23 @@ export const DATABASE_NAME = 'firebase-messaging-database'; const DATABASE_VERSION = 1; const OBJECT_STORE_NAME = 'firebase-messaging-store'; -let dbPromise: Promise | null = null; -function getDbPromise(): Promise { +let dbPromise: Promise | null = null; +function getDbPromise(): Promise { if (!dbPromise) { - dbPromise = openDb(DATABASE_NAME, DATABASE_VERSION, upgradeDb => { - // We don't use 'break' in this switch statement, the fall-through behavior is what we want, - // because if there are multiple versions between the old version and the current version, we - // want ALL the migrations that correspond to those versions to run, not only the last one. - // eslint-disable-next-line default-case - switch (upgradeDb.oldVersion) { - case 0: - upgradeDb.createObjectStore(OBJECT_STORE_NAME); + dbPromise = openDB( + DATABASE_NAME, + DATABASE_VERSION, + (upgradeDb, oldVersion) => { + // We don't use 'break' in this switch statement, the fall-through behavior is what we want, + // because if there are multiple versions between the old version and the current version, we + // want ALL the migrations that correspond to those versions to run, not only the last one. + // eslint-disable-next-line default-case + switch (oldVersion) { + case 0: + upgradeDb.createObjectStore(OBJECT_STORE_NAME); + } } - }); + ); } return dbPromise; } @@ -49,10 +53,10 @@ export async function dbGet( ): Promise { const key = getKey(firebaseDependencies); const db = await getDbPromise(); - const tokenDetails = await db + const tokenDetails = (await db .transaction(OBJECT_STORE_NAME) .objectStore(OBJECT_STORE_NAME) - .get(key); + .get(key)) as TokenDetails; if (tokenDetails) { return tokenDetails; @@ -96,7 +100,7 @@ export async function dbRemove( export async function dbDelete(): Promise { if (dbPromise) { (await dbPromise).close(); - await deleteDb(DATABASE_NAME); + await deleteDB(DATABASE_NAME); dbPromise = null; } } diff --git a/packages/messaging/src/testing/setup.ts b/packages/messaging/src/testing/setup.ts index eb4802a11a1..7aa969119c6 100644 --- a/packages/messaging/src/testing/setup.ts +++ b/packages/messaging/src/testing/setup.ts @@ -19,7 +19,7 @@ import chaiAsPromised from 'chai-as-promised'; import sinonChai from 'sinon-chai'; import { dbDelete } from '../internals/idb-manager'; -import { deleteDb } from 'idb'; +import { deleteDB } from '@firebase/util'; import { restore } from 'sinon'; import { use } from 'chai'; @@ -29,5 +29,5 @@ use(sinonChai); afterEach(async () => { restore(); await dbDelete(); - await deleteDb('fcm_token_details_db'); + await deleteDB('fcm_token_details_db'); }); diff --git a/packages/util/index.node.ts b/packages/util/index.node.ts index 8dace3b8e1e..e27c304145c 100644 --- a/packages/util/index.node.ts +++ b/packages/util/index.node.ts @@ -39,3 +39,6 @@ export * from './src/utf8'; export * from './src/exponential_backoff'; export * from './src/formatters'; export * from './src/compat'; +// This can't be used in Node but it will cause errors if libraries import +// these methods and they aren't here. +export * from './src/indexeddb'; diff --git a/packages/util/index.ts b/packages/util/index.ts index 00d661734b8..0cf518fbd81 100644 --- a/packages/util/index.ts +++ b/packages/util/index.ts @@ -34,3 +34,4 @@ export * from './src/utf8'; export * from './src/exponential_backoff'; export * from './src/formatters'; export * from './src/compat'; +export * from './src/indexeddb'; diff --git a/packages/util/src/indexeddb.ts b/packages/util/src/indexeddb.ts new file mode 100644 index 00000000000..b2d4546f18e --- /dev/null +++ b/packages/util/src/indexeddb.ts @@ -0,0 +1,174 @@ +/** + * @license + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function promisifyRequest( + request: IDBRequest, + errorMessage: string +): Promise { + return new Promise((resolve, reject) => { + request.onsuccess = event => { + resolve((event.target as IDBRequest).result); + }; + request.onerror = event => { + reject(`${errorMessage}: ${(event.target as IDBRequest).error?.message}`); + }; + }); +} + +export class DBWrapper { + objectStoreNames: DOMStringList; + constructor(private _db: IDBDatabase) { + this.objectStoreNames = this._db.objectStoreNames; + } + transaction( + storeNames: string[] | string, + mode?: IDBTransactionMode + ): TransactionWrapper { + return new TransactionWrapper( + this._db.transaction.call(this._db, storeNames, mode) + ); + } + createObjectStore( + storeName: string, + options?: IDBObjectStoreParameters + ): ObjectStoreWrapper { + return new ObjectStoreWrapper( + this._db.createObjectStore(storeName, options) + ); + } + close(): void { + this._db.close(); + } +} + +class TransactionWrapper { + complete: Promise; + constructor(private _transaction: IDBTransaction) { + this.complete = new Promise((resolve, reject) => { + this._transaction.oncomplete = function () { + resolve(); + }; + this._transaction.onerror = () => { + reject(this._transaction.error); + }; + this._transaction.onabort = () => { + reject(this._transaction.error); + }; + }); + } + objectStore(storeName: string): ObjectStoreWrapper { + return new ObjectStoreWrapper(this._transaction.objectStore(storeName)); + } +} + +class ObjectStoreWrapper { + constructor(private _store: IDBObjectStore) {} + index(name: string): IndexWrapper { + return new IndexWrapper(this._store.index(name)); + } + createIndex( + name: string, + keypath: string, + options: IDBIndexParameters + ): IndexWrapper { + return new IndexWrapper(this._store.createIndex(name, keypath, options)); + } + get(key: string): Promise { + const request = this._store.get(key); + return promisifyRequest(request, 'Error reading from IndexedDB'); + } + put(value: unknown, key?: string): Promise { + const request = this._store.put(value, key); + return promisifyRequest(request, 'Error writing to IndexedDB'); + } + delete(key: string): Promise { + const request = this._store.delete(key); + return promisifyRequest(request, 'Error deleting from IndexedDB'); + } + clear(): Promise { + const request = this._store.clear(); + return promisifyRequest(request, 'Error clearing IndexedDB object store'); + } +} + +class IndexWrapper { + constructor(private _index: IDBIndex) {} + get(key: string): Promise { + const request = this._index.get(key); + return promisifyRequest(request, 'Error reading from IndexedDB'); + } +} + +export function openDB( + dbName: string, + dbVersion: number, + upgradeCallback: ( + db: DBWrapper, + oldVersion: number, + newVersion: number | null, + transaction: TransactionWrapper + ) => void +): Promise { + return new Promise((resolve, reject) => { + try { + const request = indexedDB.open(dbName, dbVersion); + + request.onsuccess = event => { + resolve(new DBWrapper((event.target as IDBOpenDBRequest).result)); + }; + + request.onerror = event => { + reject( + `Error opening indexedDB: ${ + (event.target as IDBRequest).error?.message + }` + ); + }; + + request.onupgradeneeded = event => { + upgradeCallback( + new DBWrapper(request.result), + event.oldVersion, + event.newVersion, + new TransactionWrapper(request.transaction!) + ); + }; + } catch (e) { + reject(`Error opening indexedDB: ${e.message}`); + } + }); +} + +export async function deleteDB(dbName: string): Promise { + return new Promise((resolve, reject) => { + try { + const request = indexedDB.deleteDatabase(dbName); + request.onsuccess = () => { + resolve(); + }; + request.onerror = event => { + reject( + `Error deleting indexedDB database "${dbName}": ${ + (event.target as IDBRequest).error?.message + }` + ); + }; + } catch (e) { + reject(`Error deleting indexedDB database "${dbName}": ${e.message}`); + } + }); +} diff --git a/renovate.json b/renovate.json index 557ce0d9a51..b37b5b6500d 100644 --- a/renovate.json +++ b/renovate.json @@ -17,7 +17,6 @@ "protractor", "long", "rollup-plugin-copy-assets", - "idb", "whatwg-fetch", "typedoc", "@microsoft/tsdoc" diff --git a/yarn.lock b/yarn.lock index b30eb425bea..fdf96b07589 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8993,11 +8993,6 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -idb@3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384" - integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw== - ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"