Skip to content

Commit 06c3aa4

Browse files
authored
Merge pull request #124 from jimni1222/ws
Implemented websocket provider with KAS Node API
2 parents 605cd4b + 4f73392 commit 06c3aa4

File tree

4 files changed

+259
-16
lines changed

4 files changed

+259
-16
lines changed

index.js

+76-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
const Caver = require('caver-js')
18+
const _ = require('lodash')
1819
const KAS = require('./src/kas/kas')
1920
const KASWallet = require('./src/wallet/kasWallet')
2021

@@ -47,8 +48,9 @@ class CaverExtKAS extends Caver {
4748
* @param {number} chainId The chain id.
4849
* @param {string} accessKeyId The access key id.
4950
* @param {string} secretAccessKey The secret access key.
51+
* @param {object} [opt] An object that defines the option value used when initializing to use the KAS API.
5052
*/
51-
constructor(chainId, accessKeyId, secretAccessKey) {
53+
constructor(chainId, accessKeyId, secretAccessKey, opt) {
5254
super()
5355

5456
this.kas = new KAS()
@@ -78,7 +80,7 @@ class CaverExtKAS extends Caver {
7880
this.keyringContainer.keyring = this.wallet.keyring
7981
this.wallet = kasWallet
8082

81-
if (chainId !== undefined && accessKeyId && secretAccessKey) this.initKASAPI(chainId, accessKeyId, secretAccessKey)
83+
if (chainId !== undefined && accessKeyId && secretAccessKey) this.initKASAPI(chainId, accessKeyId, secretAccessKey, opt)
8284
}
8385

8486
/**
@@ -111,14 +113,17 @@ class CaverExtKAS extends Caver {
111113
*
112114
* @example
113115
* caver.initKASAPI(1001, 'accessKeyId', 'secretAccessKey')
116+
* caver.initKASAPI(1001, 'accessKeyId', 'secretAccessKey', { useNodeAPIWithHttp: true }) // use HttpProvider with Node API
117+
* caver.initKASAPI(1001, 'accessKeyId', 'secretAccessKey', { useNodeAPIWithHttp: false }) // use WebsocketProvider with Node API
114118
*
115119
* @param {number} chainId The chain id.
116120
* @param {string} accessKeyId The access key id.
117121
* @param {string} secretAccessKey The secret access key.
122+
* @param {object} [opt] An object that defines the option value used when initializing to use the KAS API.
118123
* @return {void}
119124
*/
120-
initKASAPI(chainId, accessKeyId, secretAccessKey) {
121-
this.initNodeAPI(chainId, accessKeyId, secretAccessKey)
125+
initKASAPI(chainId, accessKeyId, secretAccessKey, opt = { useNodeAPIWithHttp: true }) {
126+
this.initNodeAPI(chainId, accessKeyId, secretAccessKey, opt.useNodeAPIWithHttp)
122127
this.initTokenHistoryAPI(chainId, accessKeyId, secretAccessKey)
123128
this.initWalletAPI(chainId, accessKeyId, secretAccessKey)
124129
this.initAnchorAPI(chainId, accessKeyId, secretAccessKey)
@@ -130,16 +135,52 @@ class CaverExtKAS extends Caver {
130135
* Sets chain id and authentication key for Node API.
131136
*
132137
* @example
133-
* caver.initNodeAPI(1001, 'accessKeyId', 'secretAccessKey')
134-
* caver.initNodeAPI(1001, 'accessKeyId', 'secretAccessKey', 'Node API url to use')
138+
* caver.initNodeAPI(1001, 'accessKeyId', 'secretAccessKey', true) // HttpProvider
139+
* caver.initNodeAPI(1001, 'accessKeyId', 'secretAccessKey', true, 'Node API url to use') // HttpProvider
140+
*
141+
* caver.initNodeAPI(1001, 'accessKeyId', 'secretAccessKey', false) // WebsocketProvider
142+
* caver.initNodeAPI(1001, 'accessKeyId', 'secretAccessKey', false, 'Node API url to use') // WebsocketProvider
135143
*
136144
* @param {number} chainId The chain id.
137145
* @param {string} accessKeyId The access key id.
138146
* @param {string} secretAccessKey The secret access key.
147+
* @param {boolean} [useHttp] If `true`, `HttpProvider` is used. If `false`, `WebsocketProvider` is used. (defaults to `true`)
139148
* @param {string} [url] The end point url.
140149
* @return {void}
141150
*/
142-
initNodeAPI(chainId, accessKeyId, secretAccessKey, url = productionEndpoints.node) {
151+
initNodeAPI(chainId, accessKeyId, secretAccessKey, useHttp, url) {
152+
// chainId, accessKeyId, secretAccessKey
153+
// chainId, accessKeyId, secretAccessKey, useHttp
154+
// chainId, accessKeyId, secretAccessKey, url
155+
// chainId, accessKeyId, secretAccessKey, useHttp, url
156+
if (_.isString(useHttp)) {
157+
url = useHttp
158+
useHttp = undefined
159+
}
160+
useHttp = useHttp === undefined ? true : useHttp
161+
url = url === undefined ? productionEndpoints.node : url
162+
163+
if (useHttp) {
164+
return this.initNodeAPIWithHttp(chainId, accessKeyId, secretAccessKey, url)
165+
}
166+
167+
return this.initNodeAPIWithWebSocket(chainId, accessKeyId, secretAccessKey, url)
168+
}
169+
170+
/**
171+
* Sets chain id and authentication key for Node API.
172+
* This function will set caver's provider with HttpProvider.
173+
*
174+
* @example
175+
* caver.initNodeAPIWithHttp(1001, 'accessKeyId', 'secretAccessKey', 'Node API url to use')
176+
*
177+
* @param {number} chainId The chain id.
178+
* @param {string} accessKeyId The access key id.
179+
* @param {string} secretAccessKey The secret access key.
180+
* @param {string} url The end point url.
181+
* @return {void}
182+
*/
183+
initNodeAPIWithHttp(chainId, accessKeyId, secretAccessKey, url) {
143184
if (url.endsWith('/')) url = url.slice(0, url.length - 1)
144185

145186
const splitted = url.split('/')
@@ -157,6 +198,34 @@ class CaverExtKAS extends Caver {
157198
this._requestManager.provider.headers = this._requestManager.provider.headers.concat(auth)
158199
}
159200

201+
/**
202+
* Sets chain id and authentication key for Node API with web socket.
203+
* This function will set caver's provider with WebsocketProvider.
204+
*
205+
* @example
206+
* caver.initNodeAPIWithWebSocket(1001, 'accessKeyId', 'secretAccessKey', 'Node API url to use')
207+
*
208+
* @param {number} chainId The chain id.
209+
* @param {string} accessKeyId The access key id.
210+
* @param {string} secretAccessKey The secret access key.
211+
* @param {string} url The end point url.
212+
* @return {void}
213+
*/
214+
initNodeAPIWithWebSocket(chainId, accessKeyId, secretAccessKey, url) {
215+
const regex = /[ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/
216+
if (regex.test(accessKeyId) || regex.test(secretAccessKey))
217+
throw new Error(
218+
`Invalid auth: To use the websocket provider, you must use an accessKey and seretAccessKey that do not contain special characters. Please obtain a new AccessKey through the KAS Console.`
219+
)
220+
221+
const endpoint = `wss://${accessKeyId}:${secretAccessKey}@${url
222+
.slice(url.indexOf('//') + 2)
223+
.replace('/v1/klaytn', '')}/v1/ws/open?chain-id=${chainId}`
224+
225+
const ws = new this.providers.WebsocketProvider(endpoint)
226+
this.setProvider(ws)
227+
}
228+
160229
/**
161230
* Sets chain id and authentication key for Token History API.
162231
*

package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
"unitTest": "./node_modules/mocha/bin/mocha ./test/anchor/testAnchorAPI.js && ./node_modules/mocha/bin/mocha ./test/tokenHistory/testTokenHistoryAPI.js && ./node_modules/mocha/bin/mocha ./test/wallet/*.js && ./node_modules/mocha/bin/mocha ./test/helperTest.js && ./node_modules/mocha/bin/mocha ./test/kip7/testKIP7API.js",
1313
"unitTestQA": "npm run unitTest --testEnv=qa",
1414
"unitTestDev": "npm run unitTest --testEnv=dev",
15-
"intNodeAPITest": "./node_modules/mocha/bin/mocha ./test/nodeAPI/testNodeAPI.js",
16-
"intNodeAPITestQA": "./node_modules/mocha/bin/mocha ./test/nodeAPI/testNodeAPI.js --testEnv=qa",
17-
"intNodeAPITestDev": "./node_modules/mocha/bin/mocha ./test/nodeAPI/testNodeAPI.js --testEnv=dev",
18-
"intTest": "npm run intNodeAPITest && ./node_modules/mocha/bin/mocha ./test/intTest/*.js",
19-
"intTestQA": "npm run intNodeAPITestQA && ./node_modules/mocha/bin/mocha ./test/intTest/*.js --testEnv=qa",
20-
"intTestDev": "npm run intNodeAPITestDev && ./node_modules/mocha/bin/mocha ./test/intTest/*.js --testEnv=dev",
15+
"intNodeAPITest": "./node_modules/mocha/bin/mocha ./test/nodeAPI/*.js",
16+
"intNodeAPITestQA": "./node_modules/mocha/bin/mocha ./test/nodeAPI/*.js --testEnv=qa",
17+
"intNodeAPITestDev": "./node_modules/mocha/bin/mocha ./test/nodeAPI/*.js --testEnv=dev",
18+
"intTest": "./node_modules/mocha/bin/mocha ./test/intTest/*.js && npm run intNodeAPITest ",
19+
"intTestQA": "./node_modules/mocha/bin/mocha ./test/intTest/*.js --testEnv=qa && npm run intNodeAPITestQA ",
20+
"intTestDev": "./node_modules/mocha/bin/mocha ./test/intTest/*.js --testEnv=dev && npm run intNodeAPITestDev ",
2121
"lint": "eslint './**/*.js'",
2222
"lintFix": "./yamlFormatting.sh && eslint './**/*.js' --fix"
2323
},

test/nodeAPI/testNodeAPI.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ describe('Node API service enabling', () => {
5959
})
6060

6161
context('caver.initNodeAPI', () => {
62-
it('should return error if nodeAPI is not initialized', async () => {
62+
it('CAVERJS-EXT-KAS-NODE-001: should return error if nodeAPI is not initialized', async () => {
6363
const expectedError = 'Provider not set or invalid'
6464
await expect(caver.rpc.klay.getBlockNumber()).to.be.rejectedWith(expectedError)
6565
}).timeout(50000)
6666

67-
it('should set valid headers to provider with x-chain-id and auth', () => {
67+
it('CAVERJS-EXT-KAS-NODE-002: should set valid headers to provider with x-chain-id and auth', () => {
6868
caver.initNodeAPI(chainId, accessKeyId, secretAccessKey, url)
6969

7070
const headers = caver._requestManager.provider.headers
@@ -95,7 +95,7 @@ describe('Node API service', () => {
9595
})
9696

9797
context('caver.rpc.klay APIs', () => {
98-
it('should use json rpc call of Klaytn thourgh KAS', async () => {
98+
it('CAVERJS-EXT-KAS-NODE-003: should use json rpc call of Klaytn thourgh KAS', async () => {
9999
let filterId
100100

101101
const klayAPIs = [

test/nodeAPI/websocket.js

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2021 The caver-js-ext-kas Authors
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+
const { expect } = require('../extendedChai')
18+
const CaverExtKAS = require('../../index.js')
19+
20+
let caver
21+
const { url, accessKeyId, secretAccessKey } = require('../testEnv').auths.nodeAPI
22+
const { senderPrivateKey } = require('../testEnv')
23+
24+
let keyringContainer
25+
let keyring
26+
27+
const chainId = 1001
28+
29+
before(() => {
30+
caver = new CaverExtKAS()
31+
caver.initNodeAPI(chainId, accessKeyId, secretAccessKey, false, url) // Use websocket
32+
33+
keyringContainer = new caver.keyringContainer()
34+
keyring = keyringContainer.add(keyringContainer.keyring.createFromPrivateKey(senderPrivateKey))
35+
})
36+
37+
describe('caver.contract with websocket provider', () => {
38+
it('CAVERJS-EXT-KAS-NODE-004: when event fires, data should be retrived through contract.once', async () => {
39+
const byteCode =
40+
'0x6080604052348015600f57600080fd5b5060e98061001e6000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063954ab4b2146044575b600080fd5b348015604f57600080fd5b5060566058565b005b7f90a042becc42ba1b13a5d545701bf5ceff20b24d9e5cc63b67f96ef814d80f0933604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15600a165627a7a723058200ebb53e9d575350ceb2d92263b7d4920888706b5221f024e7bbc10e3dbb8e18d0029'
41+
const helloContractABI = [
42+
{
43+
constant: false,
44+
inputs: [],
45+
name: 'say',
46+
outputs: [],
47+
payable: false,
48+
stateMutability: 'nonpayable',
49+
type: 'function',
50+
},
51+
{
52+
anonymous: false,
53+
inputs: [
54+
{
55+
indexed: false,
56+
name: 'who',
57+
type: 'address',
58+
},
59+
],
60+
name: 'callevent',
61+
type: 'event',
62+
},
63+
]
64+
65+
const c = caver.contract.create(helloContractABI)
66+
c.setWallet(keyringContainer)
67+
const contract = await c.deploy(
68+
{
69+
from: keyring.address,
70+
gas: 100000000,
71+
value: 0,
72+
},
73+
byteCode
74+
)
75+
let dataVariable
76+
contract.once('callevent', (error, data) => {
77+
expect(error).to.be.null
78+
dataVariable = data
79+
})
80+
81+
const options = {
82+
from: keyring.address,
83+
gas: 30000,
84+
}
85+
86+
await contract.methods.say().send(options)
87+
88+
expect(dataVariable).not.to.null
89+
caver.currentProvider.connection.close()
90+
}).timeout(200000)
91+
92+
it('CAVERJS-EXT-KAS-NODE-005: when initialize node api with websocket, check existence of the special characters in auth', async () => {
93+
const id = 'KASFAKEACCESSKEYID'
94+
const pwd = 'KASFAKESECRETACCESSKEY'
95+
96+
const expectedError =
97+
'Invalid auth: To use the websocket provider, you must use an accessKey and seretAccessKey that do not contain special characters. Please obtain a new AccessKey through the KAS Console.'
98+
expect(() => {
99+
caver.initNodeAPIWithWebSocket(chainId, `${id}=`, pwd)
100+
}).to.throw(expectedError)
101+
expect(() => {
102+
caver.initNodeAPIWithWebSocket(chainId, `${id}[`, pwd)
103+
}).to.throw(expectedError)
104+
expect(() => {
105+
caver.initNodeAPIWithWebSocket(chainId, `${id}]`, pwd)
106+
}).to.throw(expectedError)
107+
expect(() => {
108+
caver.initNodeAPIWithWebSocket(chainId, `${id}$`, pwd)
109+
}).to.throw(expectedError)
110+
expect(() => {
111+
caver.initNodeAPIWithWebSocket(chainId, `${id}_`, pwd)
112+
}).to.throw(expectedError)
113+
expect(() => {
114+
caver.initNodeAPIWithWebSocket(chainId, `${id}~`, pwd)
115+
}).to.throw(expectedError)
116+
expect(() => {
117+
caver.initNodeAPIWithWebSocket(chainId, `${id}?`, pwd)
118+
}).to.throw(expectedError)
119+
expect(() => {
120+
caver.initNodeAPIWithWebSocket(chainId, `${id}<`, pwd)
121+
}).to.throw(expectedError)
122+
expect(() => {
123+
caver.initNodeAPIWithWebSocket(chainId, `${id}\\`, pwd)
124+
}).to.throw(expectedError)
125+
expect(() => {
126+
caver.initNodeAPIWithWebSocket(chainId, `${id}/`, pwd)
127+
}).to.throw(expectedError)
128+
expect(() => {
129+
caver.initNodeAPIWithWebSocket(chainId, `${id}?`, pwd)
130+
}).to.throw(expectedError)
131+
expect(() => {
132+
caver.initNodeAPIWithWebSocket(chainId, `${id} `, pwd)
133+
}).to.throw(expectedError)
134+
135+
expect(() => {
136+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}=`)
137+
}).to.throw(expectedError)
138+
expect(() => {
139+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}[`)
140+
}).to.throw(expectedError)
141+
expect(() => {
142+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}]`)
143+
}).to.throw(expectedError)
144+
expect(() => {
145+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}$`)
146+
}).to.throw(expectedError)
147+
expect(() => {
148+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}_`)
149+
}).to.throw(expectedError)
150+
expect(() => {
151+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}~`)
152+
}).to.throw(expectedError)
153+
expect(() => {
154+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}?`)
155+
}).to.throw(expectedError)
156+
expect(() => {
157+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}<`)
158+
}).to.throw(expectedError)
159+
expect(() => {
160+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}\\`)
161+
}).to.throw(expectedError)
162+
expect(() => {
163+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}/`)
164+
}).to.throw(expectedError)
165+
expect(() => {
166+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd}?`)
167+
}).to.throw(expectedError)
168+
expect(() => {
169+
caver.initNodeAPIWithWebSocket(chainId, id, `${pwd} `)
170+
}).to.throw(expectedError)
171+
172+
caver.currentProvider.connection.close()
173+
}).timeout(200000)
174+
})

0 commit comments

Comments
 (0)