id | title |
---|---|
server-connection |
Server Connection |
In this example we use Core
methods for connecting to the server using WebSockets.
We implement a button with counters like in Using widgets. The difference is that the state of the counters is kept in the simple Node.js (Express) server.
Here is the initial code for this example: ex15-server-connection-exercise
- We add the server's URL to
./config/default.json
{
"dev": {
"serverUrl": "ws://localhost:8081/feature-1"
}
}
It's possible to set different URLs for test
and main
networks.
In ./src/index.ts
we get this URL from the config:
const serverUrl = await Core.storage.get('serverUrl')
- Then we have to create a connection with the server.
Core.connect
has its own state, so we have to provide the default state and its type or interface.
interface IDappState {
amount: any
}
const defaultState: IDappState = { amount: 0 }
const server = Core.connect<IDappState>({ url: serverUrl }, defaultState)
To understand how the dapplet's state works read the article Shared State.
In this simple example we can only use connection
's state. But in your dapplet you might want to use one complex state for the entire app.
So let's create a common state.
const state = Core.state<IDappState>(defaultState)
Here we use the same interface and default state, but in your dapplet you can use other ones.
- In the config we get
ctx
. We can usectx.id
as a key in our states. If we use the common state, we can pass observable value of the server's state to it.
state[ctx.id].amount.next(server.state[ctx.id].amount)
- In the
button
's DEFAULT state we pass the observable counter to the label and inexec
increase its value with a click. We implement the function that increases the counters on the server side. In the dapplet we call this function by using thesend
method. The first parameter is the name of the function and the second is a parameter for the server's function. In our case it's context ID.
DEFAULT: {
img: EXAMPLE_IMG,
label: state[ctx.id].amount.value,
// label: server.state[ctx.id].amount, // alternative usage
exec: () => server.send('increment', ctx.id),
},
:::caution
Note that we don't pass the entire observable state's amount
, but instead it's value to the label
. This is because this value is observable server's amount.
When we directly use server.state[ctx.id].amount
without the common state we don't have to get its value here.
:::
This is the entire activate
method:
async activate() {
const serverUrl = await Core.storage.get('serverUrl');
const defaultState: IDappState = { amount: 0 };
const server = Core.connect<IDappState>({ url: serverUrl }, defaultState);
const state = Core.state<IDappState>(defaultState);
const { button } = this.adapter.exports;
this.adapter.attachConfig({
POST: (ctx: { id: string }) => {
state[ctx.id].amount.next(server.state[ctx.id].amount);
return button({
initial: 'DEFAULT',
DEFAULT: {
img: EXAMPLE_IMG,
label: state[ctx.id].amount.value,
// label: server.state[ctx.id].amount, // alternative usage
exec: () => server.send('increment', ctx.id),
},
});
},
});
}
- Add a storage for the counters in
server/index.js
.
const counter = {}
- Initialize a counter for the current tweet.
if (!Object.prototype.hasOwnProperty.call(counter, tweetId)) {
counter[tweetId] = {
amount: 0,
}
}
- Send the counter in
params
.
ws.send(
JSON.stringify({
jsonrpc: '2.0',
method: subscriptionId,
params: [{ amount: counter[tweetId].amount }],
})
)
- Send the counter in a callback.
ws.send(
JSON.stringify({
jsonrpc: '2.0',
method: subscriptionId,
id: currentId,
params: [{ amount: counter[currentId].amount }],
})
)
- Implement the counter increment.
const [currentId] = params
counter[currentId].amount += 1
emitter.emit('attached', currentId)
Here is the result code of the example: ex15-server-connection-solution
Run the dapplet:
npm i
npm start
:::caution
if an error occurs in the 'src/index.ts' , delete the 'package-lock.json' and run npm i
:::