-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
how to use setContext or other methods for setting the params #10
Comments
Hi @G3z, You have to be careful when you compose links with a WS termination as most of them have been designed with an HTTP termination in mind (stateless) which is the case with Given the socket is stateful and params are passed only when you connect, you will have to drop and create a new socket on logout / login actions. |
Hi @G3z, You will need to create a wrapper link on top of socket-link to do this. It will have to be somekind of a memoized factory, where it will have to keep record of the latest credentials it received, and if the ones passed are the same as the stored, it could be able to reuse previously created socket-link, but if not, it will have to close the active one, and create a new one with the credentials given and use this one from there on. |
I`ve been stuck for several days looking for a library that works correctly, I was recommended to use this one, but I can not manage to pass my token to do the authentication. Could somebody give me an code example of that implementation that mention @mgtitimoli ! Thank you! |
this is my dirty hack if anyone is interested let token = getToken(); //either undefined or proper_token
const params = {
get guardian_token() {
if (!token) {
token = getToken();
}
return token;
},
};
const wsLink = createAbsintheSocketLink(
AbsintheSocket.create(
new PhoenixSocket(WS_SERVER, {
reconnect: true,
timeout: 30000,
params,
})
)
); this way the token is checked upon each connection |
It works great! Thank you! |
that's a js feature |
Ok, awesome. Do you know why doesn't works if this is code gives the same result?
However when I use your getter function, it works fine! |
Nice hack @G3z, but it won't reconnect to socket if auth changed. The proper way to handle this would be to introduce an extra level of indirection, so instead of passing an instance of |
The way I've just commented will introduce a breaking change, that's why I'm still doubting if it is the best way or we should create another factory fn for this cases. |
Something new? How can I implement this new layer that you comment? In my case, I use Redux, which is why I think of creating a reducer called client, which is created when you log in or is deleted when you log out. |
I'm using RelayModern and want to be able to set the params on each login and remove on logout. Can environment be a service (singleton) with a static factory function |
Hi @mgtitimoli, first off a big thank you for taking these packages forward! I ran into the same issue as @G3z and @Yamilquery while upgrading my React Native app to Apollo 2. It turned out to be a major blocker because all my subscriptions require an auth token. Have you already formed an opinion on how this could be solved best? I'm quite a novice with Apollo (and JS for that matter), but if there is any way I can help please let me know! |
Hi @richeterre This was how I managed to resolve my own import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context'
import { AsyncStorage } from 'react-native'
const getUserJWT = async () => {
const token = await AsyncStorage.getItem(APP_USER_TOKEN)
return token ? `BEARER ${token}` : null
}
const httpLink = createHttpLink({ uri: httpUri });
const authLink = setContext(async (req, { headers }) => {
const token = await getUserJWT()
return {
headers: {
...headers,
authorization: token
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
}) It is working perfectly for me. |
@smithaitufe Nice solution but will this work for absinthe sockets as well? At the moment it does not work for apollo-link-ws. BTW: InMemoryCache is default for ApolloClient, so no need to specify that Edit: disregard that last remark. Doc says it is default, but you still need to explicitly specify it. |
Hi everyone, The easiest way to deal with this, is to create a new link on top of the one provided here, that can keep in its internal state the last auth params received (extracted from the context), so on each request it would be able to check if they remain the same or if they changed, reusing the previous instance of the absinthe-apollo-link (created by itself), or creating a new one correspondingly. This intermediate link will serve as a "memoized absinthe-apollo-link factory" only, it will not handle the request by itself, but it will pass it to the instance of absinthe-apollo-link it holds, which it will remain the same if the auth (or any other) params needed to create the absinthe-socket (dependency of absinthe-socket-link) didn't change or it will have to create a new one (with the new params) if they did. I will expose this in the near future, I'm currently working on the next release, it will be ready by the end of February, meanwhile you can do what I wrote above. Thanks! |
I understand the gist of what you are saying here, but I looked at the source code for |
@mgtitimoli Any news on when the new release is expected? |
Any update on this? I imagine there are more people saving their auth tokens in the asyncstorage so some solution for that is pretty pressing. |
I tried following @mgtitimoli's advice of creating a "memoized factory" for // socketLink.ts
import * as AbsintheSocket from '@absinthe/socket'
import { AbsintheSocketLink, createAbsintheSocketLink } from '@absinthe/socket-apollo-link'
import { ApolloLink, NextLink, Operation } from 'apollo-link'
import { Socket as PhoenixSocket } from 'phoenix'
import config from '../constants/config'
import store from '../redux/store'
function getAuthToken() {
return store.getState().auth.token
}
function createPhoenixSocket(authToken: string | null): PhoenixSocket {
return new PhoenixSocket(config.subscriptionsUrl, {
params: {
auth_token: authToken,
},
})
}
function createInnerSocketLink(phoenixSocket: PhoenixSocket): any {
const absintheSocket = AbsintheSocket.create(phoenixSocket)
return createAbsintheSocketLink(absintheSocket)
}
class SocketLink extends ApolloLink {
socket: PhoenixSocket
link: AbsintheSocketLink
constructor() {
super()
this.socket = createPhoenixSocket(getAuthToken())
this.link = createInnerSocketLink(this.socket)
this._watchAuthToken()
}
request(operation: Operation, forward?: NextLink | undefined) {
return this.link.request(operation, forward)
}
_watchAuthToken() {
let token = getAuthToken()
store.subscribe(() => {
const newToken = getAuthToken()
if (newToken !== token) {
token = newToken
this.socket.disconnect()
this.socket = createPhoenixSocket(token)
this.link = createInnerSocketLink(this.socket)
}
})
}
}
export default new SocketLink() and in my Apollo client setup I use the above link for all subscriptions: export default new ApolloClient({
link: split(operation => hasSubscription(operation.query), socketLink, httpLink),
cache: new InMemoryCache(),
}) Note that I'm getting my auth token from the Redux store, not AsyncStorage, but the general pattern should be similar. Hope this helps others bridge the gap until there's a more "official" solution |
Thanks for your solution @richeterre! This is how I did it with
|
Based on the https://github.com/phoenixframework/phoenix/blob/473a3cb40aae1ba36c70dfc4841ace2d3aab7471/assets/js/phoenix.js#L733 import * as AbsintheSocket from '@absinthe/socket'
import { createAbsintheSocketLink } from '@absinthe/socket-apollo-link'
import { Socket as PhoenixSocket } from 'phoenix'
const socket = new PhoenixSocket(WS_URL, {
params: () => {
token: window.localStorage.getItem('token')
},
heartbeatIntervalMs: 5000
})
export default createAbsintheSocketLink(AbsintheSocket.create(socket)) |
@mgtitimoli Has this been addressed in the recent release (v0.2.0)? I couldn't find release notes or a changelog anywhere… |
could someone confirm that once the websocket channel has been opened (with Authorization header = token AAA), each subsequent request using the websocket link will always be identified as AAA token. Or is there a way to send a different Authorization header on each request (other than re-opening another ws channel)? I'd like to understand what's happening on a low level protocol for ws. Thank you for you reply! const wsClient = new SubscriptionClient(
graphqlEndpoint,
{
reconnect: true,
connectionParams: () => ({
headers: {
'Authorization': 'mytokenAAA',
},
}),
},
ws,
);
const link = new WebSocketLink(wsClient);
makePromise(execute(link, options)); // that's using token AAA
// how to make another query (execute) using token BBB without creating another link ? |
@richeterre thank you very much for sharing your solution. I ended up with something similar: import { create as createAbsintheSocket } from '@absinthe/socket'
import { createAbsintheSocketLink } from '@absinthe/socket-apollo-link'
import { ApolloLink, Operation } from 'apollo-link'
import { Socket as PhoenixSocket } from 'phoenix'
import { $cookies, $env } from '@/plugins'
class TerminatingLink extends ApolloLink {
link?: ApolloLink
socket?: PhoenixSocket
tokenStatus = ''
constructor() {
super()
}
request(operation: Operation) {
const prev = this.tokenStatus
const curr = $cookies.socketToken.get()
this.tokenStatus = curr
if (curr && (!this.link || !prev || prev !== curr)) {
this.link = this._create(curr)
}
return this.link ? this.link.request(operation) : null
}
_create(token: string): ApolloLink {
if (this.socket) {
this.socket.disconnect()
}
this.socket = new PhoenixSocket($env.api.wssUri, {
params: { token }
})
return createAbsintheSocketLink(createAbsintheSocket(this.socket), (_) => {
$cookies.socketToken.remove()
})
}
}
export default new TerminatingLink() |
@richeterre solution is nice. My solution is a bit more advanced and try to support workflows such as disconnecting the socket when user logs out, and reconnect if the user logs in again etc... I actually want no socket to stay alive when user is not logged in. Here's some generic infrastructure code: export class ApolloUpdatableLink extends ApolloLink {
link: ApolloLink;
constructor(link: ApolloLink) {
super();
this.link = link;
}
updateLink = (link: ApolloLink) => {
this.link = link;
};
request(operation: Operation, forward?: NextLink | undefined) {
return this.link.request(operation, forward);
}
}
export class ApolloThrowOnRequestLink extends ApolloLink {
error: Error;
constructor(error: Error) {
super();
this.error = error;
}
request(operation: Operation, forward?: NextLink | undefined): Observable<FetchResult> | null {
throw this.error;
}
} And some app-specific code to achieve the behavior I want: type DisconnectableLink = { link: ApolloLink; disconnectLink: () => void };
const createUnauthenticatedSubscriptionLink = (): DisconnectableLink => {
return {
link: new ApolloThrowOnRequestLink(
new Error(
'Apollo subscription link is disabled, because it requires an authenticated user',
),
),
disconnectLink: () => {
// Nothing to do
},
};
};
const createAuthenticatedSubscriptionLink = (
token: string,
): DisconnectableLink => {
const phoenixSocket = new PhoenixSocket(SubscriptionUrl, {
params: { token },
});
return {
link: createAbsintheSocketLink(AbsintheSocket.create(phoenixSocket)),
disconnectLink: () => {
phoenixSocket.disconnect();
},
};
};
// This link will switch between the authenticated/unauthenticated underlying links and handle the disconnexion or previous link before switching
// You have to provide your own "AppSessionUpdatedEvents" (in my case it's a simple event-emitter/bus that I trigger on login/logout)
const subscriptionLink = (() => {
let currentDisconnectableLink: DisconnectableLink = createUnauthenticatedSubscriptionLink();
const updatableLink = new ApolloUpdatableLink(currentDisconnectableLink.link);
AppSessionUpdatedEvents.subscribe(appSession => {
currentDisconnectableLink.disconnectLink();
const nextDisconnectableLink = appSession
? createAuthenticatedSubscriptionLink(appSession.token)
: createUnauthenticatedSubscriptionLink();
currentDisconnectableLink = nextDisconnectableLink;
updatableLink.updateLink(nextDisconnectableLink.link);
});
})(); |
Hi guys, all the examples I have seen so far use params to set the token on the client. Is it possible to pass Authorization header to the absinthe/Phoenix framework? I have tried so many things including this: const wsClient = new PhoenixSocket(
AUTH_ABSINTHE_SOCKET_ENDPOINT,
{
reconnect: true,
connectionParams: () => ({
headers: {
'authorization': `Bearer ${token}`,
},
}),
},
);
const link = createAbsintheSocketLink(AbsintheSocket.create(wsClient)); I see that it is possible to do this using Websocket link and subscription client here |
Is it possible to use
import { setContext } from 'apollo-link-context';
to set the auth token ?with
apollo-link-http
it's done as so:i tried
but i couldn't set the connection params
since i'm on React Native i have to access
AsyncStorage
to get my token so i can't simplyThe text was updated successfully, but these errors were encountered: