<p>apollo-link-ws: كيفية تغيير connectionParams (والرمز المميز) بعد الإعداد الأولي؟</p>

تم إنشاؤها على ٣١ أكتوبر ٢٠١٧  ·  28تعليقات  ·  مصدر: apollographql/apollo-link

تصف المستندات المصادقة على مآخذ الويب على أنها توفر الرمز المميز في connectionParams عند إنشاء WsLink في البداية.

ولكن من غير الواضح كيفية تغيير الرمز المميز لاحقًا وإعادة تهيئة الاتصال إذا قام المستخدم بتسجيل الخروج وتسجيل الدخول مرة أخرى (ربما باستخدام رمز مميز مختلف)

لقد تمكنت من تحقيق النتيجة المرجوة مثل هذا:

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.

أنا أستخدم نفس العملية كما فعلت في الإصدار 1 ، على الرغم من أنه يبدو نوعًا من الاختراق لعدم وجوده كجزء من 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);

ثم يمكنك التحقق من جانب الخادم وتطبيقه على السياق باستخدام ربط التشغيل

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.

أنا أستخدم نفس العملية كما فعلت في الإصدار 1 ، على الرغم من أنه يبدو نوعًا من الاختراق لعدم وجوده كجزء من 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 لست متأكدا من ذلك. أحاول استخدام AsyncStorage من React-Native باعتباره connectParams. ولكن بسبب عدم التزامن الذي أضفته ، فإنه يستمر في الإرسال غير محدد إلى الخادم.
لدي مشكلة مع هذا؟

تحديث:
يوجد طلب سحب يعالج هذا ولكن لم يتم دمجه بعد في الفرع الرئيسي

jonathanheilmann ، الحل الخاص بك مشابه للحل الأصلي @ helios1138 . ومع ذلك ، فإن واجهة برمجة تطبيقات connect() لعميل الاشتراك مميزة ويجب عدم استخدامها بشكل مثالي.

لقد حاولت إجراء subscriptionClient.close(false, false) وهي واجهة برمجة تطبيقات عامة وكان يجب أن أجبرت على إعادة الاتصال ، وقد نجحت ، لكن الاشتراك لا يزال لا يعمل. الشيء الوحيد الذي يعمل الآن هو:

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

أي أفكار جديدة نظيفة حول هذا؟ أحب أن يكون لها حل هنا. أقوم بإنشاء رابط apollo الخاص بي عند التحميل ، ولكن بمجرد أن يقوم المستخدم بتسجيل الدخول ، يرغب حقًا في تحديث الرمز المميز الذي يتم إرساله في السياق.

هذا هو أفضل حل تمكنا من التوصل إليه: https://github.com/apollographql/apollo-server/issues/1505.

يتم إنشاء context في كل عملية ونحن ببساطة نبحث عن المستخدم ديناميكيًا.

هل يمكن لأي شخص تأكيد أنه بمجرد فتح قناة websocket (مع عنوان التفويض = رمز AAA) ، سيتم دائمًا تحديد كل طلب لاحق باستخدام رابط websocket كرمز AAA.

أم أن هناك طريقة لإرسال ترويسة تخويل مختلفة لكل طلب (بخلاف إعادة فتح قناة 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 ?

sulliwaneRyannGaleajonathanheilmann @ 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 ولسبب غير معروف لا يمكنني إرسال رمز مميز من خلال السياق.

هل هناك طريقة للوصول إلى الرمز المميز في برمجية وسيطة في الخادم؟
أنا أستخدم اليوجا وهذه هي الطريقة التي يمكنني من خلالها الوصول إلى الرمز المميز في السياق

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

بدون إغلاق المقبس ، أحصل على مجموعة من أخطاء شبكة Apollo (على الرغم من أنها تصحح نفسها ، فإننا نعرض هذه الأخطاء في مجموعة من الإخطارات المحمصة أقل من المثالية) لذلك يتعين علي إغلاق مقبس الويب بعد تحديث الرمز المميز ، ثم reconnect: true تأكد من إعادة الاتصال ... إلا أنه يعيد الاتصال في حلقة لا نهائية حتى لو اعتقدت أن المقبس مغلق مرة واحدة فقط 😔

تحرير: تم الإصلاح عن طريق استدعاء SubscriptionClient.client.close() بدلاً من SubscriptionClient.close() .... 🤔

هل هناك مثال واحد كامل للعميل والخادم حول كيفية إعادة الاتصال برمز جديد بدلاً من 100 حل شبه مبعثر مع 5 و 23 إعجابًا؟

أقوم بتثبيت اشتراكات مثل هذا apolloServer.installSubscriptionHandlers(server);
وأنشأت ApolloServer
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 أيضًا حتى أتمكن من إضافة مكالمات إرسال عندما تكون متصلاً (على سبيل المثال ، أخبر متجر redux أن مقبس الويب متصل).

/**
 * 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 ، قد يكون من الجيد استخدام واجهة برمجة تطبيقات سياق التفاعل لتحديث العميل لتطبيقات React / NextJS.

----------- واجهة برمجة تطبيقات السياق

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 التقييمات