<p>apollo-link-ws: 초기 μ„€μ • ν›„ connectionParams(및 토큰)λ₯Ό λ³€κ²½ν•˜λŠ” 방법은 λ¬΄μ—‡μž…λ‹ˆκΉŒ?</p>

에 λ§Œλ“  2017λ…„ 10μ›” 31일  Β·  28μ½”λ©˜νŠΈ  Β·  좜처: apollographql/apollo-link

λ¬Έμ„œμ—μ„œλŠ” WsLinkλ₯Ό 처음 생성할 λ•Œ connectionParams 에 토큰을 μ œκ³΅ν•˜λŠ” κ²ƒμœΌλ‘œ μ›Ή μ†ŒμΌ“μ— λŒ€ν•œ 인증을 μ„€λͺ…ν•©λ‹ˆλ‹€.

κ·ΈλŸ¬λ‚˜ μ‚¬μš©μžκ°€ λ‘œκ·Έμ•„μ›ƒν–ˆλ‹€κ°€ λ‹€μ‹œ λ‘œκ·ΈμΈν•˜λ©΄(λ‹€λ₯Έ ν† ν°μœΌλ‘œ) λ‚˜μ€‘μ— 토큰을 λ³€κ²½ν•˜κ³  연결을 λ‹€μ‹œ μ΄ˆκΈ°ν™”ν•˜λŠ” 방법이 λΆˆλΆ„λͺ…ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μ›ν•˜λŠ” κ²°κ³Όλ₯Ό 얻을 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

export const changeSubscriptionToken = token => {
  if (wsLink.subscriptionClient.connectionParams.authToken === token) {
    return
  }

  wsLink.subscriptionClient.connectionParams.authToken = token
  wsLink.subscriptionClient.close()
  wsLink.subscriptionClient.connect()
}

ν•˜μ§€λ§Œ wsLink.subscriptionClient κ°€ λΉ„κ³΅κ°œλ‘œ ν‘œμ‹œλ˜κ³  μ™ΈλΆ€μ—μ„œ μ•‘μ„ΈμŠ€ν•  수 없도둝 λ˜μ–΄ 있기 λ•Œλ¬Έμ— λ‹€μ†Œ ν•΄ν‚Ήλœ λŠλ‚Œμž…λ‹ˆλ‹€.

enhancement

κ°€μž₯ μœ μš©ν•œ λŒ“κΈ€

μƒˆλ‘œμš΄ WebSocketLink λŠ” μ—¬μ „νžˆ subscriptions-transport-ws 의 μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λ―€λ‘œ WebSocketLinkκ°€ μ•„λ‹Œ Subscription Transport ν΄λΌμ΄μ–ΈνŠΈμ— 미듀웨어λ₯Ό μˆ˜λ™μœΌλ‘œ μ μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

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 μ½œλ°±μ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ“  28 λŒ“κΈ€

connectionParamsμ—μ„œ 객체둜 ν•΄μ„λ˜λŠ” ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    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);

그런 λ‹€μŒ μ„œλ²„ μΈ‘μ—μ„œ ν™•μΈν•˜κ³  onOperation 후크λ₯Ό μ‚¬μš©ν•˜μ—¬ μ»¨ν…μŠ€νŠΈμ— μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

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κ°€ μ•„λ‹Œ Subscription Transport ν΄λΌμ΄μ–ΈνŠΈμ— 미듀웨어λ₯Ό μˆ˜λ™μœΌλ‘œ μ μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

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 μ–΄λ–€ 생각이 μžˆμŠ΅λ‹ˆκΉŒ?

λͺ‡ μ£Ό 전에 μ†”λ£¨μ…˜(? 제 κ²½μš°μ—λŠ” μž‘λ™ μ€‘μž…λ‹ˆλ‹€)을 λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€.

GraphQLModule app.module.ts κ°€μ Έμ™”μŠ΅λ‹ˆλ‹€.

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 ν™•μ‹€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. React-Native의 AsyncStorageλ₯Ό connectionParams둜 μ‚¬μš©ν•˜λ €κ³  ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λ‚΄κ°€ μΆ”κ°€ν•œ 비동기 λ•Œλ¬Έμ— μ„œλ²„μ— μ •μ˜λ˜μ§€ μ•Šμ€ μƒνƒœλ‘œ 계속 λ³΄λƒ…λ‹ˆλ‹€.
λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆκΉŒ?

μ—…λ°μ΄νŠΈ:
이 문제λ₯Ό ν•΄κ²°ν•˜μ§€λ§Œ 아직 λ§ˆμŠ€ν„° λΈŒλžœμΉ˜μ— λ³‘ν•©λ˜μ§€ μ•Šμ€ ν’€ μš”μ²­ 이 μžˆμŠ΅λ‹ˆλ‹€.

