diff --git a/.aegir.js b/.aegir.js
new file mode 100644
index 0000000..05b296b
--- /dev/null
+++ b/.aegir.js
@@ -0,0 +1,25 @@
+export default {
+ tsRepo: false,
+ build: {
+ config: {
+ format: 'esm',
+ banner: {
+ js: ''
+ },
+ footer: {
+ js: ''
+ }
+ }
+ },
+ test: {
+ before: (...args) => {
+ if (args[0].runner === 'node') {
+ return {
+ env: {
+ NODE_OPTIONS: '--loader=esmock'
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8ac209c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root=true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_style = space
+indent_size = 2
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d28495b..f4be23a 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -44,8 +44,8 @@ jobs:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- run: npm install
- - run: npx aegir test -t browser --bail --cov
- - run: npx aegir test -t webworker --bail
+ - run: npx aegir test -t browser --bail --cov --files test/**/*.browser.spec.{js,cjs,mjs}
+ - run: npx aegir test -t webworker --bail --files test/**/*.browser.spec.{js,cjs,mjs}
- uses: codecov/codecov-action@v1
test-firefox:
needs: check
@@ -54,7 +54,7 @@ jobs:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- run: npm install
- - run: npx aegir test -t browser -t webworker --bail -- --browser firefox
+ - run: npx aegir test -t browser -t webworker --bail --files test/**/*.browser.spec.{js,cjs,mjs} -- --browser firefox
test-webkit:
needs: check
runs-on: ubuntu-latest
@@ -62,11 +62,11 @@ jobs:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- run: npm install
- - run: npx aegir test -t browser -t webworker --bail -- --browser webkit
+ - run: npx aegir test -t browser -t webworker --bail --files test/**/*.browser.spec.{js,cjs,mjs} -- --browser webkit
test-electron-main:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm install
- - run: npx xvfb-maybe aegir test -t electron-main --bail
+ - run: npx xvfb-maybe aegir test -t electron-main --bail --files test/**/*.browser.spec.{js,cjs,mjs}
diff --git a/README.md b/README.md
index 0879e38..e8fbc8d 100644
--- a/README.md
+++ b/README.md
@@ -42,39 +42,54 @@ npm install --save ipfs-geoip
### CDN
-Instead of a local installation (and browserification) you may request a [remote copy from jsDelivr](https://www.jsdelivr.com/package/npm/ipfs-geoip):
+Instead of a local installation (and browserification) you may request a specific
+version `N.N.N` as a [remote copy from jsDelivr](https://www.jsdelivr.com/package/npm/ipfs-geoip):
```html
-
-
+
```
-When using prebuilt bundle from CDN, `ipfs-geoip` will be exposed under `window.IpfsGeoip`
-
+The response in the console should look similar to:
+```js
+{
+ "country_name": "USA",
+ "country_code": "US",
+ "region_code": "VA",
+ "city": "Ashburn",
+ "postal_code": "20149",
+ "latitude": 39.0469,
+ "longitude": -77.4903,
+ "planet": "Earth"
+}
+```
## Usage
### With public gateways (default)
-If `ipfs` is a string or array of strings with public gateway URLs, it will be used for
+If `gateways` is a string or array of strings with public gateway URLs, it will be used for
fetching IPFS blocks as [`application/vnd.ipld.raw`](https://www.iana.org/assignments/media-types/application/vnd.ipld.raw)
-and parsing them as DAG-CBOR locally:
+and parsing them as DAG-CBOR locally via [@ipld/dag-cbor](https://www.npmjs.com/package/@ipld/dag-cbor):
```js
const geoip = require('ipfs-geoip')
const exampleIp = '66.6.44.4'
-const ipfsGw = ['https://ipfs.io', 'https://dweb.link']
+const gateways = ['https://ipfs.io', 'https://dweb.link']
try {
- const result = await geoip.lookup(ipfsGw, exampleIp)
+ const result = await geoip.lookup(gateways, exampleIp)
console.log('Result: ', result)
} catch (err) {
console.log('Error: ' + err)
}
try {
- const result = await geoip.lookupPretty(ipfsGw, '/ip4/' + exampleIp)
+ const result = await geoip.lookupPretty(gateways, '/ip4/' + exampleIp)
console.log('Pretty result: %s', result.formatted)
} catch (err) {
console.log('Error: ' + err)
diff --git a/example/lookup.js b/example/lookup.js
index b288ee8..db7bbc4 100644
--- a/example/lookup.js
+++ b/example/lookup.js
@@ -1,5 +1,4 @@
import * as geoip from '../src/index.js'
-import { create } from 'ipfs-http-client'
const ipfsGw = process?.env?.IPFS_GATEWAY || 'https://ipfs.io'
diff --git a/package-lock.json b/package-lock.json
index a6f2865..095da1b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,6 +24,7 @@
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"csv-parse": "^5.3.0",
+ "esmock": "^2.0.6",
"gauge": "^4.0.4",
"ipfs-http-client": "^58.0.1",
"multihashes": "^4.0.3",
@@ -8033,6 +8034,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/esmock": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/esmock/-/esmock-2.0.6.tgz",
+ "integrity": "sha512-2EY6isqPxZ9xm/nPz7DE5u/M5Nt3Xq4WUTOT5Dv9sfaqim6HZ6hitpEEfrSKAryA1JsnF/YljSRo2MZCtx0LTw==",
+ "dev": true,
+ "dependencies": {
+ "resolvewithplus": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=14.16.0"
+ }
+ },
"node_modules/espree": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
@@ -19000,6 +19013,15 @@
"node": ">=4"
}
},
+ "node_modules/resolvewithplus": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/resolvewithplus/-/resolvewithplus-1.0.2.tgz",
+ "integrity": "sha512-UNvTSqWpe1lhKvXb+Qo97Q/dFUtA0kDuUG/gO7S+P9H/xYZ+A9pwlAbY6Dqp2tgAeQNdHmQG5MdNL6d9/FWo6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.16.0"
+ }
+ },
"node_modules/responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
@@ -27706,6 +27728,15 @@
"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
"dev": true
},
+ "esmock": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/esmock/-/esmock-2.0.6.tgz",
+ "integrity": "sha512-2EY6isqPxZ9xm/nPz7DE5u/M5Nt3Xq4WUTOT5Dv9sfaqim6HZ6hitpEEfrSKAryA1JsnF/YljSRo2MZCtx0LTw==",
+ "dev": true,
+ "requires": {
+ "resolvewithplus": "^1.0.2"
+ }
+ },
"espree": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
@@ -35476,6 +35507,12 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
+ "resolvewithplus": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/resolvewithplus/-/resolvewithplus-1.0.2.tgz",
+ "integrity": "sha512-UNvTSqWpe1lhKvXb+Qo97Q/dFUtA0kDuUG/gO7S+P9H/xYZ+A9pwlAbY6Dqp2tgAeQNdHmQG5MdNL6d9/FWo6Q==",
+ "dev": true
+ },
"responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
diff --git a/package.json b/package.json
index 225e6df..4eaf31e 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"dist"
],
"type": "module",
- "main": "src/index.js",
+ "main": "dist/index.min.js",
"repository": {
"type": "git",
"url": "https://github.com/ipfs-shipyard/ipfs-geoip"
@@ -26,9 +26,9 @@
"lint": "aegir lint",
"release": "aegir release",
"build": "aegir build",
- "test": "aegir test",
+ "test": "npm run test:node && npm run test:browser",
"test:node": "aegir test --target node",
- "test:browser": "aegir test --target browser",
+ "test:browser": "aegir test --target browser --files test/**/*.browser.spec.{js,cjs,mjs}",
"generate": "node bin/generate.js"
},
"dependencies": {
@@ -47,6 +47,7 @@
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"csv-parse": "^5.3.0",
+ "esmock": "^2.0.6",
"gauge": "^4.0.4",
"ipfs-http-client": "^58.0.1",
"multihashes": "^4.0.3",
@@ -64,9 +65,6 @@
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
- "aegir": {
- "tsRepo": false
- },
"pre-commit": [
"lint"
],
diff --git a/src/constants.js b/src/constants.js
new file mode 100644
index 0000000..5a796f9
--- /dev/null
+++ b/src/constants.js
@@ -0,0 +1 @@
+export const MAX_LOOKUP_RETRIES = 3
diff --git a/src/lookup.js b/src/lookup.js
index 72563cc..5bf001b 100644
--- a/src/lookup.js
+++ b/src/lookup.js
@@ -1,9 +1,10 @@
import { default as memoize } from 'p-memoize'
import ip from 'ip'
-import * as dagCbor from '@ipld/dag-cbor'
+import { decode as dagCborDecode } from '@ipld/dag-cbor'
import { CID } from 'multiformats/cid'
import fetch from 'cross-fetch'
import { formatData } from './format.js'
+import { MAX_LOOKUP_RETRIES } from './constants.js'
export const GEOIP_ROOT = CID.parse('bafyreihnpl7ami7esahkfdnemm6idx4r2n6u3apmtcrxlqwuapgjsciihy') // b-tree version of GeoLite2-City-CSV_20221018
@@ -45,6 +46,27 @@ async function getRawBlock (ipfs, cid) {
}
}
+/**
+ * Gets Obj and Block after retrying multiple times.
+ *
+ * @param {object|string} ipfs
+ * @param {CID} cid
+ * @param {number} numTry - this will be 1 for the first try and recurse till MAX_LOOKUP_RETRIES is reached.
+ * @returns {Promise<{obj, block}>}
+ */
+async function getObjAndBlockWithRetries (ipfs, cid, numTry = 1) {
+ try {
+ const block = await getRawBlock(ipfs, cid)
+ const obj = await dagCborDecode(block)
+ return { obj, block }
+ } catch (e) {
+ if (numTry < MAX_LOOKUP_RETRIES) {
+ return await getObjAndBlockWithRetries(ipfs, cid, numTry + 1)
+ }
+ throw e
+ }
+}
+
/**
* @param {object|string} ipfs
* @param {CID} cid
@@ -54,8 +76,7 @@ async function getRawBlock (ipfs, cid) {
async function _lookup (ipfs, cid, lookfor) {
let obj, block
try {
- block = await getRawBlock(ipfs, cid)
- obj = await dagCbor.decode(block)
+ ({ obj, block } = await getObjAndBlockWithRetries(ipfs, cid))
} catch (e) {
if (process?.env?.DEBUG || process?.env?.TEST) {
if (!block) {
diff --git a/test/format.spec.js b/test/format.browser.spec.js
similarity index 100%
rename from test/format.spec.js
rename to test/format.browser.spec.js
diff --git a/test/generate.spec.js b/test/generate.browser.spec.js
similarity index 100%
rename from test/generate.spec.js
rename to test/generate.browser.spec.js
diff --git a/test/lookup.spec.js b/test/lookup.browser.spec.js
similarity index 100%
rename from test/lookup.spec.js
rename to test/lookup.browser.spec.js
diff --git a/test/lookupMultiple.node.spec.js b/test/lookupMultiple.node.spec.js
new file mode 100644
index 0000000..3ff93c1
--- /dev/null
+++ b/test/lookupMultiple.node.spec.js
@@ -0,0 +1,37 @@
+import { decode as dagCborDecode } from '@ipld/dag-cbor'
+import esmock from 'esmock'
+import { expect } from 'chai'
+
+describe('[Runner Node]: lookup via HTTP Gateway supporting application/vnd.ipld.raw responses', function () {
+ const ipfsGW = process?.env?.IPFS_GATEWAY || 'https://ipfs.io'
+
+ it('looks up multiple times before failing', async () => {
+ let decodeCallCount = 0
+ const rewiredGeoIp = await esmock('../src/index.js', {}, {
+ '@ipld/dag-cbor': {
+ decode: (...args) => {
+ decodeCallCount += 1
+ if (decodeCallCount === 1) {
+ throw new Error('Decode Failed')
+ }
+ return dagCborDecode(...args)
+ }
+ }
+ })
+
+ const result = await rewiredGeoIp.lookup(ipfsGW, '66.6.44.4')
+ expect(decodeCallCount).to.be.greaterThan(1)
+ expect(
+ result
+ ).to.be.eql({
+ country_name: 'USA',
+ country_code: 'US',
+ region_code: 'VA',
+ city: 'Ashburn',
+ postal_code: '20149',
+ latitude: 39.0469,
+ longitude: -77.4903,
+ planet: 'Earth'
+ })
+ })
+})