<p>apollo-link-ws: bagaimana cara mengubah connectionParams (dan token) setelah pengaturan awal?</p>

Dibuat pada 31 Okt 2017  ·  28Komentar  ·  Sumber: apollographql/apollo-link

Dokumen menjelaskan otentikasi pada soket web sebagai menyediakan token di connectionParams saat membuat WsLink pada awalnya.

Tetapi tidak jelas bagaimana mengubah token nanti dan menginisialisasi ulang koneksi jika pengguna logout dan login lagi (mungkin dengan token yang berbeda)

Saya berhasil mencapai hasil yang diinginkan seperti ini:

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

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

tapi rasanya agak kacau, karena wsLink.subscriptionClient ditandai sebagai pribadi dan tidak seharusnya dapat diakses dari luar

enhancement

Komentar yang paling membantu

Karena WebSocketLink yang baru masih membuat instance subscriptions-transport-ws , saya secara manual menerapkan middleware saya ke klien Subscription Transport, daripada ke WebSocketLink.

Saya menggunakan proses yang sama seperti yang saya lakukan di v1, meskipun rasanya agak sulit untuk tidak memilikinya sebagai bagian dari 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])

Nilai apa pun yang Anda tambahkan ke objek opsi tersedia di callback onOperation di sisi server.

Semua 28 komentar

Anda bisa menggunakan fungsi yang menyelesaikan ke objek di connectionParams.

    const wsLink = new WebSocketLink({
      uri: config.subscriptionURL,
      options: {
        reconnect: true,
        connectionParams: () => ({
          authToken: reduxStore.getState().authentication.token,
        }),
      },
    });

EDIT:

Oh tunggu, ini tidak dipanggil setelah pertama kali... Mungkin berhubungan dengan middleware lain yang menempatkan objek otentikasi pada respons yang dapat Anda parsing dengan sisi server onOperation ?

Inilah yang saya dapatkan untuk Anda, dimodelkan setelah saya melakukannya dengan ApolloClient V1

Tambahkan middleware tautan baru untuk melampirkan token autentikasi ke payload

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