@jonathanheilmann , κ·€ν•˜μ˜ μ†”λ£¨μ…˜μ€ @helios1138 μ›λž˜ ν•΄κ²° 방법과 μœ μ‚¬ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ ꡬ독 ν΄λΌμ΄μ–ΈνŠΈμ— λŒ€ν•œ connect() APIλŠ” λΉ„κ³΅κ°œλ‘œ ν‘œμ‹œλ˜λ©° μ΄μƒμ μœΌλ‘œλŠ” μ‚¬μš©ν•˜μ§€ μ•Šμ•„μ•Ό ν•©λ‹ˆλ‹€.

곡개 API이고 κ°•μ œλ‘œ λ‹€μ‹œ μ—°κ²°ν•΄μ•Ό ν•˜λŠ” subscriptionClient.close(false, false) λ₯Ό μ‹œλ„ν–ˆμ§€λ§Œ μž‘λ™ν•˜μ§€λ§Œ μ—¬μ „νžˆ ꡬ독이 μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν˜„μž¬ μž‘λ™ν•˜λŠ” μœ μΌν•œ 것은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

subscriptionClient.close();
subscriptionClient.connect(); // this is a private API :-(

이에 λŒ€ν•œ μƒˆλ‘œμš΄ 아이디어가 μžˆμŠ΅λ‹ˆκΉŒ? 여기에 μ†”λ£¨μ…˜μ΄ μžˆλŠ” 것을 μ’‹μ•„ν•©λ‹ˆλ‹€. λ‘œλ“œν•  λ•Œ λ‚΄ apollo 링크λ₯Ό μƒμ„±ν•˜μ§€λ§Œ 일단 λ‚΄ μ‚¬μš©μžκ°€ id에 λ‘œκ·ΈμΈν•˜λ©΄ μ»¨ν…μŠ€νŠΈμ—μ„œ μ „μ†‘λ˜λŠ” 토큰을 μ—…λ°μ΄νŠΈν•˜λŠ” 것을 정말 μ’‹μ•„ν•©λ‹ˆλ‹€.

이것이 μš°λ¦¬κ°€ 생각해낼 수 μžˆλŠ” 졜고의 μ†”λ£¨μ…˜μž…λ‹ˆλ‹€: https://github.com/apollographql/apollo-server/issues/1505.

context λŠ” λͺ¨λ“  μž‘μ—…μ—μ„œ μƒμ„±λ˜λ©° μ‚¬μš©μžλ₯Ό λ™μ μœΌλ‘œ μ‘°νšŒν•˜κΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€.

이것은 λ‚˜μ—κ²Œ λ„μ›€μ΄λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
https://github.com/apollographql/apollo-link/issues/446#issuecomment -410073779

μ›Ή μ†ŒμΌ“ 채널이 열리면(Authorization 헀더 = 토큰 AAA 포함) μ›Ή μ†ŒμΌ“ 링크λ₯Ό μ‚¬μš©ν•˜λŠ” 각 후속 μš”μ²­μ€ 항상 AAA ν† ν°μœΌλ‘œ μ‹λ³„λ©λ‹ˆλ‹€.

λ˜λŠ” 각 μš”μ²­μ— λŒ€ν•΄ λ‹€λ₯Έ Authorization 헀더λ₯Ό λ³΄λ‚΄λŠ” 방법이 μžˆμŠ΅λ‹ˆκΉŒ(λ‹€λ₯Έ ws 채널을 λ‹€μ‹œ μ—¬λŠ” 것 μ œμ™Έ)?

ws에 λŒ€ν•œ μ €μˆ˜μ€€ ν”„λ‘œν† μ½œμ—μ„œ 무슨 일이 μΌμ–΄λ‚˜κ³  μžˆλŠ”μ§€ μ΄ν•΄ν•˜κ³  μ‹ΆμŠ΅λ‹ˆλ‹€.

λ‹΅μž₯ν•΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€!

μ—¬κΈ°κΉŒμ§€ λ‚΄ μ½”λ“œκ°€ μžˆμŠ΅λ‹ˆλ‹€(ν•˜λ‚˜μ˜ ν† ν°μœΌλ‘œ μ˜¬λ°”λ₯΄κ²Œ μž‘λ™ν•¨).

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λ…„ 전에 해결책을 κ²Œμ‹œν–ˆμŠ΅λ‹ˆλ‹€: apollographql/apollo-server#1505. AFAIK 이것은 μ—¬μ „νžˆ β€‹β€‹μž‘λ™ν•©λ‹ˆλ‹€.

