expo-image-picker 和 apollo-upload-client

问题描述 投票:0回答:1

我想用 React Native Expo 创建聊天应用程序。我使用 expo-image-picker 拍照并将其上传到我使用 apollo 构建的 api graphql。我使用 apollo-upload-client 上传图片,但我发现该包不支持此 18.0.0 版本的 React Native。 我收到 Apollo 错误:响应不成功:当我尝试上传时收到状态代码 400

这是我的 apollo 客户端实例:

import fetch from "cross-fetch";
import {
  ApolloClient,
  NormalizedCacheObject,
  InMemoryCache,
} from "@apollo/client";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { ApolloLink, HttpLink, split } from "@apollo/client/core";
import { getMainDefinition } from "@apollo/client/utilities";
import { createClient } from "graphql-ws";
import { AuthStorage } from "./utils/AuthStorage";
import createUploadLink from "apollo-upload-client/createUploadLink.mjs";

export const apolloClient = async (
  token?: string
): Promise<ApolloClient<NormalizedCacheObject>> => {
  const API_URI = process.env.EXPO_PUBLIC_API_URI;
  const API_URI_WS = process.env.EXPO_PUBLIC_URI_WS;

  const user = token ? token : (await AuthStorage.isAuth())?.token;
  const httpLink: HttpLink = new HttpLink({
    uri: API_URI,
    fetch,
    headers: {
      authorization: `Bearer ${user}`,
    },
  });

  const wsLink: GraphQLWsLink = new GraphQLWsLink(
    createClient({
      url: API_URI_WS as string,
      lazy: true,
      connectionParams: {
        authentication: `Bearer ${user}`,
      },
    })
  );

  const uploadLink = createUploadLink({
    uri: API_URI,
    fetch,
    headers: {
      authorization: `Bearer ${user}`,
    },
  });

  const splitLink = split(
    ({ query }) => {
      const definition = getMainDefinition(query);
      return (
        definition.kind === "OperationDefinition" &&
        definition.operation === "subscription"
      );
    },
    wsLink,
    httpLink
  );

  const link = ApolloLink.split(
    (operation) => {
      const definition = getMainDefinition(operation.query);
      return (
        definition.kind === "OperationDefinition" &&
        definition.operation === "mutation" &&
        !!definition.selectionSet?.selections.find(
          (selection) =>
            selection.kind === "Field" && selection.name.value === "upload"
        )
      );
    },
    uploadLink as any,
    splitLink
  );

  const client = new ApolloClient({
    link: link,
    cache: new InMemoryCache(),
  });

  return client;
};

这是上传带有消息的图像的突变:

export const SEND_MESSAGE_MOBILE = gql`
  mutation SendMessageDiscussGroupMobile(
    $discussionId: Float!
    $userId: Float!
    $messageInput: MessageInput!
    $data: [Upload!]
    $receiverId: Float
    $discussGroupId: Float
  ) {
    sendMessageDiscussGroupMobile(
      discussionId: $discussionId
      userId: $userId
      messageInput: $messageInput
      data: $data
      receiverId: $receiverId
      discussGroupId: $discussGroupId
    ) {
      theme
      createdAt
      updatedAt
      User {
        lastname
        firstname
        id
        photo
        status
      }
      Receiver {
        firstname
        lastname
        id
        photo
        status
      }
      DiscussGroup {
        groupName
        coverPhoto
        id
      }
      messages {
        id
        User {
          id
          lastname
          firstname
          photo
          status
        }
        content
        files {
          name
          extension
          url
          id
        }
        receiverId
        discussGroupId
        createdAt
        updatedAt
      }
      id
    }
  }
`;

这是拍照上传的方法:

const cameraPress = async () => {
    try {
      await requestCameraPermissionsAsync();
      let result = await launchCameraAsync({
        cameraType: CameraType.back,
        allowsEditing: false,
        aspect: [1, 1],
        quality: 1,
      });
      if (!result.canceled) {
        const reponse = await fetch(result.assets[0].uri);
        const blob = await reponse.blob();
        const file = new File([blob], result.assets[0].fileName as string, {
          type: result.assets[0].mimeType,
        });
        await sendMessage([file], message);
      }
    } catch (error) {
      console.log(error);
    }
  };
react-native expo apollo expo-image-picker apollo-upload-client
1个回答
-1
投票

大家也有同样的问题 "@apollo/client": "^3.9.7", "apollo-upload-client": "^18.0.0", "世博会": "~50.0.17", "expo-image-picker": "~14.7.1",

带有斑点的结果: https://prnt.sc/JmTZy7RNsIoA https://prnt.sc/MkXxL00C6mjS

FormData 的结果: https://prnt.sc/YZcwDlJ9NgEK https://prnt.sc/93Fw0icDvv1P

