首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Apollo服务器将cookie设置为在下一个js应用程序中访问

Apollo服务器将cookie设置为在下一个js应用程序中访问
EN

Stack Overflow用户
提问于 2019-10-23 16:24:57
回答 2查看 3.6K关注 0票数 2

我有一个Next.js应用程序,我正在使用apollo-server-micro和apollo-client。当用户遇到登录变异时,我无法设置cookie会话。这是我的server.js设置(npm start)

代码语言:javascript
复制
const express = require('express');
const next = require('next');
const { apolloServer } = require('pages/api/graphql');
const cookieSession = require('cookie-session');

const dev = process.env.NODE_ENV !== 'production';
const app = nextApp({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.use(express.static('./public'));

  server.use(
        '/api',
        cookieSession({
            httpOnly: false,
            keys: ['super-secret-key'],
            maxAge: 24 * 60 * 60 * 1000,
            name: 'xyz',
        })
    );

  server.get('*', handle);

  server.listen(8000, () => {
    console.log('Server running on port 8000');
  })
})

pages/api/graphql.js中,我有以下代码(直接来自Next.js文档)

代码语言:javascript
复制
import { ApolloServer } from 'apollo-server-micro'
import { schema } from '../../apollo/schema'

export const apolloServer = new ApolloServer({
  schema,
  context: (ctx) => {
    return ctx.req;
  }
})

export const config = {
  api: {
    bodyParser: false
  }
}

export default apolloServer.createHandler({ path: '/api/graphql' })

下面是用于登录的变体

代码语言:javascript
复制
const jobs = require('../data/jobs');
const users = require('../data/users');

const getUserByEmail = email => users.find(user => user.email === email);

export const resolvers = {
  Query: {
    viewer (_parent, _args, _context, _info) {
      return { id: 1, name: 'John Smith', status: 'cached' }
    },
    jobs: (parent, args, context, info) => {
      return jobs;
    }
  },

  Mutation: {
    login: (parent, args, context, info) => {
      const { email } = args;
      const user = getUserByEmail(email);
      console.log(context.headers);
      if (user) {
        context.req = {
          ...context.req,
          session: user
        }
        return user;
      }
    }
  }
}

现在还没有加密,我只想让它启动并运行起来。如何设置会话(以便cookie显示在Chrome中,并可用于将来的传入请求)

更新: Apollo客户端配置

代码语言:javascript
复制
import React from 'react'
import Head from 'next/head'
import { ApolloProvider } from '@apollo/react-hooks'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'

let apolloClient = null

/**
 * Creates and provides the apolloContext
 * to a next.js PageTree. Use it by wrapping
 * your PageComponent via HOC pattern.
 * @param {Function|Class} PageComponent
 * @param {Object} [config]
 * @param {Boolean} [config.ssr=true]
 */
export function withApollo (PageComponent, { ssr = true } = {}) {
  const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
    const client = apolloClient || initApolloClient(apolloState)
    return (
      <ApolloProvider client={client}>
        <PageComponent {...pageProps} />
      </ApolloProvider>
    )
  }

  // Set the correct displayName in development
  if (process.env.NODE_ENV !== 'production') {
    const displayName =
      PageComponent.displayName || PageComponent.name || 'Component'

    if (displayName === 'App') {
      console.warn('This withApollo HOC only works with PageComponents.')
    }

    WithApollo.displayName = `withApollo(${displayName})`
  }

  if (ssr || PageComponent.getInitialProps) {
    WithApollo.getInitialProps = async ctx => {
      const { AppTree } = ctx

      // Initialize ApolloClient, add it to the ctx object so
      // we can use it in `PageComponent.getInitialProp`.
      const apolloClient = (ctx.apolloClient = initApolloClient())

      // Run wrapped getInitialProps methods
      let pageProps = {}
      if (PageComponent.getInitialProps) {
        pageProps = await PageComponent.getInitialProps(ctx)
      }

      // Only on the server:
      if (typeof window === 'undefined') {
        // When redirecting, the response is finished.
        // No point in continuing to render
        if (ctx.res && ctx.res.finished) {
          return pageProps
        }

        // Only if ssr is enabled
        if (ssr) {
          try {
            // Run all GraphQL queries
            const { getDataFromTree } = await import('@apollo/react-ssr')
            await getDataFromTree(
              <AppTree
                pageProps={{
                  ...pageProps,
                  apolloClient
                }}
              />
            )
          } catch (error) {
            // Prevent Apollo Client GraphQL errors from crashing SSR.
            // Handle them in components via the data.error prop:
            // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
            console.error('Error while running `getDataFromTree`', error)
          }

          // getDataFromTree does not call componentWillUnmount
          // head side effect therefore need to be cleared manually
          Head.rewind()
        }
      }

      // Extract query data from the Apollo store
      const apolloState = apolloClient.cache.extract()

      return {
        ...pageProps,
        apolloState
      }
    }
  }

  return WithApollo
}

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 * @param  {Object} initialState
 */