@clayne11 κ·€ν•˜μ˜ μ†”λ£¨μ…˜μ€ 미듀웨어λ₯Ό μ‚¬μš©ν•˜λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. ν›Œλ₯­ν•˜λ‹€κ³  μƒκ°ν•˜μ§€λ§Œ Angular 및 Yoga-Graphql둜 μž‘μ—…ν•˜κ³  μ•Œ 수 μ—†λŠ” 이유둜 μ»¨ν…μŠ€νŠΈλ₯Ό 톡해 토큰을 보낼 수 μ—†μŠ΅λ‹ˆλ‹€.

μ„œλ²„μ˜ 미듀웨어에 μžˆλŠ” 토큰에 μ ‘κ·Όν•  수 μžˆλŠ” 방법이 μžˆλ‚˜μš”?
λ‚˜λŠ” Yogaλ₯Ό μ‚¬μš©ν•˜κ³  있으며 이것이 μ»¨ν…μŠ€νŠΈμ—μ„œ 토큰에 μ•‘μ„ΈμŠ€ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€.

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.client.close() λ₯Ό ν˜ΈμΆœν•˜μ—¬ μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€. .... πŸ€”

5κ°œμ™€ 23개의 μ’‹μ•„μš”κ°€ μžˆλŠ” 100개의 흩어져 μžˆλŠ” μ„Έλ―Έ μ†”λ£¨μ…˜ λŒ€μ‹  μƒˆ ν† ν°μœΌλ‘œ λ‹€μ‹œ μ—°κ²°ν•˜λŠ” μ™„μ „ν•œ ν΄λΌμ΄μ–ΈνŠΈ 및 μ„œλ²„ μ˜ˆμ œκ°€ μžˆμŠ΅λ‹ˆκΉŒ?

apolloServer.installSubscriptionHandlers(server); 와 같은 ꡬ독을 μ„€μΉ˜ν•©λ‹ˆλ‹€.
그리고 ApolloServerλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.
const apolloServer = new ApolloServer({ schema, introspection: true, subscriptions: { onConnect: (connectionParams, webSocket, context) => { }, onDisconnect: (webSocket, context) => {}, ...there is no OnOperation: () => {}!!! },

onOperation을 μΆ”κ°€ν•˜λŠ” λ‹€λ₯Έ 방법이 μžˆμŠ΅λ‹ˆκΉŒ? μ•„λ‹ˆλ©΄ μˆ˜λ™μœΌλ‘œ SubscriptionServerλ₯Ό μƒμ„±ν•˜κ³  apolloServer.installSubscriptionHandlers(server) λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ•„μ•Ό ν•©λ‹ˆκΉŒ?

κ²°κ΅­ ν΄λΌμ΄μ–ΈνŠΈλ₯Ό λ™μ μœΌλ‘œ μƒμ„±ν–ˆμŠ΅λ‹ˆλ‹€. μ‚¬μš©μžκ°€ λ‘œκ·ΈμΈν•˜μ§€ μ•Šμ€ 경우 ν΄λΌμ΄μ–ΈνŠΈλŠ” new WebSocket 링크λ₯Ό μƒμ„±ν•˜μ§€ μ•Šκ³  그렇지 μ•ŠμœΌλ©΄ μƒμ„±ν•©λ‹ˆλ‹€. μ•„λž˜μ˜ generateApolloClient ν•¨μˆ˜λŠ” SubscriptionClient 도 λ…ΈμΆœν•˜λ―€λ‘œ μ—°κ²°λ˜μ—ˆμ„ λ•Œ λ””μŠ€νŒ¨μΉ˜ ν˜ΈμΆœμ„ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€(예: redux store에 websocket이 μ—°κ²°λ˜μ—ˆμŒμ„ μ•Œλ¦½λ‹ˆλ‹€).

/**
 * 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>];
};

generateApolloClient ApolloProvider ꡬ성 μš”μ†Œμ˜ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

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 λ‹΅λ³€ 기반 React/NextJS μ•±μš© ν΄λΌμ΄μ–ΈνŠΈλ₯Ό μƒˆλ‘œ 고치렀면 λ°˜μ‘ μ»¨ν…μŠ€νŠΈ APIλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

----------- μ»¨ν…μŠ€νŠΈ API

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));

μ€‘μš” μ°Έκ³  사항: 제곡된 Apollo ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλŠ” 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'),
}),
이 νŽ˜μ΄μ§€κ°€ 도움이 λ˜μ—ˆλ‚˜μš”?
0 / 5 - 0 λ“±κΈ‰