how can I make Flutter graphql subscription work with ferry package? - flutter

I have a graphql api with apollo-server. I tested all queries, mutations and subscriptions with Graphql Playground.
I am developing the client app in Flutter using Ferry package as grapqhl client. All queries and mutations work fine, but subscriptions don't.
When sending a subscription request the websocket connection is established, however the subscription is not started. I tested the subscription on the Graphql Playground and the connection request messages looks like this
Graphql Playground network panel
but with ferry client it get stuck on connection_init
Flutter Web app network panel
var link = WebSocketLink(
"ws://localhost:4000/graphql",
initialPayload: {"subscriptionParam": arg},
);
var client = Client(link: link);
client.request(request).listen((data) {//request is an object from autogenerated class from ferry
log(data.toString());//never gets here
}, onError: (error, stack) {
log("Subscription error: " + error.toString());
});
What is wrong in my code? Help please!

So guys I solved my problem, the issue was related to link not sending Sec-WebSocket-Protocol:graphql-ws on connection request headers. So I change the link initialization to:
final link = WebSocketLink(
null, //Global.graphqlWsServerUrl,
autoReconnect: true,
reconnectInterval: Duration(seconds: 1),
initialPayload: {"subscriptionParam": arg},
channelGenerator: () => WebSocketChannel.connect(Uri.parse(Global.graphqlWsServerUrl), protocols: ['graphql-ws']),
);

Related

next.js socket.io useeffect not updating on socket message

i'm facing an issue with my socket client on next.js . i have created a context for providing my socket instance for all components in the application , the problem is that when i want to use the socket context on my component i am using the useeffect hook be called once the socket is changed and i will handle on message call ( like any tutorial i have seen on the web ) but with new message on the socket the useeffect is not called at all . for those who may think this is context issue i should say its not i have tested socket initiation on the component itslef and still useeffect not being called .
here is the way im using the socket instance :
export default function MyComp(props){
const cookies = new Cookies()
const token = cookies.get('token');
const socket = io(routes.socket_url, { path: '/socket',
transports: ['websocket'],
query:{
token,
user_id:'someuserid'
}
});
useEffect(() => {
console.log('socket changed');
console.log(socket);
console.log('socket changed');
},[socket])
}
i can actually see the connection on my devtools and also see the message in the network tab so it means we have a connection (also checked on server ) but the console.log() part is never called on new messages .
and here is my component using the context .
export default function MyComp(props){
const socket = useContext(SocketContext);
useEffect(() => {
console.log('socket changed');
console.log(socket);
console.log('socket changed');
},[socket])
}
the connection in this one is also available in the devtools and it gets the new message on networks tab but the logging is never called in useeffect .
thank you for any help .
guys i found the answer . i was using socket.io-client v 3.1.1 and i started testing other versions . for some strange reason
in version 3 socket instance is not receiving any messages though the connection is already stablished and i can see the messages coming in the networks tab .
anyways i changed version from 3 to 2.1.1 and its working like a charm .

How to solve the {code: UNAUTHENTICATED, details: null, message: UNAUTHENTICATED}) error using Google Cloud Functions?