import { ApolloClient, InMemoryCache, ApolloLink, NormalizedCacheObject, Observable } from '@apollo/client'
import { onError } from  '@apollo/client/link/error';
import AsyncStorage from '@react-native-async-storage/async-storage';
import createUploadLink from "apollo-upload-client/createUploadLink.mjs";
import { parseHeaders } from '@apollo/client/link/http/parseAndCheckHttpResponse';

import { router } from "expo-router";

// utils
import toaster from './toaster';

export const uploadFetch = (url: string, options: any) =>
  new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.onload = () => {
      const opts: any = {
        status: xhr.status,
        statusText: xhr.statusText,
        headers: parseHeaders(xhr.getAllResponseHeaders() || ''),
      };
      opts.url =
        'responseURL' in xhr
          ? xhr.responseURL
          : opts.headers.get('X-Request-URL');
      const body = 'response' in xhr ? xhr.response : (xhr as any).responseText;
      resolve(new Response(body, opts));
    };
    xhr.onerror = () => {
      reject(new TypeError('Network request failed'));
    };
    xhr.ontimeout = () => {
      reject(new TypeError('Network request failed'));
    };
    xhr.open(options.method, url, true);

    Object.keys(options.headers).forEach((key) => {
      xhr.setRequestHeader(key, options.headers[key]);
    });

    if (xhr.upload) {
      xhr.upload.onprogress = options.onProgress;
    }

    options.onAbortPossible(() => {
      xhr.abort();
    });

    xhr.send(options.body);
  });

  const customFetch = (uri: any, options: any) => {
    if (options.onProgress) {
      return uploadFetch(uri, options);
    }
    return fetch(uri, options);
  };

// @ts-ignore
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
// @ts-ignore
const httpLink = new createUploadLink({ uri: apiUrl});

const authMiddleware = new ApolloLink((operation, forward) => {
  return new Observable((observer) => {
    const fetchToken = async () => {
      try {
        const token = await AsyncStorage.getItem('authToken');
        observer.next(token);
        observer.complete();
      } catch (error) {
        observer.error(error);
      }
    };

    fetchToken();
  }).flatMap((token) => {
    operation.setContext(({ headers = {} }) => ({
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : '',
      },
    }));

    return forward(operation);
  });
});

const errorLink = onError(({ graphQLErrors, networkError }: any) => {
  if (graphQLErrors) {
    if (graphQLErrors?.[0]?.extensions?.validation) {
      const errors = graphQLErrors?.[0]?.extensions?.validation;
      Object.keys(errors).forEach((key) => {
        errors[key].forEach((error: any) => {
          toaster(error);
        });
      });
      return;
    }
    if (graphQLErrors?.[0]?.extensions?.reason) {
      const errors = graphQLErrors?.[0]?.extensions?.reason;
      toaster(errors);
      return;
    }
    graphQLErrors.forEach(({ message }: {message: string}) => {
      if (message == 'Access denied. The team is deactivated.'
      // && !  user?.is_impersonated
      ) {
        router.push("/login")
      } else if (message == 'This action is unauthorized.'
      // && !user?.is_impersonated
      ) {
        toaster(`GraphQL Error: ${message}`);
      } else {
        toaster(`GraphQL Error: ${message}`);
      }
    });
  }
  if (graphQLErrors) {
    const {ApolloError} = graphQLErrors;
    toaster(ApolloError || graphQLErrors[0].message)
  }
  if (networkError) {
    toaster(`Network Error: ${networkError.message}`)
  }
});


const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
  link: ApolloLink.from([errorLink, authMiddleware.concat(httpLink) ]),
  cache: new InMemoryCache(),
});

export {client};

图Ql突变

export const UPDATE_AVATAR = gql`
  mutation UpdateProfile( $avatar: Upload!) {
    updateProfile(input: {avatar: $avatar}) {
      avatar_url
    }
  }
`;

示例代码在这里

  const [image, setImage] = useState<string | null>(null);

  const pickImage = async (useLibrary: boolean) => {
    let result;

    const options: ImagePicker.ImagePickerOptions = {
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
      allowsMultipleSelection: false,
    };

    if (useLibrary) {
      result = await ImagePicker.launchImageLibraryAsync(options);
    } else {
      await ImagePicker.requestCameraPermissionsAsync();
      result = await ImagePicker.launchCameraAsync(options);
    }

    if (!result.canceled) {
      uploadFile(result);
      setImage(result.assets[0].uri);
    }
  };

  const uploadFile = async (file) => {
    const response = await fetch(file.assets[0].uri);
    const blob = await response.blob();

    // or FormData
    const formData = new FormData();
    formData.append("avatar", {
      uri: file.assets[0].uri,
      type: "image/jpeg",
      name: "name.jpeg",
      fileName: "name.jpeg",
      size: file.assets[0].size,
    });


    try {
      const data = await updateProfile({
        // or variables: {avatar: blob} - result the same one
        variables: {
          avatar: blob,
        },
      });
    } catch (e) {
      console.error("Error Occured", e);
    }
  };

© www.soinside.com 2019 - 2024. All rights reserved.