Kemudian sisi server Anda dapat memeriksanya dan menerapkannya ke konteks menggunakan kait 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}`,
    });

mungkin, Anda dapat mencoba mengirim ulang pesan ini: 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);

Apakah ada cara yang disarankan untuk melakukan ini?

berlangganan masalah ini

Karena WebSocketLink yang baru masih membuat instance subscriptions-transport-ws , saya secara manual menerapkan middleware saya ke klien Subscription Transport, daripada ke WebSocketLink.

Saya menggunakan proses yang sama seperti yang saya lakukan di v1, meskipun rasanya agak sulit untuk tidak memilikinya sebagai bagian dari 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])

Nilai apa pun yang Anda tambahkan ke objek opsi tersedia di callback onOperation di sisi server.

juga mengalami masalah ini
@jbaxleyiii ada pendapat?

Saya telah membuat solusi (? Ini sedang terjadi dalam kasus saya) beberapa minggu yang lalu:

Modul GraphQLModule diimpor ke app.module.ts saya :

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

Dalam layanan Otentikasi saya, saya menyuntikkan modul ini ke konstruktor melalui private graphQLModule: GraphQLModule .
Setelah masuk atau keluar yang saya lakukan:

const client = this.graphQLModule.subscriptionClient;
// Close actual connection
client.close(true, true);
// todo: set/unset session token in sessionStorage
// Connect again
(<any>client).connect();

Ini membuka klien langganan saat aplikasi dimulai dan memulai kembali sepenuhnya saat login/logout.

Idealnya ini akan mendukung fungsi pengembalian janji juga, jadi jika kita harus async mengambil token, kita bisa.

@clayne11 Saya tidak yakin itu. Saya mencoba menggunakan AsyncStorage dari React-Native sebagai connectionParams. Tetapi karena async yang saya tambahkan, itu terus mengirim tidak terdefinisi ke server.
Saya punya masalah dengan ini?

MEMPERBARUI:
Ada Permintaan Tarik yang membahas ini tetapi belum digabungkan ke cabang master

@jonathanheilmann , solusi Anda mirip dengan @helios1138 solusi asli. Namun, connect() API untuk klien langganan ditandai sebagai pribadi dan idealnya tidak boleh digunakan.

Saya mencoba melakukan subscriptionClient.close(false, false) yang merupakan API publik dan seharusnya memaksa koneksi ulang, dan berhasil, tetapi langganan tetap tidak berfungsi. Satu-satunya hal yang berfungsi saat ini adalah:

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

Adakah ide baru yang bersih tentang ini? Senang punya solusi di sini. Saya membuat tautan apollo saya saat dimuat tetapi begitu pengguna saya masuk, id sangat ingin memperbarui token yang dikirim dalam konteksnya.

Ini adalah solusi terbaik yang dapat kami temukan: https://github.com/apollographql/apollo-server/issues/1505.

context dibuat pada setiap operasi dan kami hanya mencari pengguna secara dinamis.

dapatkah seseorang mengonfirmasi bahwa setelah saluran soket web dibuka (dengan header Otorisasi = token AAA), setiap permintaan berikutnya yang menggunakan tautan soket web akan selalu diidentifikasi sebagai token AAA.

Atau apakah ada cara untuk mengirim header Otorisasi yang berbeda pada setiap permintaan (selain membuka kembali saluran ws lain)?

Saya ingin memahami apa yang terjadi pada protokol tingkat rendah untuk ws.

Terima kasih atas balasan Anda!

di sini adalah kode saya sejauh ini (berfungsi dengan benar dengan satu token):

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 Untuk memperbarui token/param koneksi secara dinamis, Anda harus mengaturnya menggunakan middleware dari SubscriptionClient. Lihat inti saya di sini: 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]);

Satu-satunya solusi yang saya temukan adalah menutup klien secara manual ...

Saya memposting solusi 1,5 tahun yang lalu: apollographql/apollo-server#1505. AFAIK ini masih berfungsi.

@clayne11 solusi Anda menyiratkan untuk menggunakan middleware. Saya pikir ini hebat tetapi saya bekerja dengan Angular dan Yoga-Graphql dan untuk alasan yang tidak diketahui saya tidak dapat mengirim token melalui konteks.

Apakah ada cara untuk mengakses token di middleware di server?
Saya menggunakan Yoga dan ini adalah cara saya mengakses token dalam konteks

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

Tanpa menutup soket saya mendapatkan banyak kesalahan jaringan apollo (meskipun memperbaiki sendiri kami menunjukkan kesalahan itu dalam banyak pemberitahuan roti panggang sehingga kurang ideal) jadi saya harus menutup soket web setelah memperbarui token, lalu reconnect: true pastikan terhubung kembali... kecuali terhubung kembali dalam infinite loop meskipun soket hanya ditutup sekali

Sunting: Diperbaiki dengan memanggil SubscriptionClient.client.close() alih-alih SubscriptionClient.close() ....

Apakah ada satu contoh klien dan server lengkap cara menghubungkan kembali dengan token baru alih-alih 100 semi-solusi yang tersebar dengan 5 dan 23 suka?

Saya memasang langganan seperti ini apolloServer.installSubscriptionHandlers(server);
dan saya membuat ApolloServer
const apolloServer = new ApolloServer({ schema, introspection: true, subscriptions: { onConnect: (connectionParams, webSocket, context) => { }, onDisconnect: (webSocket, context) => {}, ...there is no OnOperation: () => {}!!! },

Apakah ada cara lain untuk menambahkan onOperation? Atau saya harus membuat SubscriptionServer secara manual dan tidak menggunakan apolloServer.installSubscriptionHandlers(server) ?

Saya akhirnya menghasilkan klien secara dinamis. Jika pengguna tidak masuk, klien tidak membuat tautan new WebSocket , jika tidak demikian. Fungsi generateApolloClient di bawah ini juga menampilkan SubscriptionClient sehingga saya dapat menambahkan panggilan pengiriman saat telah terhubung (mis., beri tahu redux store bahwa soket web terhubung).

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

Berikut adalah kode pada komponen ApolloProvider yang menggunakan fungsi 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>;
};

Ini berfungsi dengan baik dan saya melihat token baru di server setiap kali pengguna keluar dan masuk lagi, tetapi sepertinya sedikit kerja ekstra. Pikiran?

Berdasarkan jawaban @dorsett85 Mungkin ide yang baik untuk menggunakan api konteks reaksi untuk menyegarkan klien untuk aplikasi React/NextJS.

----------- API Konteks

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

--------- Penyedia Apollo

   <ApolloClientContextProvider>
      <ApolloClientContext.Consumer>
        {([apolloClient, setApolloClient]) => {
          return (
            <ApolloProvider client={apolloClient}>
              <Component {...pageProps} />
            </ApolloProvider>
          );
        }}
      </ApolloClientContext.Consumer>
    </ApolloClientContextProvider>

-------- fungsi util menghasilkan Klien baru dengan auth

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({})
  });
};

---------- komponen masuk

const [apolloClient, setApolloClient] = useContext(ApolloClientContext);

----------- saat masuk
setApolloClient(generateApolloClientWithAuth(token));

Catatan penting: Kode Klien Apollo yang disediakan menggunakan SSR

@kamilkisiela Bisakah kita menjadikan anggota wsLink.subscriptionClient menjadi anggota publik? Tidak mungkin itu perlu diubah, bukan?

gunakan get untuk mendapatkan nilai terakhir

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

Apakah ada yang percaya jika saya mengatakan ini berhasil:

const wsLink = process.browser
    ? new WebSocketLink({
        uri: webSocketUri,
        options: {
          reconnect: true,
          connectionParams: () => ({
            token: cookie.get('token'),
          }),
        },
      })
    : null;

yaitu
Ubah saja:

connectionParams: {
            token: cookie.get('token'),
},

KE:

connectionParams: () => ({
            token: cookie.get('token'),
}),
Apakah halaman ini membantu?
0 / 5 - 0 peringkat