рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рд╢реБрд░реВ рдореЗрдВ WsLink рдмрдирд╛рддреЗ рд╕рдордп connectionParams
рдореЗрдВ рдЯреЛрдХрди рдкреНрд░рджрд╛рди рдХрд░рдиреЗ рдХреЗ рд░реВрдк рдореЗрдВ websockets рдкрд░ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддреЗ рд╣реИрдВред
рд▓реЗрдХрд┐рди рдпрд╣ рд╕реНрдкрд╖реНрдЯ рдирд╣реАрдВ рд╣реИ рдХрд┐ рдЯреЛрдХрди рдХреЛ рдмрд╛рдж рдореЗрдВ рдХреИрд╕реЗ рдмрджрд▓рд╛ рдЬрд╛рдП рдФрд░ рдпрджрд┐ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд▓реЙрдЧ рдЖрдЙрдЯ рдХрд░рддрд╛ рд╣реИ рдФрд░ рдлрд┐рд░ рд╕реЗ рд▓реЙрдЧ рдЗрди рдХрд░рддрд╛ рд╣реИ рддреЛ рдХрдиреЗрдХреНрд╢рди рдХреЛ рдлрд┐рд░ рд╕реЗ рд╢реБрд░реВ рдХрд░реЗрдВ (рд╕рдВрднрд╡рддрдГ рдПрдХ рдЕрд▓рдЧ рдЯреЛрдХрди рдХреЗ рд╕рд╛рде)
рдореИрдВрдиреЗ рдЗрд╕ рддрд░рд╣ рд╡рд╛рдВрдЫрд┐рдд рдкрд░рд┐рдгрд╛рдо рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдмрдВрдзрди рдХрд┐рдпрд╛:
export const changeSubscriptionToken = token => {
if (wsLink.subscriptionClient.connectionParams.authToken === token) {
return
}
wsLink.subscriptionClient.connectionParams.authToken = token
wsLink.subscriptionClient.close()
wsLink.subscriptionClient.connect()
}
рд▓реЗрдХрд┐рди рдпрд╣ рдереЛрдбрд╝реЗ рд╣реИрдХреА рд▓рдЧрддрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ wsLink.subscriptionClient
рдХреЛ рдирд┐рдЬреА рдХреЗ рд░реВрдк рдореЗрдВ рдЪрд┐рд╣реНрдирд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ рдФрд░ рдЗрд╕реЗ рдмрд╛рд╣рд░ рд╕реЗ рдкрд╣реБрдВрдЪ рдпреЛрдЧреНрдп рдирд╣реАрдВ рдорд╛рдирд╛ рдЬрд╛рддрд╛ рд╣реИ
рдЖрдк рдПрдХ рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдЬреЛ рдХрдиреЗрдХреНрд╢рдирдкрд░рдореНрд╕ рдореЗрдВ рдХрд┐рд╕реА рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЛ рд╣рд▓ рдХрд░рддрд╛ рд╣реИред
const wsLink = new WebSocketLink({
uri: config.subscriptionURL,
options: {
reconnect: true,
connectionParams: () => ({
authToken: reduxStore.getState().authentication.token,
}),
},
});
рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ:
рдУрд╣ рд░реБрдХреЛ, рдпрд╣ рдкрд╣рд▓реА рдмрд╛рд░ рдХреЗ рдмрд╛рдж рдирд╣реАрдВ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИ... рд╣реЛ рд╕рдХрддрд╛ рд╣реИ рдХрд┐ рдХрд┐рд╕реА рдЕрдиреНрдп рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХреЗ рд╕рд╛рде рд╕рдВрдЧрдд рд╣реЛ рдЬреЛ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдкрд░ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдСрдмреНрдЬреЗрдХреНрдЯ рдбрд╛рд▓рддрд╛ рд╣реИ рдЬрд┐рд╕реЗ рдЖрдк onOperation
рд╕рд░реНрд╡рд░ рд╕рд╛рдЗрдб рд╕реЗ рдкрд╛рд░реНрд╕ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ?
рдпрд╣рд╛рдБ рдореЗрд░реЗ рдкрд╛рд╕ рдЖрдкрдХреЗ рд▓рд┐рдП рдХреНрдпрд╛ рд╣реИ, рдЬрд┐рд╕ рддрд░рд╣ рд╕реЗ рдореИрдВ рдЗрд╕реЗ ApolloClient V1 рдХреЗ рд╕рд╛рде рдХрд░ рд░рд╣рд╛ рдерд╛, рдЙрд╕рдХреЗ рдмрд╛рдж рддреИрдпрд╛рд░ рдХрд┐рдпрд╛ рдЧрдпрд╛
рдкреЗрд▓реЛрдб рдкрд░ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдЯреЛрдХрди рд╕рдВрд▓рдЧреНрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдирдпрд╛ рд▓рд┐рдВрдХ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдЬреЛрдбрд╝реЗрдВ
const authMiddleware = new ApolloLink((operation, forward) => {
// Add the authorization to the headers for HTTP authentication
operation.setContext({
headers: {
authorization: `Bearer ${authToken()}`,
},
});
// Add onto payload for WebSocket authentication
(operation as Operation & { authToken: string | undefined }).authToken = authToken();
return (forward as any)(operation);
});
const myLink = concat(myLink, wsLink);
рдлрд┐рд░ рд╕рд░реНрд╡рд░ рд╕рд╛рдЗрдб рдЖрдк рдЗрд╕реЗ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдСрдирдСрдкрд░реЗрд╢рди рд╣реБрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕рдВрджрд░реНрдн рдкрд░ рд▓рд╛рдЧреВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ
function configureDecodeTokenSocketMiddleware(authURL: string) {
return async function decodeTokenSocketMiddleware<ConnectionParams extends { authToken: string }>(connectionParams: ConnectionParams, operationParams: object) {
let authPayload;
try {
if (typeof connectionParams.authToken === 'string') {
authPayload = await verifyJWT(authURL, connectionParams.authToken);
} else {
throw new Error('Auth Token not available');
}
} catch(e) {
authPayload = {};
}
return {
...operationParams,
context: {
authentication: authPayload,
},
};
};
}
new SubscriptionServer({
execute,
subscribe,
schema,
onOperation: configureDecodeTokenSocketMiddleware(appConfig.authURL),
}, {
server: appConfig.server,
path: `/${appConfig.subscriptionsEndpoint}`,
});
рд╣реЛ рд╕рдХрддрд╛ рд╣реИ, рдЖрдк рдЗрд╕ рд╕рдВрджреЗрд╢ рдХреЛ рдлрд┐рд░ рд╕реЗ рднреЗрдЬрдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ: https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L507
const payload: ConnectionParams = typeof this.connectionParams === 'function' ? this.connectionParams() : this.connectionParams;
this.sendMessage(undefined, MessageTypes.GQL_CONNECTION_INIT, payload);
рдХреНрдпрд╛ рдРрд╕рд╛ рдХрд░рдиреЗ рдХрд╛ рдХреЛрдИ рдЕрдиреБрд╢рдВрд╕рд┐рдд рддрд░реАрдХрд╛ рд╣реИ?
рд╕рджрд╕реНрдпрддрд╛ рдЗрд╕ рдореБрджреНрджреЗ
рдЪреВрдВрдХрд┐ рдирдпрд╛ WebSocketLink
рдЕрднреА рднреА subscriptions-transport-ws
рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдмрдирд╛ рд░рд╣рд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдореИрдВ рдЕрдкрдиреЗ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХреЛ WebSocketLink рдХреЗ рдмрдЬрд╛рдп рд╕рдмреНрд╕рдХреНрд░рд┐рдкреНрд╢рди рдЯреНрд░рд╛рдВрд╕рдкреЛрд░реНрдЯ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рд▓рд╛рдЧреВ рдХрд░ рд░рд╣рд╛ рд╣реВрдВред
рдореИрдВ рдЙрд╕реА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдЬреИрд╕рд╛ рдореИрдВрдиреЗ v1 рдореЗрдВ рдХрд┐рдпрд╛ рдерд╛, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдпрд╣ WebSocketLink API рдХреЗ рд╣рд┐рд╕реНрд╕реЗ рдХреЗ рд░реВрдк рдореЗрдВ рдирд╣реАрдВ рд╣реЛрдиреЗ рдХреЗ рдХрд╛рд░рдг рд╣реИрдХреА рдХреА рддрд░рд╣ рд▓рдЧрддрд╛ рд╣реИред
// create the web socket link
const wsLink = new WebSocketLink({
uri: 'ws://example.com',
options: {
reconnect: true
}
})
// create my middleware using the applyMiddleware method from subscriptions-transport-ws
const subscriptionMiddleware = {
applyMiddleware (options, next) {
options.auth = { ... }
next()
}
}
// add the middleware to the web socket link via the Subscription Transport client
wsLink.subscriptionClient.use([subscriptionMiddleware])
рдЖрдк рд╡рд┐рдХрд▓реНрдк рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ рдЬреЛ рднреА рдорд╛рди рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ, рд╡реЗ рд╕рд░реНрд╡рд░ рд╕рд╛рдЗрдб рдкрд░ onOperation
рдХреЙрд▓рдмреИрдХ рдореЗрдВ рдЙрдкрд▓рдмреНрдз рд╣реЛрддреЗ рд╣реИрдВред
рдпрд╣ рд╕рдорд╕реНрдпрд╛ рднреА рд╣реЛ рд░рд╣реА рд╣реИ
@jbaxleyiii рдХреЛрдИ рд╡рд┐рдЪрд╛рд░?
рдореИрдВрдиреЗ рдХреБрдЫ рд╣рдлреНрддреЗ рдкрд╣рд▓реЗ рдПрдХ рд╕рдорд╛рдзрд╛рди рдмрдирд╛рдпрд╛ рд╣реИ (? рдпрд╣ рдореЗрд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ рдЬрд╛рдЧ рд░рд╣рд╛ рд╣реИ):
рдореЗрд░реЗ app.module.ts
рдореЗрдВ рдПрдХ рдореЙрдбреНрдпреВрд▓ GraphQLModule
рдЖрдпрд╛рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ:
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { Apollo, ApolloModule } from 'apollo-angular';
import { HttpLinkModule } from 'apollo-angular-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
@NgModule({
exports: [
ApolloModule,
HttpClientModule,
HttpLinkModule
]
})
export class GraphQLModule {
public subscriptionClient: SubscriptionClient = null;
constructor(apollo: Apollo) {
const wssEndpoint = 'wss://your-domain.com/subscriptions';
// It's required to define connectionParams as function, as otherwise the updated token is not transferred
const connectionParams = () => {
const token = sessionStorage.getItem('token');
return token ? { 'Authorization': 'token ' + token } : {};
};
const wsLink = new WebSocketLink({
uri: wssEndpoint,
options: {
connectionParams: connectionParams,
reconnect: true
}
});
this.subscriptionClient = (<any>wsLink).subscriptionClient;
const cache = new InMemoryCache({});
apollo.create({
link: wsLink,
cache: cache.restore(window[ '__APOLLO_CLIENT__' ]),
connectToDevTools: true,
queryDeduplication: true
});
}
}
рдореЗрд░реА рдкреНрд░рдорд╛рдгреАрдХрд░рдг рд╕реЗрд╡рд╛ рдореЗрдВ, рдореИрдВ рдЗрд╕ рдореЙрдбреНрдпреВрд▓ рдХреЛ рдХрдВрд╕реНрдЯреНрд░рдХреНрдЯрд░ рдореЗрдВ private graphQLModule: GraphQLModule
рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░ рд░рд╣рд╛ рд╣реВрдВред
рд▓реЙрдЧрд┐рди рдпрд╛ рд▓реЙрдЧрдЖрдЙрдЯ рдХреЗ рдмрд╛рдж рдореИрдВ рдХрд░ рд░рд╣рд╛ рд╣реВрдБ:
const client = this.graphQLModule.subscriptionClient;
// Close actual connection
client.close(true, true);
// todo: set/unset session token in sessionStorage
// Connect again
(<any>client).connect();
рдпрд╣ рд╕рдмреНрд╕рдХреНрд░рд┐рдкреНрд╢рди рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдРрдк рд╕реНрдЯрд╛рд░реНрдЯ рдкрд░ рдЦреЛрд▓рддрд╛ рд╣реИ рдФрд░ рд▓реЙрдЧрд┐рди/рд▓реЙрдЧрдЖрдЙрдЯ рдкрд░ рдЗрд╕реЗ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдлрд┐рд░ рд╕реЗ рд╢реБрд░реВ рдХрд░рддрд╛ рд╣реИред
рдЖрджрд░реНрд╢ рд░реВрдк рд╕реЗ рдпрд╣ рдПрдХ рд╡рд╛рджрд╛ рд╡рд╛рдкрд╕реА рд╕рдорд╛рд░реЛрд╣ рдХрд╛ рднреА рд╕рдорд░реНрдерди рдХрд░реЗрдЧрд╛, рдЗрд╕рд▓рд┐рдП рдпрджрд┐ рд╣рдореЗрдВ рдЯреЛрдХрди рдХреЛ рдПрд╕рд┐рдВрдХ рдХрд░рдирд╛ рд╣реИ рддреЛ рд╣рдо рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
@ clayne11 рдореБрдЭреЗ рдпрдХреАрди рдирд╣реАрдВ рд╣реИ рдХрд┐ рдпрд╣ рдХрд░рддрд╛ рд╣реИред рдореИрдВ рдХрдиреЗрдХреНрд╢рди рдкреИрд░рд╛рдореНрд╕ рдХреЗ рд░реВрдк рдореЗрдВ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛-рдореВрд▓ рд╕реЗ AsyncStorage рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░ рд░рд╣рд╛ рд╣реВрдВред рд▓реЗрдХрд┐рди рдореИрдВрдиреЗ рдЬреЛ рдПрд╕рд┐рдВрдХ рдЬреЛрдбрд╝рд╛ рд╣реИ, рд╡рд╣ рд╕рд░реНрд╡рд░ рдкрд░ рдЕрдкрд░рд┐рднрд╛рд╖рд┐рдд рднреЗрдЬрддрд╛ рд░рд╣рддрд╛ рд╣реИред
рдореБрдЭреЗ рдЗрд╕рд╕реЗ рдХреЛрдИ рд╕рдорд╕реНрдпрд╛ рд╣реИ?
рдЕрдкрдбреЗрдЯ рдХрд░реЗрдВ:
рдПрдХ рдкреБрд▓ рдЕрдиреБрд░реЛрдз рд╣реИ рдЬреЛ рдЗрд╕реЗ рд╕рдВрдмреЛрдзрд┐рдд рдХрд░рддрд╛ рд╣реИ рд▓реЗрдХрд┐рди рдЕрднреА рддрдХ рдорд╛рд╕реНрдЯрд░ рд╢рд╛рдЦрд╛ рдореЗрдВ рд╡рд┐рд▓рдп рдирд╣реАрдВ рд╣реБрдЖ рд╣реИ
@jonathanheilmann , рдЖрдкрдХрд╛ рд╕рдорд╛рдзрд╛рди @ helios1138 рдореВрд▓ рд╕рдорд╛рдзрд╛рди рдХреЗ рд╕рдорд╛рди рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рд╕рджрд╕реНрдпрддрд╛ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЗ рд▓рд┐рдП connect()
API рдХреЛ рдирд┐рдЬреА рдХреЗ рд░реВрдк рдореЗрдВ рдЪрд┐рд╣реНрдирд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ рдФрд░ рдЖрджрд░реНрд╢ рд░реВрдк рд╕реЗ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред
рдореИрдВрдиреЗ subscriptionClient.close(false, false)
рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХреА рдЬреЛ рдПрдХ рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рдПрдкреАрдЖрдИ рд╣реИ рдФрд░ рдЗрд╕реЗ рдлрд┐рд░ рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдордЬрдмреВрд░ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдерд╛, рдФрд░ рдпрд╣ рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╕рджрд╕реНрдпрддрд╛ рдЕрднреА рднреА рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддреА рд╣реИред рдХреЗрд╡рд▓ рдПрдХ рдЪреАрдЬ рдЬреЛ рдЕрднреА рдХрд╛рдо рдХрд░ рд░рд╣реА рд╣реИ рд╡рд╣ рд╣реИ:
subscriptionClient.close();
subscriptionClient.connect(); // this is a private API :-(
рдЗрд╕ рдкрд░ рдХреЛрдИ рд╕рд╛рдл рдирдП рд╡рд┐рдЪрд╛рд░? рдпрд╣рд╛рдВ рд╕рдорд╛рдзрд╛рди рдХрд░рдирд╛ рдкрд╕рдВрдж рд╣реИред рдореИрдВ рд▓реЛрдб рдкрд░ рдЕрдкрдирд╛ рдЕрдкреЛрд▓реЛ рд▓рд┐рдВрдХ рдмрдирд╛рддрд╛ рд╣реВрдВ рд▓реЗрдХрд┐рди рдПрдХ рдмрд╛рд░ рдЬрдм рдореЗрд░рд╛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЖрдИрдбреА рдореЗрдВ рд▓реЙрдЧ рдЗрди рдХрд░рддрд╛ рд╣реИ рддреЛ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рд╕рдВрджрд░реНрдн рдореЗрдВ рднреЗрдЬреЗ рдЬрд╛ рд░рд╣реЗ рдЯреЛрдХрди рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдирд╛ рдкрд╕рдВрдж рдХрд░рддреЗ рд╣реИрдВред
рдпрд╣ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рд╕рдорд╛рдзрд╛рди рд╣реИ рдЬрд┐рд╕рдХреЗ рд╕рд╛рде рд╣рдо рдЖрдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реИрдВ: https://github.com/apollographql/apolo-server/issues/1505ред
рдкреНрд░рддреНрдпреЗрдХ рдСрдкрд░реЗрд╢рди рдкрд░ context
рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рдЧрддрд┐рд╢реАрд▓ рд░реВрдк рд╕реЗ рджреЗрдЦрддреЗ рд╣реИрдВред
рдЗрд╕рд╕реЗ рдореБрдЭреЗ рдорджрдж рдорд┐рд▓реА
https://github.com/apolographql/apolo-link/issues/446#issuecomment -410073779
рдХреНрдпрд╛ рдХреЛрдИ рдкреБрд╖реНрдЯрд┐ рдХрд░ рд╕рдХрддрд╛ рд╣реИ рдХрд┐ рдПрдХ рдмрд╛рд░ рд╡реЗрдмрд╕реЙрдХреЗрдЯ рдЪреИрдирд▓ (рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╣реЗрдбрд░ = рдЯреЛрдХрди рдПрдПрдП рдХреЗ рд╕рд╛рде) рдЦреЛрд▓рд╛ рдЧрдпрд╛ рд╣реИ, рддреЛ рд╡реЗрдмрд╕реЙрдХреЗрдЯ рд▓рд┐рдВрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдкреНрд░рддреНрдпреЗрдХ рдмрд╛рдж рдХреЗ рдЕрдиреБрд░реЛрдз рдХреЛ рд╣рдореЗрд╢рд╛ рдПрдПрдП рдЯреЛрдХрди рдХреЗ рд░реВрдк рдореЗрдВ рдкрд╣рдЪрд╛рдирд╛ рдЬрд╛рдПрдЧрд╛ред
рдпрд╛ рдХреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдЕрдиреБрд░реЛрдз рдкрд░ рдПрдХ рдЕрд▓рдЧ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╢реАрд░реНрд╖рд▓реЗрдЦ рднреЗрдЬрдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рд╣реИ (рджреВрд╕рд░реЗ рдбрдмреНрд▓реВрдПрд╕ рдЪреИрдирд▓ рдХреЛ рдлрд┐рд░ рд╕реЗ рдЦреЛрд▓рдиреЗ рдХреЗ рдЕрд▓рд╛рд╡рд╛)?
рдореИрдВ рд╕рдордЭрдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ рдХрд┐ рдбрдмреНрд▓реНрдпреВрдПрд╕ рдХреЗ рд▓рд┐рдП рдирд┐рдореНрди рд╕реНрддрд░ рдХреЗ рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рдкрд░ рдХреНрдпрд╛ рд╣реЛ рд░рд╣рд╛ рд╣реИред
рдЖрдкрдХреЗ рдЙрддреНрддрд░ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж!
рдпрд╣рд╛рдБ рдореЗрд░рд╛ рдХреЛрдб рдЕрдм рддрдХ рд╣реИ (рдПрдХ рдЯреЛрдХрди рдХреЗ рд╕рд╛рде рд╕рд╣реА рдврдВрдЧ рд╕реЗ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реИ):
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 ?
@sulliwane @RyannGalea @jonathanheilmann @ helios1138 рдЯреЛрдХрди/рдХрдиреЗрдХреНрд╢рди рдкреИрд░рд╛рдо рдХреЛ рдЧрддрд┐рд╢реАрд▓ рд░реВрдк рд╕реЗ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЖрдкрдХреЛ рдЗрд╕реЗ SubscriptionClient рдХреЗ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕реЗрдЯ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рдореЗрд░рд╛ рд╕рд╛рд░ рдпрд╣рд╛рдБ рджреЗрдЦреЗрдВ: https://gist.github.com/cowlicks/71e766164647f224bf15f086ea34fa52
const subscriptionMiddleware = {
applyMiddleware: function(options, next) {
// Get the current context
const context = options.getContext();
// set it on the `options` which will be passed to the websocket with Apollo
// Server it becomes: `ApolloServer({contetx: ({payload}) => (returns options)
options.authorization = context.authorization;
next()
},
};
const server = new ApolloServer({
context: ({connection, payload, req}) => {
// whatever you return here can be accessed from the middleware of the SubscriptionMiddleware with
// applyMiddleware: (options, next) => options.getContext()
return {authorization: payload.authorization};
},
});
const link = new WebSocketLink({
uri: WS_URL,
webSocketImpl: WebSocket,
options: {
reconnect: true,
}
});
link.subscriptionClient.use([subscriptionMiddleware]);
рдореБрдЭреЗ рдорд┐рд▓рд╛ рдПрдХрдорд╛рддреНрд░ рд╕рдорд╛рдзрд╛рди рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рдмрдВрдж рдХрд░рдирд╛ рд╣реИ ...
рдореИрдВрдиреЗ 1.5 рд╕рд╛рд▓ рдкрд╣рд▓реЗ рдПрдХ рд╕рдорд╛рдзрд╛рди рдкреЛрд╕реНрдЯ рдХрд┐рдпрд╛ рдерд╛: рдЕрдкреЛрд▓реЛрдЧреНрд░рд╛рдлрдХреНрд▓/рдЕрдкреЛрд▓реЛ-рд╕рд░реНрд╡рд░#1505ред AFAIK рдпрд╣ рдЕрднреА рднреА рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред
@ clayne11 рдЖрдкрдХрд╛ рд╕рдорд╛рдзрд╛рди рдПрдХ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХрд╛ рддрд╛рддреНрдкрд░реНрдп рд╣реИред рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рд╣реИ рд▓реЗрдХрд┐рди рдореИрдВ рдХреЛрдгреАрдп рдФрд░ рдпреЛрдЧ-рдЧреНрд░рд╛рдлрдХрд▓ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рддрд╛ рд╣реВрдВ рдФрд░ рдЕрдЬреНрдЮрд╛рдд рдХрд╛рд░рдг рд╕реЗ рдореИрдВ рд╕рдВрджрд░реНрдн рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЯреЛрдХрди рдирд╣реАрдВ рднреЗрдЬ рд╕рдХрддрд╛ред
рд╕рд░реНрд╡рд░ рдореЗрдВ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдореЗрдВ рдЯреЛрдХрди рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рд╣реИ?
рдореИрдВ рдпреЛрдЧ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдФрд░ рдЗрд╕ рддрд░рд╣ рдореИрдВ рд╕рдВрджрд░реНрдн рдореЗрдВ рдЯреЛрдХрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдВ
const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
middlewares: [permissions],
resolvers,
context: ({ connection, ...request }) => {
if(connection) {
wsToken: connection.context.authToken
}
}
return ({...request, prisma });
}
});
const options = {
port: process.env.PORT || 4000,
tracing: true,
subscriptions: {
path: "/",
onConnect: async (connectionParams, webSocket) => {
if (connectionParams.authToken) {
// I can access this through context
return {
authToken: connectionParams.authToken
};
}
}
}
};
server.express.use(async (req, res, next) => {
/*
I want to access authToken here
*/
next();
});
server.start(options, ({port}) => {
console.log(`Server is runnning on http://localhost:${port}`);
});
рд╕реЙрдХреЗрдЯ рдмрдВрдж рдХрд┐рдП рдмрд┐рдирд╛ рдореБрдЭреЗ рдЕрдкреЛрд▓реЛ рдиреЗрдЯрд╡рд░реНрдХ рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХрд╛ рдПрдХ рдЧреБрдЪреНрдЫрд╛ рдорд┐рд▓рддрд╛ рд╣реИ (рднрд▓реЗ рд╣реА рдпрд╣ рд╕реНрд╡рдпрдВ рдХреЛ рд╕рд╣реА рдХрд░рддрд╛ рд╣реИ, рд╣рдо рдЙрди рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХреЛ рдЖрджрд░реНрд╢ рд╕реЗ рдХрдо рдЯреЛрд╕реНрдЯ рдЕрдзрд┐рд╕реВрдЪрдирд╛рдУрдВ рдХреЗ рд╕рдореВрд╣ рдореЗрдВ рджрд┐рдЦрд╛рддреЗ рд╣реИрдВ) рдЗрд╕рд▓рд┐рдП рдореБрдЭреЗ рдЯреЛрдХрди рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж рд╡реЗрдмрд╕реЛрдХреЗрдЯ рдмрдВрдж рдХрд░рдирд╛ рд╣реЛрдЧрд╛, рдлрд┐рд░ reconnect: true
рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рдпрд╣ рдлрд┐рд░ рд╕реЗ рдЬреБрдбрд╝рддрд╛ рд╣реИ ... рд╕рд┐рд╡рд╛рдп рдЗрд╕рдХреЗ рдХрд┐ рдпрд╣ рдПрдХ рдЕрдирдВрдд рд▓реВрдк рдореЗрдВ рдлрд┐рд░ рд╕реЗ рдЬреБрдбрд╝рддрд╛ рд╣реИ, рдпрд╣рд╛рдВ рддрдХ тАЛтАЛтАЛтАЛрдХрд┐ рд╕реЛрдЪрд╛ рдХрд┐ рд╕реЙрдХреЗрдЯ рдХреЗрд╡рд▓ рдПрдХ рдмрд╛рд░ рдмрдВрдж рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ
рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ: SubscriptionClient.close() рдХреЗ рдмрдЬрд╛рдп SubscriptionClient.close()
SubscriptionClient.client.close()
рдкрд░ рдХреЙрд▓ рдХрд░рдХреЗ рдлрд┐рдХреНрд╕реНрдб
рдХреНрдпрд╛ рдПрдХ рдкреВрд░реНрдг рдХреНрд▓рд╛рдЗрдВрдЯ рдФрд░ рд╕рд░реНрд╡рд░ рдЙрджрд╛рд╣рд░рдг рд╣реИ рдХрд┐ 5 рдФрд░ 23 рдкрд╕рдВрдж рдХреЗ рд╕рд╛рде 100 рдмрд┐рдЦрд░реЗ рд╣реБрдП рдЕрд░реНрдз-рд╕рдорд╛рдзрд╛рдиреЛрдВ рдХреЗ рдмрдЬрд╛рдп рдирдП рдЯреЛрдХрди рдХреЗ рд╕рд╛рде рдлрд┐рд░ рд╕реЗ рдХреИрд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд┐рдпрд╛ рдЬрд╛рдП?
рдореИрдВ рдЗрд╕ рддрд░рд╣ рдХреА рд╕рджрд╕реНрдпрддрд╛рдПрдВ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рддрд╛ рд╣реВрдВ apolloServer.installSubscriptionHandlers(server);
рдФрд░ рдореИрдВ рдЕрдкреЛрд▓реЛрд╕рд░реНрд╡рд░ рдмрдирд╛рддрд╛ рд╣реВрдБ
const apolloServer = new ApolloServer({
schema,
introspection: true,
subscriptions: {
onConnect: (connectionParams, webSocket, context) => {
},
onDisconnect: (webSocket, context) => {},
...there is no OnOperation: () => {}!!!
},
рдХреНрдпрд╛ рдСрдкрд░реЗрд╢рди рдкрд░ рдЬреЛрдбрд╝рдиреЗ рдХрд╛ рдХреЛрдИ рдФрд░ рддрд░реАрдХрд╛ рд╣реИ? рдпрд╛ рдореБрдЭреЗ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ SubscriptionServer рдмрдирд╛рдирд╛ рд╣реИ рдФрд░ apolloServer.installSubscriptionHandlers(server)
рдХрд╛ рдЙрдкрдпреЛрдЧ рдирд╣реАрдВ рдХрд░рдирд╛ рд╣реИ?
рдореИрдВрдиреЗ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдЧрддрд┐рд╢реАрд▓ рд░реВрдк рд╕реЗ рдЙрддреНрдкрдиреНрди рдХрд░рдирд╛ рд╕рдорд╛рдкреНрдд рдХрд░ рджрд┐рдпрд╛ред рдпрджрд┐ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд▓реЙрдЧ рдЗрди рдирд╣реАрдВ рд╣реИ, рддреЛ рдХреНрд▓рд╛рдЗрдВрдЯ new WebSocket
рд▓рд┐рдВрдХ рдирд╣реАрдВ рдмрдирд╛рддрд╛ рд╣реИ, рдЕрдиреНрдпрдерд╛ рдпрд╣ рдХрд░рддрд╛ рд╣реИред рдиреАрдЪреЗ рджрд┐рдпрд╛ рдЧрдпрд╛ generateApolloClient
рдлрд╝рдВрдХреНрд╢рди SubscriptionClient
рдХреЛ рднреА рдЙрдЬрд╛рдЧрд░ рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдореИрдВ рдХрдиреЗрдХреНрдЯ рд╣реЛрдиреЗ рдкрд░ рдкреНрд░реЗрд╖рдг рдХреЙрд▓ рдЬреЛрдбрд╝ рд╕рдХрддрд╛ рд╣реВрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд░реЗрдбрдХреНрд╕ рд╕реНрдЯреЛрд░ рдХреЛ рдмрддрд╛рдПрдВ рдХрд┐ рд╡реЗрдмрд╕реНрдХреЗрдЯ рдХрдиреЗрдХреНрдЯ рд╣реИ)ред
/**
* Http link
*/
const httpLink = new HttpLink({
uri: '/graphql'
});
/**
* Perform actions before each http request
*/
const middlewareLink = new ApolloLink((operation, forward) => {
operation.setContext({
headers: {
authorization: localStorage.getItem('token')
}
});
return forward(operation);
});
const cache = new InMemoryCache();
type ApolloSubscriptionClient<TLoggedIn extends boolean> = TLoggedIn extends true ? SubscriptionClient : undefined;
/**
* Function to create our apollo client. After login we want to add subscriptions
* with websocket, so we'll need to recreate the entire client depending on the
* user being logged in or logged out.
*/
export const generateApolloClient = <TLoggedIn extends boolean>(
loggedIn: TLoggedIn
): [ApolloClient<NormalizedCacheObject>, ApolloSubscriptionClient<TLoggedIn>] => {
let link = middlewareLink.concat(httpLink);
// Only apply our subscription client when the user is logged in
let subscriptionClient: SubscriptionClient | undefined;
if (loggedIn) {
subscriptionClient = new SubscriptionClient('ws://localhost:4001/graphql/ws', {
reconnect: true,
connectionParams: () => ({ authorization: localStorage.getItem('token') })
});
const wsLink = new WebSocketLink(subscriptionClient);
link = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
link
);
}
const apolloClient = new ApolloClient({ link, cache });
return [apolloClient, subscriptionClient as ApolloSubscriptionClient<TLoggedIn>];
};
рдпрд╣рд╛рдВ ApolloProvider
рдШрдЯрдХ рдореЗрдВ рдХреЛрдб рд╣реИ рдЬреЛ generateApolloClient
рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ:
const GraphqlProvider: React.FC = ({ children }) => {
const dispatch = useAppDispatch();
const loggedIn = useUserLoggedIn();
const [client, setClient] = useState(generateApolloClient(false)[0]);
useEffect(() => {
if (loggedIn) {
const [apolloClient, subscriptionClient] = generateApolloClient(loggedIn);
subscriptionClient.onConnected(() => {
dispatch(setSubscriptionConnected(true));
})
setClient(apolloClient);
}
}, [dispatch, loggedIn]);
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
рдпрд╣ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реИ рдФрд░ рдЬрдм рднреА рдХреЛрдИ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд▓реЙрдЧ рдЖрдЙрдЯ рдХрд░рддрд╛ рд╣реИ рдФрд░ рдлрд┐рд░ рд╕реЗ рд╡рд╛рдкрд╕ рдЖрддрд╛ рд╣реИ, рддреЛ рдореБрдЭреЗ рд╕рд░реНрд╡рд░ рдкрд░ рдПрдХ рдирдпрд╛ рдЯреЛрдХрди рджрд┐рдЦрд╛рдИ рджреЗрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдереЛрдбрд╝рд╛ рдЕрддрд┐рд░рд┐рдХреНрдд рдХрд╛рдо рд╣реИред рд╡рд┐рдЪрд╛рд░?
@ dorsett85 рдЙрддреНрддрд░ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рд░рд┐рдПрдХреНрдЯ/рдиреЗрдХреНрд╕реНрдЯрдЬреЗрдПрд╕ рдРрдкреНрд╕ рдХреЗ рд▓рд┐рдП рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рд░реАрдлреНрд░реЗрд╢ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рд╕рдВрджрд░реНрдн рдПрдкреАрдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдПрдХ рдЕрдЪреНрдЫрд╛ рд╡рд┐рдЪрд╛рд░ рд╣реЛ рд╕рдХрддрд╛ рд╣реИред
----------- рдкреНрд░рд╕рдВрдЧ рдПрдкреАрдЖрдИ
export const ApolloClientContext = createContext(undefined);
export const ApolloClientContextProvider: React.FC<{}> = ({ children }) => {
const [apolloClient, setApolloClient] = useState(client);
return (
<ApolloClientContext.Provider value={[apolloClient, setApolloClient]}>{children}</ApolloClientContext.Provider>
);
};
--------- рдЕрдкреЛрд▓реЛ рдкреНрд░рджрд╛рддрд╛
<ApolloClientContextProvider>
<ApolloClientContext.Consumer>
{([apolloClient, setApolloClient]) => {
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}}
</ApolloClientContext.Consumer>
</ApolloClientContextProvider>
-------- рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЗ рд╕рд╛рде рдирдП рдХреНрд▓рд╛рдЗрдВрдЯ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ
export const generateApolloClientWithAuth = (token): ApolloClient<any> => {
const httpLink = createHttpLink({
uri: 'http://localhost:5000/graphql',
fetch,
credentials: 'include'
});
const wsLink = process.browser
? new WebSocketLink({
uri: `ws://localhost:5000/graphql`,
options: {
reconnect: true,
connectionParams() {
console.log('-token', token);
return {
authorization: token ? `Bearer ${token}` : ''
};
}
}
})
: null;
const splitLink = process.browser
? split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
wsLink,
httpLink
)
: httpLink;
return new ApolloClient({
ssrMode: true,
link: splitLink,
connectToDevTools: process.env.NODE_ENV === 'development',
cache: new InMemoryCache().restore({})
});
};
----------рд▓реЙрдЧрд┐рди рдШрдЯрдХ
const [apolloClient, setApolloClient] = useContext(ApolloClientContext);
----------- рд▓реЙрдЧрд┐рди рдХрд░рдиреЗ рдкрд░
setApolloClient(generateApolloClientWithAuth(token));
рдорд╣рддреНрд╡рдкреВрд░реНрдг рдиреЛрдЯ: рдмрд╢рд░реНрддреЗ рдЕрдкреЛрд▓реЛ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛрдб SSR рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реЛ
@kamilkisiela рдХреНрдпрд╛ рд╣рдо рд╕рд┐рд░реНрдл wsLink.subscriptionClient
рд╕рджрд╕реНрдп рдХреЛ рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рд╕рджрд╕реНрдп рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВ? рдпрд╣ рд╕рдВрднрд╛рд╡рдирд╛ рдирд╣реАрдВ рд╣реИ рдХрд┐ рдЗрд╕реЗ рдмрджрд▓рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреА, рд╣реИ рдирд╛?
рдЕрдВрддрд┐рдо рдореВрд▓реНрдп рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП
get
рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ
const wsLink = new WebSocketLink({
uri: CLIENT_CONFIG.websocketUrl,
options: {
lazy: true,
reconnect: true,
connectionParams: {
get authorization() {
return user.getAuthorization();
},
},
connectionCallback: (error) => {
//@ts-ignore
if (error?.message === "Authentication Failure!") {
//@ts-ignore
//wsLink.subscriptionClient.close(false, false);
}
},
},
});
рдХреНрдпрд╛ рдХреЛрдИ рдореБрдЭ рдкрд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдХрд░реЗрдЧрд╛ рдЕрдЧрд░ рдореИрдВ рдХрд╣реВрдВ рдХрд┐ рдпрд╣ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ:
const wsLink = process.browser
? new WebSocketLink({
uri: webSocketUri,
options: {
reconnect: true,
connectionParams: () => ({
token: cookie.get('token'),
}),
},
})
: null;
рдЕрд░реНрдерд╛рдд
рдмрд╕ рдмрджрд▓ рджреЛ:
connectionParams: {
token: cookie.get('token'),
},
рдкреНрд░рддрд┐:
connectionParams: () => ({
token: cookie.get('token'),
}),
рд╕рдмрд╕реЗ рдЙрдкрдпреЛрдЧреА рдЯрд┐рдкреНрдкрдгреА
рдЪреВрдВрдХрд┐ рдирдпрд╛
WebSocketLink
рдЕрднреА рднреАsubscriptions-transport-ws
рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдмрдирд╛ рд░рд╣рд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдореИрдВ рдЕрдкрдиреЗ рдорд┐рдбрд▓рд╡реЗрдпрд░ рдХреЛ WebSocketLink рдХреЗ рдмрдЬрд╛рдп рд╕рдмреНрд╕рдХреНрд░рд┐рдкреНрд╢рди рдЯреНрд░рд╛рдВрд╕рдкреЛрд░реНрдЯ рдХреНрд▓рд╛рдЗрдВрдЯ рдкрд░ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рд▓рд╛рдЧреВ рдХрд░ рд░рд╣рд╛ рд╣реВрдВредрдореИрдВ рдЙрд╕реА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣рд╛ рд╣реВрдВ рдЬреИрд╕рд╛ рдореИрдВрдиреЗ v1 рдореЗрдВ рдХрд┐рдпрд╛ рдерд╛, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдпрд╣ WebSocketLink API рдХреЗ рд╣рд┐рд╕реНрд╕реЗ рдХреЗ рд░реВрдк рдореЗрдВ рдирд╣реАрдВ рд╣реЛрдиреЗ рдХреЗ рдХрд╛рд░рдг рд╣реИрдХреА рдХреА рддрд░рд╣ рд▓рдЧрддрд╛ рд╣реИред
рдЖрдк рд╡рд┐рдХрд▓реНрдк рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ рдЬреЛ рднреА рдорд╛рди рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ, рд╡реЗ рд╕рд░реНрд╡рд░ рд╕рд╛рдЗрдб рдкрд░
onOperation
рдХреЙрд▓рдмреИрдХ рдореЗрдВ рдЙрдкрд▓рдмреНрдз рд╣реЛрддреЗ рд╣реИрдВред