function initApolloClient (initialState) {
  // Make sure to create a new client for every server-side request so that data
  // isn't shared between connections (which would be bad)
  if (typeof window === 'undefined') {
    return createApolloClient(initialState)
  }

  // Reuse client on the client-side
  if (!apolloClient) {
    apolloClient = createApolloClient(initialState)
  }

  return apolloClient
}

/**
 * Creates and configures the ApolloClient
 * @param  {Object} [initialState={}]
 */
function createApolloClient (initialState = {}) {
  const ssrMode = typeof window === 'undefined'
  const cache = new InMemoryCache().restore(initialState)

  // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
  return new ApolloClient({
    ssrMode,
    link: createIsomorphLink(),
    cache
  })
}

function createIsomorphLink () {
  if (typeof window === 'undefined') {
    const { SchemaLink } = require('apollo-link-schema')
    const { schema } = require('./schema')
    return new SchemaLink({ schema })
  } else {
    const { HttpLink } = require('apollo-link-http')
    return new HttpLink({
      uri: '/apii/graphql',
      credentials: 'same-origin'
    })
  }
}

更新:使用apollo micro的Apollo-server配置

代码语言:javascript
复制
import { ApolloServer } from 'apollo-server-micro'
import { schema } from '../../apollo/schema'

export const apolloServer = new ApolloServer({
  schema,
  context: (ctx) => {
    return ctx.req;
  }
})

export const config = {
  api: {
    bodyParser: false
  }
}

export default apolloServer.createHandler({ path: '/api/graphql' })
EN

回答 2

Stack Overflow用户

发布于 2019-10-24 11:08:19

如果您使用的是SchemaLink,我认为您可以直接从PageContext获取req对象,并使用它来重新构造GraphQL上下文对象。

代码语言:javascript
复制
const { AppTree, req } = ctx
// elsewhere
new SchemaLink({ schema, context: { req } })

这里有一个不使用SchemaLink的示例,您可以在项目中采用它。关键是从PageContext中提取Cookie值。然后,您可以使用它来创建一个填充Cookie头的自定义fetch实现,然后将这个fetch函数传递给您的HttpLink构造函数。

代码语言:javascript
复制
let apolloClient: ApolloClient<any>

export const getClient = (
  initialState: NormalizedCacheObject = {},
  cookie?: string,
): ApolloClient<any> => {
  const fetch: WindowOrWorkerGlobalScope['fetch'] = async (url, init = {}) => {
    const headers = { ...init.headers } as Record<string, string>
    if (cookie) {
      headers.Cookie = cookie
    }

    const response = await unfetch(url, {
      ...init,
      headers,
    })
    return response
  }

  return new ApolloClient<any>({
    ssrMode: typeof window === 'undefined',
    link: new HttpLink({
      uri: 'http://localhost:3000/graphql',
      credentials: 'same-origin',
      fetch,
    }),
    cache: new InMemoryCache().restore(initialState),
  })
}

export const initApollo = (
  initialState?: NormalizedCacheObject,
  cookie?: string,
): ApolloClient<any> => {
  if (typeof window === 'undefined') {
    return getClient(initialState, cookie)
  }
  if (!apolloClient) {
    apolloClient = getClient(initialState, cookie)
  }

  return apolloClient
}

