Skip to content

Commit 0b7326a

Browse files
authored
fix(pubsub): multibase in pubsub http rpc (#3922)
This PR aims to restore interop with go-ipfs by applying the same changes as in ipfs/kubo#8183 TLDR is that we clean up and unify the API. BREAKING CHANGE: We had to make breaking changes to `pubsub` commands sent over HTTP RPC to fix data corruption caused by topic names and payload bytes that included `\n`. More details in ipfs/kubo#7939 and ipfs/kubo#8183
1 parent d266bdc commit 0b7326a

File tree

8 files changed

+65
-13
lines changed

8 files changed

+65
-13
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"devDependencies": {
7878
"aegir": "^36.0.1",
7979
"delay": "^5.0.0",
80-
"go-ipfs": "0.10.0",
80+
"go-ipfs": "0.11.0",
8181
"ipfsd-ctl": "^10.0.4",
8282
"it-all": "^1.0.4",
8383
"it-first": "^1.0.4",

src/files/rm.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { configure } from '../lib/configure.js'
22
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
3+
import HTTP from 'ipfs-utils/src/http.js'
34

45
/**
56
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -20,7 +21,15 @@ export const createRm = configure(api => {
2021
headers: options.headers
2122
})
2223

23-
await res.text()
24+
const body = await res.text()
25+
// we don't expect text body to be ever present
26+
// (if so, it means an error such as https://github.com/ipfs/go-ipfs/issues/8606)
27+
if (body !== '') {
28+
/** @type {Error} */
29+
const error = new HTTP.HTTPError(res)
30+
error.message = body
31+
throw error
32+
}
2433
}
2534
return rm
2635
})

src/lib/http-rpc-wire-format.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
2+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
3+
import { base64url } from 'multiformats/bases/base64'
4+
5+
/* HTTP RPC:
6+
* - wraps binary data in multibase. base64url is used to avoid issues
7+
* when a binary data is passed as search param in URL.
8+
* Historical context: https://github.com/ipfs/go-ipfs/issues/7939
9+
* Multibase wrapping introduced in: https://github.com/ipfs/go-ipfs/pull/8183
10+
*/
11+
12+
/**
13+
* @param {Array<string>} strings
14+
* @returns {Array<string>} strings
15+
*/
16+
const rpcArrayToTextArray = strings => {
17+
if (Array.isArray(strings)) {
18+
return strings.map(rpcToText)
19+
}
20+
return strings
21+
}
22+
23+
/**
24+
* @param {string} mb
25+
* @returns {string}
26+
*/
27+
const rpcToText = mb => uint8ArrayToString(rpcToBytes(mb))
28+
29+
/**
30+
* @param {string} mb
31+
* @returns {Uint8Array}
32+
*/
33+
const rpcToBytes = mb => base64url.decode(mb)
34+
35+
/**
36+
* @param {string} text
37+
* @returns {string}
38+
*/
39+
const textToUrlSafeRpc = text => base64url.encode(uint8ArrayFromString(text))
40+
41+
export { rpcArrayToTextArray, rpcToText, rpcToBytes, textToUrlSafeRpc }

src/pubsub/ls.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { configure } from '../lib/configure.js'
22
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
3+
import { rpcArrayToTextArray } from '../lib/http-rpc-wire-format.js'
34

45
/**
56
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -17,7 +18,7 @@ export const createLs = configure(api => {
1718
headers: options.headers
1819
})).json()
1920

20-
return Strings || []
21+
return rpcArrayToTextArray(Strings) || []
2122
}
2223
return ls
2324
})

src/pubsub/peers.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { configure } from '../lib/configure.js'
22
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
3+
import { textToUrlSafeRpc } from '../lib/http-rpc-wire-format.js'
34

45
/**
56
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -14,7 +15,7 @@ export const createPeers = configure(api => {
1415
const res = await api.post('pubsub/peers', {
1516
signal: options.signal,
1617
searchParams: toUrlSearchParams({
17-
arg: topic,
18+
arg: textToUrlSafeRpc(topic),
1819
...options
1920
}),
2021
headers: options.headers

src/pubsub/publish.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { configure } from '../lib/configure.js'
22
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
33
import { multipartRequest } from 'ipfs-core-utils/multipart-request'
44
import { abortSignal } from '../lib/abort-signal.js'
5+
import { textToUrlSafeRpc } from '../lib/http-rpc-wire-format.js'
56
import { AbortController } from 'native-abort-controller'
67

78
/**
@@ -15,7 +16,7 @@ export const createPublish = configure(api => {
1516
*/
1617
async function publish (topic, data, options = {}) {
1718
const searchParams = toUrlSearchParams({
18-
arg: topic,
19+
arg: textToUrlSafeRpc(topic),
1920
...options
2021
})
2122

src/pubsub/subscribe.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
2-
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
31
import debug from 'debug'
42
import { configure } from '../lib/configure.js'
53
import { toUrlSearchParams } from '../lib/to-url-search-params.js'
4+
import { textToUrlSafeRpc, rpcArrayToTextArray, rpcToBytes } from '../lib/http-rpc-wire-format.js'
65
const log = debug('ipfs-http-client:pubsub:subscribe')
76

87
/**
@@ -43,7 +42,7 @@ export const createSubscribe = (options, subsTracker) => {
4342
api.post('pubsub/sub', {
4443
signal: options.signal,
4544
searchParams: toUrlSearchParams({
46-
arg: topic,
45+
arg: textToUrlSafeRpc(topic),
4746
...options
4847
}),
4948
headers: options.headers
@@ -95,10 +94,10 @@ async function readMessages (response, { onMessage, onEnd, onError }) {
9594
}
9695

9796
onMessage({
98-
from: uint8ArrayToString(uint8ArrayFromString(msg.from, 'base64pad'), 'base58btc'),
99-
data: uint8ArrayFromString(msg.data, 'base64pad'),
100-
seqno: uint8ArrayFromString(msg.seqno, 'base64pad'),
101-
topicIDs: msg.topicIDs
97+
from: msg.from,
98+
data: rpcToBytes(msg.data),
99+
seqno: rpcToBytes(msg.seqno),
100+
topicIDs: rpcArrayToTextArray(msg.topicIDs)
102101
})
103102
} catch (/** @type {any} */ err) {
104103
err.message = `Failed to parse pubsub message: ${err.message}`

test/utils/factory.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const commonOptions = {
1616

1717
const commonOverrides = {
1818
go: {
19-
ipfsBin: isNode ? path() : undefined
19+
ipfsBin: isNode ? (process.env.IPFS_GO_EXEC || path()) : undefined
2020
}
2121
}
2222

0 commit comments

Comments
 (0)