How to use Peer.js in Next.js with TypeScript? - javascript

Next.js runs on server side also, so Peer.js raise error when using Next.js. Here one says: https://stackoverflow.com/a/66292100/239219
this is probably because peer js is performing some side effect during import.
He propose this:
useEffect(() => {
import('peerjs').then(({ default: Peer }) => {
// Do your stuff here
});
}, [])
But I need DataConnection as using Typescript, and also asign it to a useState. would you show an example how?
This is what I put togeter, but TypesScript raise errors:
useEffect(() => {
import('peerjs').then(({ default: Peer, DataConnection }) => {
const peer = new Peer(localStorage.token)
peer.on('connection', (conn: DataConnection) => {
console.log('Connected to peer:', conn)
conn.on('data', (data) => {
console.log('Received data:', data)
})
})
return () => {
peer.destroy()
}
})
}, [])
like: 'DataConnection' refers to a value, but is being used as a type here. Did you mean 'typeof DataConnection'?

You can use a type-only import (introduced in version 3.8) at the top of the file:
import type { DataConnection } from "peerjs";
This import will be erased in the output, so rest assured that this will not import it "early".
Or if that doesn't fancy you, you can also "inline" the import:
peer.on('connection', (conn: import("peerjs").DataConnection) => {
Looks weird, but when import(...) is used as a type, it resolves to the namespace that importing the module would give you.

Related

How do I store a non serialisable variable in a react - redux state without breaking it if I really must (like saving a device connection via WebAPIs)

I keep getting the statement "do not save non-serializable variables in your state" in almost every google search result - But what happens when I really should?
Progect: I am building an app for deviceS connected via SerialPort (using SerialPort WebAPI).
I wish to save the connection instance since I use it throughout all my application and I am honestly tired of passing the instance down and up whenever I need it without react knowing to re-render data and display new data - which is important for me too.
Steps that I have done:
It was easy to ignore the non-serializable error using serializableCheck: false:
export default configureStore({
reducer: {
serialport: SerialPortDevicesReducer,
bluetooth: BluetoothDevicesReducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
thunk,
serializableCheck: false
}).concat(logger),})
But now I am facing the big problem:
Whenever I create a connection I get the object that handles that specific SerialPort device object that is connected.
deviceReducer: {
id: 1,
instance: SerialPort{[attr and methods here]},
...
}
Whenever I use methods like open(), write() or read() it changes the main connection instance object and breaks with that known error:
Error: Invariant failed: A state mutation was detected between
dispatches, in the path 'serialport.0.instance.readable'. This may
cause incorrect behavior
Since It's not serializable I cannot clone it (which I think is the reason?) and then re-assign it + I think cloning a connection instance will cause other device-connection issues.
I ended up writing the connect method case directly in the state with a "promise" new variable to handle the result.
// click in a react component
const handleConnect = () => {
try {
if ( dispatch(connect(device)) ) {
setActiveStep((prevActiveStep) => prevActiveStep + 1)
return true
}
}
catch (e) {
console.error("Device cannot connect: ", e)
}
}
// In a file that trigges dispatch() to the reduces
const connect = (deviceId) => async (dispatch, getState) => {
try {
dispatch({
type: "serialport/connect",
payload: deviceId
})
} catch(e) {
console.log(e)
}
}
// in reducer
const SerialPortDevicesReducer = (state = initialState, action) => {
switch (action.type) {
case 'serialport/connect':
try {
return {
...state,
[action.payload]: {
...state[action.payload],
promise: state[action.payload].instance.open({baudRate: 115200})
}
}
} catch (e) {
console.error("Cannot run promise inside reducer: ", e)
}
This is the only workaround I currently found. And this basically forces me to handle (maybe some complex) things in the reducer instead of just passing data to it. I tried applying the same for the write method:
// click in component
const handleExecute = (command) => {
try {
dispatch(writeToSP(device1.device, command))
} catch (e) {
console.log(e)
}
}
// In file which trigges the dispatch()
const writeToSP = (deviceId, command = "Z !\n") => async (dispatch) => {
let startTime = new Date().getTime()
let encoder = new TextEncoder()
try {
dispatch({
type: "serialport/write",
payload: {
id: deviceId,
// cmd: encoder.encode(command),
// startTime
}
})
} catch (e) {
console.error("error writing: ", e)
}
}
// in reducer
...
case 'serialport/write':
try {
const writer = state[action.payload.id].instance.writable.getWriter()
} catch (e) {
console.error("Cannot run promise inside reducer: ", e)
}
and again, get the error of "Error: Invariant failed: A state mutation was detected..." which I am guessing a result of it changing other attributes in the SerialPort instance.
Having packages like redux-promise-middleware are awesome, but it seems like an object in my state is the one responsible for its own promise and changes.
How do I handle this specific situation?
Simple: don't put it into Redux. Redux is made for data, not for arbirtary external libraries/dependency injection.
If that value will never change after initialization and you do the initialization outside of React, just put it into a global variable that is exported.
If that value will change over time, you should use the Dependency Injection mechanism of React for it: Context. This is really what context is made for - not sharing state values, but global dependencies.

Nodejs MQTT: unsubscribe is not a function

I have inplemented MQTT well but I am experiencing an issue with unsubscribe method. All other functions (mqttCon.publish() , mqttCon.subscribe(), mqttCon.on()...) are working well except this. I have checked the spelling and even done ctrl +click and it takes me to the library implementation meaning it is the right method and referenced well yet I keep getting the error below. How can I solve it?
This is the line: mqttCon.unsubscribe(topic)
TypeError: mqttCon.unsubscribe is not a function
at noopHandler (/home/dev/project-dir/src/mqtt/processMessage.js:5:13)
at module.exports (/home/dev/project-dir/src/mqtt/processMessage.js:10:20)
at MqttClient.client.on (/home/dev/project-dir/src/mqtt/mqttCon.js:16:13)
at MqttClient.emit (events.js:189:13)
at MqttClient._handlePublish (/home/dev/project-dir/node_modules/mqtt/lib/client.js:1271:12)
at MqttClient._handlePacket (/home/dev/project-dir/node_modules/mqtt/lib/client.js:410:12)
at work (/home/dev/project-dir/node_modules/mqtt/lib/client.js:321:12)
at Writable.writable._write (/home/dev/project-dir/node_modules/mqtt/lib/client.js:335:5)
at doWrite (/home/dev/project-dir/node_modules/mqtt/node_modules/readable-stream/lib/_stream_writable.js:428:64)
at writeOrBuffer (/home/dev/project-dir/node_modules/mqtt/node_modules/readable-stream/lib/_stream_writable.js:417:5)
NOTE: I am using ES6(Emacscript 6+) javascript and not Typescript.
Nodejs 12.18.1 and npm 6.14.6
Here is my connection code mqttCon.js:
const mqtt = require('mqtt')
const processMessage = require('./processMessage')
const logger = require('../logConf')
const options = {
host: '',
port: '',
username:'',
password: '',
protocol: ''
};
const client = mqtt.connect(options)
client.on("connect", function () {
console.log("MQTT connected with status: " + client.connected);
if (client.connected) {
client.on('message', (topic, message) => {
processMessage(topic, String(message))
})
}
})
client.on('error', error => {
console.log(error,'ERROR')
logger.errorLogger.error(error)
})
client.on('reconnect', error => {
console.log(error,'RECONNECT')
logger.errorLogger.error(error)
})
client.on('close', error => {
console.log(error,'CLOSE')
logger.errorLogger.error(error)
})
client.on('disconnect', error => {
console.log(error,'DISCONNECT')
logger.errorLogger.error(error)
})
client.on('offline', error => {
console.log(error,'OFFLINE')
logger.errorLogger.error(error)
})
module.exports = client
This is the processMessage.js :
const mqttCon = require('./mqttCon')
const logger = require('../logConf')
let noopHandler = (topic, message) => {
console.log(String(message))
mqttCon.unsubscribe(topic) //THIS IS WHERE THE ERROR IS OCCURRING *******************
}
module.exports = (topic, message) => {
switch (topic) {
case 'NOOOOOOOOOOOOOOOOOOOOOOOOP':
return noopHandler(topic, message)
case 'anotherTopic':
// return handleAnotherTopic(String(message))
return
default:
logger.errorLogger.error(new Error(`No handler for topic ${topic}`))
}
}
Your mqttCon.js file has no client.prototype.unsubscribe = function() {}, so the error is correct. You are defining client as a module, but you are really needing to call mqtt.unsubscribe() somewhere. So you need to either add an unsubscribe() function to the client constant (which really should be a var in this case), or call the mqtt.unsubscribe() function after requiring the mqtt module in your processMessage.js file....which I think goes against what you are trying to do anyway. You might want to read up a bit more about how module.exports actually works: https://www.sitepoint.com/understanding-module-exports-exports-node-js/
UPDATE:
The above influenced my thoughts and the issue was that I was importing processMessage.js which inturn imports mqttCon.js which imported it. Circular import, meaning mqttCon(mqttClient) was always not yet initialized inside processMessage.js. The solution was that I imported processMessage.js inside client.on('connect'....)... block when client is already initialized and exported well as a module like below:
client.on("connect", () => {
console.log("MQTT connected with status: " + client.connected);
if (client.connected) {
client.on('message', (topic, message) => {
require('./processMessage')(topic, String(message))
})
}
})

Mock Service Worker / Node isn't working and I can't see why

If anyone can spot whatever's wrong with this code, I'd really appreciate. Not seeing an issue myself, but it's failing.
import React from "react"
import {setupServer} from "msw/node"
import {rest} from "msw"
describe("mocking apis", () => {
const testCall = jest.fn()
const server = setupServer(
...[
rest.get("/test", (req, res, ctx) => {
console.log('This line is never run')
testCall()
return res(ctx.json({message: "success"}))
}),
]
)
test("test", async () => {
server.listen()
fetch("/test", {method: "GET"})
expect(testCall).toHaveBeenCalled()
server.close();
})
})
I also had this problem. After a while I realized the cause. In my src/setupTests.js file I had:
import { enableFetchMocks } from 'jest-fetch-mock';
...
enableFetchMocks();
So, fetch() was not being called at all.
I made 3 changes to the posted code to get it to work:
Import and call disableFetchMocks().
Add await before fetch(....
Change the URL to http://localhost/test, to address a test error that said I needed to use an absolute URL.
Here is the working code (cleaned up to AirB&B style by PyCharm):
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { disableFetchMocks } from 'jest-fetch-mock';
describe('mocking apis', () => {
const testCall = jest.fn();
const server = setupServer(
...[
rest.get('http://localhost/test', (req, res, ctx) => {
console.log('This line is run');
testCall();
return res(ctx.json({ message: 'success' }));
}),
],
);
test('test', async () => {
disableFetchMocks();
server.listen();
await fetch('http://localhost/test', { method: 'GET' });
expect(testCall).toHaveBeenCalled();
server.close();
});
});
When you run your tests these run in a node environment, in this fetch function does not exist (it means: global.fetch) for that reason you need to make a polyfill.
I recommend installing the npm package 'whatwg-fetch'
npm install whatwg-fetch
and use it like this:
import 'whatwg-fetch';
This video could help

Cannot stringify a function when using axios get within nuxtServerInit

i am trying Udemy Nuxt.js course connect my app to the backend,
when trying the following code i got GET http://localhost:3000/ 500 (Internal Server Error) on the client side,
import Vuex from 'vuex';
import axios from 'axios';
const createStore = () => {
return new Vuex.Store({
state: {
loadedPosts: [],
},
mutations: {
setPosts(state, posts) {
state.loadedPosts = posts;
},
},
actions: {
nuxtServerInit(vuexContext, context) {
return axios
.get('https://nuxt-blog-7a712.firebaseio.com/posts.json')
.then((res) => {
const postArray = [];
for (const key in res.data) {
postArray.push({ ...res.data[key], id: key });
}
vuexContext.commit('setPost', postArray);
})
.catch((e) => context.error(e));
},
WARN Cannot stringify a function transformRequest
WARN Cannot stringify a function transformResponse
WARN Cannot stringify a function httpAdapter
WARN Cannot stringify a function validateStatus
WARN Cannot stringify arbitrary non-POJOs Writable
WARN Cannot stringify a function
i have google it to find some answers but couldn't find a fix, some posts talked about devalue package but do not know about this package usage
maybe late, but I had trouble with this as well. The warnings are not what are breaking your code, they are just warnings.
What is breaking your code has to be something else, but looking at your code I can't find it..
try changing
.catch((e) => context.error(e));
to
.catch((e) => console.log(e); context.error(e));
to get a better idea of what is going wrong!

Firebase Callable Function context is undefined

I have written a firebase Http callable cloud function based on the tutorial here: https://www.youtube.com/watch?v=3hj_r_N0qMs from the firebase team. However, my function is unable to verify the custom claims on a user (me) as 'context.auth' is undefined
I've updated firebase, firebase tools, firebase-functions and admin SDK to the latest versions.
My functions/Index.ts file
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp()
export const addAdmin = functions.https.onCall((data, context) => {
if (context.auth.token.admin !== true) {
return {
error: 'Request not authorized'
};
}
const uid = data.uid
return grantAdminRole(uid).then(() => {
return {
result: `Request fulfilled!`
}
})
})
async function grantAdminRole(uid: string): Promise<void> {
const user = await admin.auth().getUser(uid);
if (user.customClaims && (user.customClaims as any).admin === true) {
console.log('already admin')
return;
}
return admin.auth().setCustomUserClaims(user.uid, {
admin: true,
}).then(() => {
console.log('made admin');
})
}
My app.component.ts code
makeAdmin() {
var addAdmin = firebase.functions().httpsCallable('addAdmin');
addAdmin({ uid: '[MY-USER-ID]' }).then(res => {
console.log(res);
})
.catch(error => {
console.log(error)
})
}
The function executes well if I don't try to access 'context' and I can add a custom claim to this user. However if I try to access context.auth I find the error:
Unhandled error TypeError: Cannot read property 'token' of undefined"
The error message is telling you that context.auth doesn't have a value. As you can see from the API documentation, auth will be null if there is no authenticated user making the request. This suggests to me that your client app does not have a signed-in user at the time of the request to the callable function, so make sure that is the case before invoking the function. If you allow the case where a callable function can be invoked without a signed in user, you will need to check for that case in your function code by checking context.auth before doing work on behalf of that user.
Turns out I wasn't properly integrating AngularFire Functions. I found the solution to my problem here: https://github.com/angular/angularfire2/blob/master/docs/functions/functions.md
I changed my client component code to the following:
import { AngularFireFunctions } from '#angular/fire/functions';
//other component code
makeAdmin() {
const callable = this.fns.httpsCallable('addAdmin');
this.data$ = callable({ uid: '[USERID]' })
.subscribe(resp => {
console.log({ resp });
}, err => {
console.error({ err });
});
}

Categories

Resources