export const withApollo = (PageComponent: NextPage, { ssr = true } = {}) => {
  const WithApollo = ({ apolloState, ...pageProps }: WithApolloProps) => {
    const client = apolloClient || initApollo(apolloState)

    return (
      <ApolloProvider client={client} >
          <PageComponent {...pageProps} />
      </ApolloProvider>
    )
  }

  if (ssr || PageComponent.getInitialProps) {
    WithApollo.getInitialProps = async (ctx: PageContext) => {
      const { AppTree } = ctx
      const cookie = (ctx.req && ctx.req.headers.cookie) || undefined
      const apolloClient = (ctx.apolloClient = initApollo({}, cookie))

      let pageProps = {}
      if (PageComponent.getInitialProps) {
        pageProps = await PageComponent.getInitialProps(ctx)
      }

      if (typeof window === 'undefined') {
        if (ctx.res && ctx.res.finished) {
          return pageProps
        }

        if (ssr) {
          try {
            const { getDataFromTree } = await import('@apollo/react-ssr')
            await getDataFromTree(
                <AppTree
                    pageProps={{
                      ...pageProps,
                      apolloClient,
                    }}
                />,
            )
          } catch (error) {
            console.error('Error while running `getDataFromTree`', error)
          }

          Head.rewind()
        }
      }

      const apolloState = apolloClient.cache.extract()

      return {
        ...pageProps,
        apolloState,
      }
    }
  }

  return WithApollo
}

另外,您需要确保在初始化Apollo Server时正确设置了上下文。不要只返回req对象,因为这会将您的上下文设置为req对象。取而代之的是:

代码语言:javascript
复制
context: ({ req }) => {
  return { req };
}

然后确保您在解析器中正确地改变了会话:

代码语言:javascript
复制
context.req.session.user = user

实际上,您的代码覆盖了用户的session对象,这不是您想要做的。在会话上设置user属性意味着您可以在代码中的其他位置使用context.req.session.user访问它。

票数 1
EN

Stack Overflow用户

发布于 2021-06-28 14:59:33

下面是本文https://www.rockyourcode.com/nextjs-with-apollo-ssr-cookies-and-typescript/中的一个解决方案

代码语言:javascript
复制
import {
  ApolloClient,
  InMemoryCache,
  NormalizedCacheObject,
} from "@apollo/client";
import { createUploadLink } from "apollo-upload-client";
import merge from "deepmerge";
import { IncomingHttpHeaders } from "http";
import fetch from "isomorphic-unfetch";
import isEqual from "lodash/isEqual";
import { AppProps } from "next/app";
import { useMemo } from "react";

const APOLLO_STATE_PROP_NAME = "__APOLLO_STATE__";

let apolloClient: ApolloClient<NormalizedCacheObject> | undefined;

const createApolloClient = (headers: IncomingHttpHeaders | null = null) => {
  const enhancedFetch = (url: RequestInfo, init: RequestInit) => {
    return fetch(url, {
      ...init,
      headers: {
        ...init.headers,
        "Access-Control-Allow-Origin": "*", // TODO: Check this out
        Cookie: headers?.cookie ?? "",
      },
    }).then((response) => response);
  };

  return new ApolloClient({
    ssrMode: typeof window === "undefined",
    link: createUploadLink({
      uri: "http://localhost:4000/graphql",
      // Make sure that CORS and cookies work
      fetchOptions: {
        mode: "cors",
      },
      credentials: "include",
      fetch: enhancedFetch,
    }),
    cache: new InMemoryCache(),
  });
};

type InitialState = NormalizedCacheObject | undefined;

interface IInitializeApollo {
  headers?: IncomingHttpHeaders | null;
  initialState?: InitialState | null;
}

export const initializeApollo = (
  { headers, initialState }: IInitializeApollo = {
    headers: null,
    initialState: null,
  },
) => {
  const _apolloClient = apolloClient ?? createApolloClient(headers);

  if (initialState) {
    const existingCache = _apolloClient.extract();

    const data = merge(initialState, existingCache, {
      arrayMerge: (destinationArray, sourceArray) => [
        ...sourceArray,
        ...destinationArray.filter((d) =>
          sourceArray.every((s) => !isEqual(d, s)),
        ),
      ],
    });

    _apolloClient.cache.restore(data);
  }

  if (typeof window === "undefined") return _apolloClient;

  if (!apolloClient) apolloClient = _apolloClient;
  return _apolloClient;
};

export const addApolloState = (
  client: ApolloClient<NormalizedCacheObject>,
  pageProps: AppProps["pageProps"],
) => {
  if (pageProps?.props) {
    pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
  }

  return pageProps;
};

export function useApollo(pageProps: AppProps["pageProps"]) {
  const state = pageProps[APOLLO_STATE_PROP_NAME];
  const store = useMemo(
    () => initializeApollo({ initialState: state }),
    [state],
  );
  return store;
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58518568

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档