I am currently facing the above mentioned error when using the cloud_functions dependency on my Flutter app. My function https call is as follows:
final HttpsCallable callable = CloudFunctions(region: "region name").getHttpsCallable(functionName: 'function-name')..timeout = const Duration(seconds:30);
And my function invocation within the code is as given below:
onPressed: () async {
try {
dynamic resp = await callable.call(<String, dynamic>{
'message':'hello!',
'url': urlController.text,
});
setState(() {
imgurl = resp.data['image'];
time = resp.data['timestamp'];
});
I have added the ID I am using for authentication to my function via the console IAM. Unfortunately, I still keep receiving the following error:
PlatformException(functionsError, Cloud function failed with exception., {code: UNAUTHENTICATED, details: null, message: UNAUTHENTICATED})
How can I resolve that?
Thanks for your help in advance.
If you have deployed your function in "private mode", I mean, allow only authenticated user, you have to add a valid identity_token in the header of your request.
You have an example here, mostly on end user because it's your use case. Don't use a service account key file because your flutter app is public and you will share your secret publicly.
You can also use Cloud Endpoint with Firebase authentication mode. I wrote an article for setting up an authentication with API key. Simply update the authentication mode and it will works.

Flutter call http requests from a unit\widget test without mocking

I have an app that follows MVC+service architecture. The service layer makes the http requests for rest APIs. However the response of the http requests change intermittently which cause my models to change or else random crashes in my app. SO to capture the change in these APIs I want to write some automated tests which can tell me exactly what changed. A sample test case is as following:
test("login_valid", () async {
final loginData = LoginData(
email: "abc#gmail.com",
password: "123"
);
final parameters = loginData.toJson();
var json = await httpService.post(parameters);
var loginResponse = LoginResponse.fromJson(json);
expect(loginResponse.status, "OK");
});
However, the above code throws SocketException upon run. I know this exception is thrown when INTERNET permission is not given in AndroidManifest.xml but I don't know how to set this for unit\widget tests.
P.S. I can't mock the service layer using mockit or similar framework because the whole point is to test my service layer which doesn't have any business logic but just provides network integration.
Any solution or suggestion will be really helpful. I am okay with other approaches to achieve the same intent also, if there are any.
Check this or the gihub issue discussion pointed there
https://timm.preetz.name/articles/http-request-flutter-test

How to use JWT with WebSocketChannel in Flutter

I have an existing Websocket Channel which needs authenticate user by his JWT in order to send/receive messages using this socket connection. The problem is - I don't know how to send my access token in message body when establishing connection. The official documentation says:
"If the url contains user information this will be passed as basic authentication when setting up the connection."
But in my case JWT is passed in a message like this:
{"method":"auth","accessToken":"${MY_TOKEN}"}
I tried to connect by passing JWT in headers or use sink after connection is established, but when I send a new message, it only calls onDone callback and closes connection.
final _channel = IOWebSocketChannel.connect('${WEB_SOCKET_URL}');
...
void initState() {
_channel.stream.listen((message) {
print('message');
}, onError: (error, StackTrace stackTrace) {
print('error');
}, onDone: () {
print('done');
});
_channel.sink.add({
"method": "auth",
"accessToken": "${MY_TOKEN}"
});
}
I expect connection to be established and then I can use it to send/received messages but it's only closes when I try to use "sink.add()" method.
Give a try to this method. This is easy as mentioned in https://api.flutter.dev/flutter/dart-io/WebSocket/connect.html
final WebSocketChannel channel =
IOWebSocketChannel.connect("ws://<socketurl>/ws/",headers: {
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $yourtoken'
});
You may pass it in the URL:
WebSocketChannel.connect("ws://localhost:8080/api/foo/ws?authorization=eyJhbGciOiJIUzI1...")
Using IOWebSocketChannel as suggested throws a platform error if used with flutter web, whereas WebSocketChannel automatically determines the proper platform.
Traditionally it was considered poor practice to have credentials in
query params because URLs can get stored in places such as logs for
proxies, browser history, etc. However, neither of those concerns
apply to websockets (a browser won't keep history of the connections
made by a page), and proxies do not have access to the URL when there
is a TLS tunnel. This concern arose when non-TLS interactions were the
default.
source
You are trying to send an object on the socket I think it is not happy with that. The server may not be able trop properly handle the error of receiving something other than a json string and closes the connection.
Try this to send a json string:
var message = {
"method": "auth",
"accessToken": "${MY_TOKEN}"
};
_channel.sink.add(jsonEncode(message));

Calling mongoose from react client side

Yes, I know I should call it from server side. But the purpose is to invoke MongoDB strait from the react-redux app. It's like firebase serverless apps do.
I write
import mongoose from 'mongoose';
let mongoDB = 'mongodb://127.0.0.1/my_database';
mongoose.connect(mongoDB);
mongoose.Promise = global.Promise;
let db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
And I get:
TypeError: __
WEBPACK_IMPORTED_MODULE_6_mongoose___default.a.connect is not a function
How to solve this problem?
From the comment here
Mongoose won't work in the frontend because it relies on functionality from Node which isn't present in browser JS implementations. You can't import Mongoose into frontend code.
Try importing mongoose in your react app
import mongoose from "mongoose";
and iterating through its properties:
Object.getOwnPropertyNames(mongoose).forEach(prop => {
console.log(prop);
});
You'll get
Promise
PromiseProvider
Error
Schema
Types
VirtualType
SchemaType
utils
Document
The methods that you need to work with MongoDB, such as connect, are not being imported.
mongoDB has to be initialized from the server. it is not like firebase that you can connect directly from the server. If you wanna do an operation from the client, you have to define an endpoint on the server, make a request from the client to this endpoint and handle this request on this endpoint. Since you are not clear what exactly you are trying to do, I will give you a simple example.
Let's say in one component you are fetching songs from the mongodb and rendering them to the screen and you wanna add a clear button to clear up the lists.
<a href="/delete">
<button>Clear the Screen</button>
</a>
let's say I have a Song model defined in mongoose.
app.use("/delete", async (req, res) => {
await Song.deleteMany();
res.redirect("/");
});
I sent a request from the client, and server handled this CRUD operation.
NOTE that since you are making a request from the client to the server you have to set up proxy. Or you can use webpack-dev-middleware so express will serve the webpack files.
You need MongoDB Realm
Pseudo-Code Example :
import * as Realm from "realm-web";
const REALM_APP_ID = "<Your App ID>"; // e.g. myapp-abcde
const app = new Realm.App({ id: REALM_APP_ID });