@graphcommerce/magento-customer 4.4.2 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1499](https://github.com/graphcommerce-org/graphcommerce/pull/1499) [`d205b037f`](https://github.com/graphcommerce-org/graphcommerce/commit/d205b037fee82b8c03993f2c586f477e826093bf) Thanks [@paales](https://github.com/paales)! - Unified and simplified the customer authentication error handling
8
+
9
+ ## 4.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#1492](https://github.com/graphcommerce-org/graphcommerce/pull/1492) [`bed806ddd`](https://github.com/graphcommerce-org/graphcommerce/commit/bed806dddd7e025806a69798ef9587aa165d392f) Thanks [@mikekeehnen](https://github.com/mikekeehnen)! - Removed useExtractCustomerErrors hook and implemented error handling via HttpLink Error
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [[`ffec8800a`](https://github.com/graphcommerce-org/graphcommerce/commit/ffec8800a50ff2fe9b9fc5feeb5a0a878b573f0e), [`bed806ddd`](https://github.com/graphcommerce-org/graphcommerce/commit/bed806dddd7e025806a69798ef9587aa165d392f)]:
18
+ - @graphcommerce/react-hook-form@3.2.1
19
+ - @graphcommerce/graphql@3.2.0
20
+ - @graphcommerce/ecommerce-ui@1.0.17
21
+ - @graphcommerce/magento-graphql@3.0.13
22
+ - @graphcommerce/magento-store@4.2.8
23
+
3
24
  ## 4.4.2
4
25
 
5
26
  ### Patch Changes
@@ -2,20 +2,31 @@ import { ApolloErrorAlert, ApolloErrorAlertProps } from '@graphcommerce/ecommerc
2
2
  import { Trans } from '@lingui/react'
3
3
  import { Link } from '@mui/material'
4
4
  import NextLink from 'next/link'
5
- import { useExtractCustomerErrors } from '../../hooks/useExtractCustomerErrors'
5
+ import { useCustomerSession } from '../../hooks/useCustomerSession'
6
+ import { useAuthorizationErrorMasked } from './useAuthorizationErrorMasked'
6
7
 
7
- type MagentoErrorAlertProps = ApolloErrorAlertProps
8
+ export type ApolloCustomerErrorAlertProps = ApolloErrorAlertProps
8
9
 
9
- export function ApolloCustomerErrorAlert(props: MagentoErrorAlertProps) {
10
- const { error, unauthorized } = useExtractCustomerErrors(props)
10
+ export function ApolloCustomerErrorAlert(props: ApolloCustomerErrorAlertProps) {
11
+ const { error, graphqlErrorAlertProps } = props
12
+ const [newError, unauthorized] = useAuthorizationErrorMasked(error)
13
+ const { token } = useCustomerSession()
11
14
 
12
- const action = unauthorized && (
13
- <NextLink href='/account/signin' passHref>
14
- <Link underline='hover'>
15
- <Trans id='Create Account' /> / <Trans id='Sign in' />
16
- </Link>
17
- </NextLink>
15
+ return (
16
+ <ApolloErrorAlert
17
+ {...props}
18
+ error={newError}
19
+ graphqlErrorAlertProps={{
20
+ action: unauthorized ? (
21
+ <NextLink href='/account/signin' passHref>
22
+ <Link underline='hover'>
23
+ {token ? <Trans id='Sign in' /> : <Trans id='Create Account' />}
24
+ </Link>
25
+ </NextLink>
26
+ ) : (
27
+ graphqlErrorAlertProps?.action
28
+ ),
29
+ }}
30
+ />
18
31
  )
19
-
20
- return <ApolloErrorAlert error={error} graphqlErrorAlertProps={{ action }} />
21
32
  }
@@ -1,41 +1,41 @@
1
- import { ApolloErrorFullPage, ApolloErrorAlertProps } from '@graphcommerce/ecommerce-ui'
1
+ import { ApolloErrorFullPage, ApolloErrorFullPageProps } from '@graphcommerce/ecommerce-ui'
2
2
  import { iconPerson, IconSvg } from '@graphcommerce/next-ui'
3
3
  import { Trans } from '@lingui/react'
4
4
  import { Button } from '@mui/material'
5
5
  import PageLink from 'next/link'
6
- import { useExtractCustomerErrors } from '../../hooks/useExtractCustomerErrors'
6
+ import type { SetOptional } from 'type-fest'
7
+ import { useCustomerSession } from '../../hooks/useCustomerSession'
8
+ import { useAuthorizationErrorMasked } from './useAuthorizationErrorMasked'
7
9
 
8
- type ApolloCustomerErrorFullPageProps = {
9
- signInHref: string
10
- signUpHref: string
11
- } & ApolloErrorAlertProps
10
+ export type ApolloCustomerErrorFullPageProps = {
11
+ /** @deprecated Not used */
12
+ signInHref?: string
13
+ /** @deprecated Not used */
14
+ signUpHref?: string
15
+ } & SetOptional<ApolloErrorFullPageProps, 'icon'>
12
16
 
13
17
  export function ApolloCustomerErrorFullPage(props: ApolloCustomerErrorFullPageProps) {
14
- const { signInHref, signUpHref } = props
15
- const { error, unauthorized } = useExtractCustomerErrors(props)
18
+ const { error, icon, altButton, button, ...alertProps } = props
19
+ const [, unauthorized] = useAuthorizationErrorMasked()
20
+ const { token } = useCustomerSession()
16
21
 
17
22
  return (
18
23
  <ApolloErrorFullPage
19
- error={error}
20
24
  icon={<IconSvg src={iconPerson} size='xxl' />}
25
+ {...props}
26
+ error={error}
21
27
  button={
22
28
  unauthorized ? (
23
- <PageLink href={signInHref} passHref>
29
+ <PageLink href='/account/signin' passHref>
24
30
  <Button variant='contained' color='primary' size='large'>
25
- <Trans id='Log in' />
26
- </Button>
27
- </PageLink>
28
- ) : undefined
29
- }
30
- altButton={
31
- unauthorized ? (
32
- <PageLink href={signUpHref} passHref>
33
- <Button variant='text' color='primary'>
34
- <Trans id='Or create an account' />
31
+ {token ? <Trans id='Sign in' /> : <Trans id='Create Account' />}
35
32
  </Button>
36
33
  </PageLink>
37
- ) : undefined
34
+ ) : (
35
+ button
36
+ )
38
37
  }
38
+ {...alertProps}
39
39
  />
40
40
  )
41
41
  }
@@ -1,7 +1,31 @@
1
1
  import { ApolloErrorSnackbar, ApolloErrorSnackbarProps } from '@graphcommerce/ecommerce-ui'
2
+ import { Trans } from '@lingui/react'
3
+ import { Button, Link } from '@mui/material'
4
+ import NextLink from 'next/link'
5
+ import { useCustomerSession } from '../../hooks/useCustomerSession'
6
+ import { useAuthorizationErrorMasked } from './useAuthorizationErrorMasked'
2
7
 
3
- type ApolloCustomerErrorSnackbarProps = ApolloErrorSnackbarProps
8
+ export type ApolloCustomerErrorSnackbarProps = ApolloErrorSnackbarProps
4
9
 
5
10
  export function ApolloCustomerErrorSnackbar(props: ApolloCustomerErrorSnackbarProps) {
6
- return <ApolloErrorSnackbar {...props} />
11
+ const { error, action } = props
12
+ const [newError, unauthorized] = useAuthorizationErrorMasked(error)
13
+ const { token } = useCustomerSession()
14
+
15
+ return (
16
+ <ApolloErrorSnackbar
17
+ {...props}
18
+ action={
19
+ unauthorized ? (
20
+ <NextLink href='/account/signin' passHref>
21
+ <Button variant='pill' color='secondary'>
22
+ {token ? <Trans id='Sign in' /> : <Trans id='Create Account' />}
23
+ </Button>
24
+ </NextLink>
25
+ ) : (
26
+ action
27
+ )
28
+ }
29
+ />
30
+ )
7
31
  }
@@ -0,0 +1,3 @@
1
+ export * from './ApolloCustomerErrorAlert'
2
+ export * from './ApolloCustomerErrorFullPage'
3
+ export * from './ApolloCustomerErrorSnackbar'
@@ -0,0 +1,16 @@
1
+ import { ApolloError } from '@graphcommerce/graphql'
2
+ import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
3
+ import { i18n } from '@lingui/core'
4
+ import { useCustomerSession } from '../../hooks/useCustomerSession'
5
+
6
+ export function useAuthorizationErrorMasked(error?: ApolloError) {
7
+ const { token } = useCustomerSession()
8
+
9
+ return graphqlErrorByCategory({
10
+ category: 'graphql-authorization',
11
+ error,
12
+ mask: token
13
+ ? i18n._(/* i18n */ `Please reauthenticate and try again`)
14
+ : i18n._(/* i18n */ `You must sign in to continue`),
15
+ })
16
+ }
@@ -4,9 +4,7 @@ export * from './AccountAddresses/AccountAddresses'
4
4
  export * from './AddressFields/AddressFields'
5
5
  export * from './AddressMultiLine/AddressMultiLine'
6
6
  export * from './AddressSingleLine/AddressSingleLine'
7
- export * from './ApolloCustomerError/ApolloCustomerErrorAlert'
8
- export * from './ApolloCustomerError/ApolloCustomerErrorFullPage'
9
- export * from './ApolloCustomerError/ApolloCustomerErrorSnackbar'
7
+ export * from './ApolloCustomerError'
10
8
  export * from './ChangeNameForm/ChangeNameForm'
11
9
  export * from './ChangePasswordForm/ChangePassword.gql'
12
10
  export * from './ChangePasswordForm/ChangePasswordForm'
package/hooks/index.ts CHANGED
@@ -4,6 +4,5 @@ export * from './CustomerToken.gql'
4
4
  export * from './IsEmailAvailable.gql'
5
5
  export * from './useCustomerQuery'
6
6
  export * from './useCustomerSession'
7
- export * from './useExtractCustomerErrors'
8
7
  export * from './useFormIsEmailAvailable'
9
8
  export * from './useGuestQuery'
@@ -1,17 +1,28 @@
1
- import { ApolloCache, NormalizedCacheObject, setContext } from '@graphcommerce/graphql'
2
- import { CustomerTokenDocument } from '../hooks/CustomerToken.gql'
1
+ import {
2
+ ApolloCache,
3
+ ApolloLink,
4
+ ClientContext,
5
+ NormalizedCacheObject,
6
+ setContext,
7
+ } from '@graphcommerce/graphql'
8
+ import { CustomerTokenDocument } from '../hooks'
9
+ import { onAuthenticationError } from './onAuthenticationError'
3
10
 
4
- export const createCustomerTokenLink = (cache: ApolloCache<NormalizedCacheObject>) =>
5
- setContext((_, context) => {
6
- if (!context.headers) context.headers = {}
7
- try {
8
- const query = cache.readQuery({ query: CustomerTokenDocument })
9
- if (query?.customerToken?.token) {
10
- context.headers.authorization = `Bearer ${query?.customerToken?.token}`
11
- return context
12
- }
13
- return context
14
- } catch (error) {
11
+ export const addTokenHeader = setContext((_, context: ClientContext) => {
12
+ if (!context.headers) context.headers = {}
13
+ try {
14
+ const query = context.cache.readQuery({ query: CustomerTokenDocument })
15
+ if (query?.customerToken?.token) {
16
+ context.headers.authorization = `Bearer ${query?.customerToken?.token}`
15
17
  return context
16
18
  }
17
- })
19
+ return context
20
+ } catch (error) {
21
+ return context
22
+ }
23
+ })
24
+
25
+ export const customerTokenLink = ApolloLink.from([addTokenHeader, onAuthenticationError])
26
+
27
+ /** Not really required anymore, you can use customerTokenLink directly */
28
+ export const createCustomerTokenLink = (_: ApolloCache<NormalizedCacheObject>) => customerTokenLink
@@ -0,0 +1,35 @@
1
+ import { InMemoryCache, onError } from '@graphcommerce/graphql'
2
+ import { CustomerTokenDocument } from '../hooks/CustomerToken.gql'
3
+
4
+ function invalidateToken(cache: InMemoryCache) {
5
+ const res = cache.readQuery({
6
+ query: CustomerTokenDocument,
7
+ })
8
+
9
+ if (res?.customerToken?.valid) {
10
+ // Write arbitrary old token to document
11
+ cache.writeQuery({
12
+ query: CustomerTokenDocument,
13
+ data: {
14
+ customerToken: {
15
+ ...res.customerToken,
16
+ createdAt: new Date('2000').toUTCString(),
17
+ valid: false,
18
+ },
19
+ },
20
+ broadcast: true,
21
+ })
22
+ }
23
+ }
24
+
25
+ export const onAuthenticationError = onError(({ graphQLErrors, operation }) => {
26
+ const { cache } = operation.getContext()
27
+ if (graphQLErrors) {
28
+ for (const err of graphQLErrors) {
29
+ if (err.extensions.category === 'graphql-authorization') {
30
+ // Modify the operation context with a new token
31
+ invalidateToken(cache as InMemoryCache)
32
+ }
33
+ }
34
+ }
35
+ })
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-customer",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "4.4.2",
5
+ "version": "4.5.1",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -15,17 +15,18 @@
15
15
  "@graphcommerce/eslint-config-pwa": "^4.1.7",
16
16
  "@graphcommerce/prettier-config-pwa": "^4.0.6",
17
17
  "@graphcommerce/typescript-config-pwa": "^4.0.3",
18
- "@playwright/test": "^1.21.1"
18
+ "@playwright/test": "^1.21.1",
19
+ "type-fest": "^2.12.2"
19
20
  },
20
21
  "dependencies": {
21
- "@graphcommerce/ecommerce-ui": "1.0.16",
22
- "@graphcommerce/graphql": "3.1.3",
22
+ "@graphcommerce/ecommerce-ui": "1.0.17",
23
+ "@graphcommerce/graphql": "3.2.0",
23
24
  "@graphcommerce/graphql-mesh": "4.1.3",
24
25
  "@graphcommerce/image": "3.1.6",
25
- "@graphcommerce/magento-graphql": "3.0.12",
26
- "@graphcommerce/magento-store": "4.2.7",
26
+ "@graphcommerce/magento-graphql": "3.0.13",
27
+ "@graphcommerce/magento-store": "4.2.8",
27
28
  "@graphcommerce/next-ui": "4.8.3",
28
- "@graphcommerce/react-hook-form": "3.2.0"
29
+ "@graphcommerce/react-hook-form": "3.2.1"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "@lingui/react": "^3.13.2",
@@ -1,43 +0,0 @@
1
- import { ApolloError, useApolloClient } from '@graphcommerce/graphql'
2
- import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
3
- import { i18n } from '@lingui/core'
4
- import { useEffect } from 'react'
5
- import { CustomerTokenDocument } from './CustomerToken.gql'
6
-
7
- export type UseExtractErrors = { error?: ApolloError }
8
- export function useExtractCustomerErrors({ error }: UseExtractErrors) {
9
- const client = useApolloClient()
10
-
11
- const [newError, unauthorized] = graphqlErrorByCategory({
12
- category: 'graphql-authorization',
13
- error,
14
- extract: false,
15
- mask: i18n._(/* i18n */ `You must sign in to continue`),
16
- })
17
-
18
- useEffect(() => {
19
- if (!unauthorized) return
20
-
21
- const { customerToken } =
22
- client.cache.readQuery({
23
- query: CustomerTokenDocument,
24
- }) ?? {}
25
-
26
- if (customerToken) {
27
- // Write arbitrary old token to document
28
- client.cache.writeQuery({
29
- query: CustomerTokenDocument,
30
- data: {
31
- customerToken: {
32
- ...customerToken,
33
- createdAt: new Date('2000').toUTCString(),
34
- valid: false,
35
- },
36
- },
37
- broadcast: true,
38
- })
39
- }
40
- }, [client.cache, unauthorized])
41
-
42
- return { error: newError, unauthorized }
43
- }