Expo Push API를 이용한 알림 송신

hwakyungChoi·2021년 9월 22일
1


push 알림을 위한 모든 클라이언트 측 기능을 제공하는 expo-notifications 모듈과 함께 expo는 APNs 및 FCM으로 이러한 알림을 보낼 수도 있습니다! 여러분이 해야 하는 건 마지막 단계에서 가져온 ExpoPushToken와 함께 당사의 서버로 요청을 전송하는 것 뿐입니다.

push 알림을 보낼 준비가 되었으면 사용자 record에서 Expo push token을 가져와서 일반적인 이전 HTTPS POST 요청을 사용하여 expo API로 전송합니다. 서버에서 이 작업을 수행할 수 있습니다(원하는 경우 command line 도구를 작성하여 보내거나 앱에서 바로 보낼 수 있습니다). 엑스포 팀과 커뮤니티는 몇 가지 언어로 이 작업을 마무리했습니다.

Push 알림 안정적으로 구현하기

푸시 알림은 서버에서 수신자 장치로 여러 시스템을 통해 이동합니다. 알림은 대부분 전달되지만 도중에 시스템 및 시스템 간의 네트워크 연결에 문제가 있는 경우가 있습니다. 중단 및 오류를 처리하면 푸시 알림이 대상에 보다 안정적으로 도착할 수 있습니다.

동시 연결 제한

한 번에 많은 푸시 알림을 보낼 때는 동시 연결 수를 제한하시길 바랍니다. Node SDK는 이 기능을 구현하고 최대 6개의 동시 연결을 엽니다. 이렇게 하면 최대 부하가 완화되고 expo push notification 서비스가 푸시 알림을 성공적으로 받을 수 있습니다.

실패 시 재시도

푸시 알림을 보내는 첫 번째 단계는 Expo push notification 서비스에 전달하는 것입니다. Expo push notification 서비스는 내부적으로 이를 애플(APNs), 구글(FCM) 또는 기타 푸시 알림 제공업체에 전달할 대기열에 추가합니다. 이 첫 번째 단계는 서버와 Expo push notification 서비스 간의 네트워크 문제, 엑스포 알림 서비스의 중단 또는 가용성 저하, 잘못 구성된 푸시 자격 증명 또는 잘못된 알림 payload 등의 여러 가지 이유로 실패할 수 있습니다.

이러한 실패 중 일부는 일시적입니다. Expo push notification 서비스가 중단되었거나 연결할 수 없는 경우 네트워크 오류, HTTP 429 오류(Too Many Requests) 또는 HTTP 5xx 오류(서버 오류)가 표시되면 exponential backoff를 사용하여 몇 초 동안 기다렸다가 다시 시도하시길바랍니다. 첫 번째 재시도가 실패하면 exponential backoff 이후 더 오래 기다린 후 다시 시도하시길 바랍니다. 이렇게 하면 일시적으로 사용할 수 없는 서비스를 다시 시도하기 전에 복구할 수 있습니다.

여러 번 시도해도 다른 실패는 저절로 해결되지 않습니다. 푸시 알림 payload의 형식이 잘못된 경우 payload 문제를 설명하는 HTTP 400 응답을 받을 수 있습니다. 프로젝트에 대한 push 자격 증명이 없거나 동일한 요청으로 다른 프로젝트에 대한 푸시 알림을 보내는 경우에도 오류가 발생합니다. 이러한 문제를 해결하려면 이러한 문제의 근본 원인을 수정해야 합니다.

Push receipt에서 오류 확인

Expo push notification 서비스는 알림 수신 시 push ticket으로 응답합니다. 푸시 티켓은 엑스포가 알림 payload를 받았으나 아직 전송하지 않았음을 나타냅니다. 각 푸시 티켓에는 티켓 ID가 들어 있으며, 나중에 push receipt을 조회할 때 사용합니다. Expo에서 알림을 APNs, FCM 등에게 전달하려고 시도한 후 push receipt을 사용할 수 있으며 푸시 알림 공급자에 대한 전달이 성공했는지 여부를 알려줍니다.

push receipt을 확인해야 합니다. 푸시 알림을 전달하는 문제가 있는 경우 push receipt은 기본 원인에 대한 정보를 얻는 가장 좋은 방법이기 때문입니다. receipt은 APNs 또는 FCM, 엑스포 푸시 알림 서비스 또는 알림 payload에 문제가 있음을 나타낼 수 있습니다.

푸시 확인 메일은 APN 또는 FCM과 같은 푸시 알림 공급자가 해당 정보로 응답하는 경우 수신자 장치가 알림 등록을 취소했는지 여부를 알려줍니다. push receipt에는 DeviceNotRegistered로 설정된 추가 정보 → 오류 필드가 포함됩니다. 이 시나리오에서 이 장치의 푸시 토큰에 대한 알림 전송을 중지하여 앱이 올바른 시민으로 유지되도록 서버에 다시 등록합니다.

DeviceNotRegistered 오류는 Apple, Google 또는 다른 푸시 알림 공급자가 장치가 등록되지 않은 것으로 간주할 때만 푸시 영수증에 나타납니다. 이 오류는 정의되지 않은 시간이 걸리며 앱을 제거하고 바로 푸시 알림을 전송하여 테스트할 수 없는 경우가 많습니다.

푸시 알림을 보낸 후 15분 후에 push receipt을 확인하는 것이 좋습니다. push receipt은 더 빨리 사용할 수 있는 경우가 많지만, 엑스포 푸시 알림 서비스는 사용자에게 receipt을 제공할 수 있는 편안한 시간을 제공합니다. 15분 후에 푸시 수신이 없으면 엑스포 푸시 알림 서비스에 오류가 있음을 나타냅니다. 마지막으로, push receipt은 24시간 후에 삭제됩니다.

SLA

엑스포 푸시 알림 서비스에는 SLA가 없으며 APNs 및 FCM 서비스에도 가끔 중단이 발생할 수 있습니다. 위의 지침을 따르면 응용 프로그램을 임시 서비스 중단에 대해 강력한 상태로 만들 수 있습니다.

HTTP/2 API

이전에 나열된 라이브러리 중 하나를 사용하는 대신 HTTP/2 API로 직접 요청을 보낼 수 있습니다(이 API는 현재 인증이 필요하지 않습니다).
이렇게 하려면 다음 HTTP 헤더와 함께 https://exp.host/--/api/v2/push/send에 POST 요청을 보냅니다.

host: exp.host
accept: application/json
accept-encoding: gzip, deflate
content-type: application/json

cURL을 사용하는 "hello world" 푸시 알림으로 CLI를 사용하여 보낼 수 있습니다(placeholder 푸시 토큰을 사용자 자신의 것으로 교체).

curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/send" -d '{
  "to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
  "title":"hello",
  "body": "world"
}'

요청 본문은 JSON이어야 합니다. 모두 동일한 프로젝트에 사용되는 경우(아래에 나와 있음) 단일 메시지 개체(위 예) 또는 최대 100개의 메시지 개체 배열일 수 있습니다. 엑스포 서버에 수행해야 하는 요청 수를 효율적으로 최소화하기 위해 여러 메시지를 보내려는 경우 배열을 사용하는 것이 좋습니다. 다음은 네 가지 메시지를 보내는 요청 본문의 예입니다.

[
  {
    "to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
    "sound": "default",
    "body": "Hello world!"
  },
  {
    "to": "ExponentPushToken[yyyyyyyyyyyyyyyyyyyyyy]",
    "badge": 1,
    "body": "You've got mail"
  },
  {
    "to": [
      "ExponentPushToken[zzzzzzzzzzzzzzzzzzzzzz]",
      "ExponentPushToken[aaaaaaaaaaaaaaaaaaaaaa]"
    ],
    "body": "Breaking news!"
  }
]

엑스포 서버는 선택적으로 gzip으로 압축된 요청 boy을 허용합니다. 이렇게 하면 많은 알림을 보내는 데 필요한 업로드 대역폭 양을 크게 줄일 수 있습니다. Node Expo Server SDK는 자동으로 요청을 압축하고 요청을 조절하여 로드를 완화합니다. 따라서 Node Expo Server SDK을 사용하는 것을 추천 드립니다.

Psuh tickets

위의 요청은 두 개의 선택적 필드인 데이터 및 오류가 있는 JSON 개체로 응답합니다. 데이터는 메시지가 전송된 순서와 동일한 푸시 티켓 배열(또는 단일 수신자에게 단일 메시지를 보내는 경우 하나의 푸시 티켓 개체)을 포함합니다. 각 티켓에는 엑스포가 알림을 성공적으로 받았는지 여부를 나타내는 상태 필드와 나중에 푸시 수신을 검색하는 데 사용할 수 있는 ID 필드가 포함됩니다.

참고: 확인 ID와 함께 확인 상태는 메시지가 사용자가 수신한 메시지가 아니라 엑스포의 서버에 의해 수신되었음을 의미합니다. 따라서 푸시 수신을 확인해야 합니다.

위의 예에서 계속하여 성공적인 response body는 다음과 같습니다.

{
  "data": [
    { "status": "ok", "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" },
    { "status": "ok", "id": "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY" },
    { "status": "ok", "id": "ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ" },
    { "status": "ok", "id": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA" }
  ]
}

전체 요청이 아닌 개별 메시지에 오류가 발생한 경우 오류 메시지의 해당 푸시 티켓은 오류 상태가 되며 오류를 설명하는 필드는 다음과 같습니다.

{
  "data": [
    {
      "status": "error",
      "message": "\\\"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]\\\" is not a registered push notification recipient",
      "details": {
        "error": "DeviceNotRegistered"
      }
    },
    {
      "status": "ok",
      "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
    }
  ]
}

전체 요청이 실패한 경우 HTTP 상태 코드는 4xx 또는 5xx이며 오류 필드는 오류 개체 배열(일반적으로 한 개)이 됩니다. 그렇지 않으면 HTTP 상태 코드가 200이 되고 메시지가 iOS 및 안드로이드 푸시 알림 서비스로 전송됩니다!

Push receipts

일괄 알림을 받은 후 expo는 각 notification을 iOS와 안드로이드 푸시 알림 서비스(APN과 FCM)로 전달하도록 대기화합니다. 대부분의 notification은 일반적으로 몇 초 안에 전달됩니다. 때로는 알림 전송에 시간이 더 걸릴 수 있습니다. 특히 iOS 또는 안드로이드 푸시 알림 서비스가 알림을 수신하고 전달하는 데 평소보다 오래 걸리거나 엑스포의 클라우드 인프라에 부하가 많이 걸리는 경우 더욱 그렇습니다.
엑스포가 iOS나 안드로이드 푸시 알림 서비스에 알림을 전달하면, 엑스포는 iOS나 안드로이드 푸시 알림 서비스의 성공 여부를 알려주는 push receipt을 만듭니다. 자격 증명 오류나 서비스 중단 시간 등으로 인해 알림을 전달하는 동안 오류가 발생한 경우 푸시 수신 확인서에 해당 오류에 대한 자세한 정보가 포함됩니다.
푸시 영수증을 가져오려면 https://exp.host/--/api/v2/push/getReceipts로 POST 요청을 보냅니다. 요청 본문은 티켓 ID 문자열 배열인 필드 이름 ID가 있는 JSON 개체여야 합니다.

curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/getReceipts" -d '{
  "ids": [
    "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY",
    "ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ",
  ]
}'

푸시 수신에 대한response body은 푸시 티켓과 매우 유사하며, 데이터와 오류라는 두 개의 선택적 필드가 있는 JSON 객체입니다. 데이터는 receipt에 대한 receipt ID 매핑을 포함합니다. 수신에는 상태 필드, 두 개의 선택적 메시지 및 세부 정보 필드("상태": "오류")가 포함됩니다. 요청된 영수증 ID에 대한 푸시 영수증이 없는 경우 매핑에 해당 ID가 포함되지 않습니다. 위의 요청에 대한 성공적인 응답은 다음과 같습니다.

{
  "data": {
    "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX": { "status": "ok" },
    "ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ": { "status": "ok" }
    // When there is no receipt with a given ID (YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY in this
    // example), the ID is omitted from the response.
  }
}

각 푸시 receipt는 해결해야 할 오류에 대한 정보를 포함할 수 있으므로 확인해야 합니다. 예를 들어 장치가 더 이상 알림을 받을 수 없는 경우 애플 documentation에는 해당 장치에 대한 알림 전송을 중지하라는 메시지가 표시됩니다. 푸시 receipt에는 이러한 오류에 대한 정보가 포함됩니다.

참고:receipt의 상태가 OK(확인)라고 표시되더라도 장치가 메시지를 받았음을 보장하지는 않습니다. 푸시receipt의 "OK(확인)"는 안드로이드 또는 iOS 푸시 알림 서비스가 알림을 성공적으로 수신했음을 의미합니다. 예를 들어 수신자 장치가 꺼져 있는 경우 iOS 또는 안드로이드 푸시 알림 서비스가 메시지를 전달하려고 하지만 장치가 메시지를 반드시 수신하지는 않습니다.

전체 요청이 실패한 경우 HTTP 상태 코드는 4xx 또는 5xx이며 오류 필드는 오류 개체 배열(일반적으로 한 개)이 됩니다. 그렇지 않으면 HTTP 상태 코드는 200이 되고 메시지는 사용자의 장치로 전달됩니다!

에러

엑스포는 이 전체 프로세스 동안 발생하는 오류에 대한 세부 정보를 제공합니다. 서버에서 자동으로 처리할 수 있는 논리를 구현할 수 있도록 아래의 가장 일반적인 오류 중 일부를 다루겠습니다. 엑스포가 어떤 이유로든 안드로이드나 iOS 푸시 알림 서비스에 메시지를 전달할 수 없다면 푸시 영수증의 세부 정보에는 서비스별 정보도 포함될 수 있다. 이 기능은 엑스포에 가능한 버그를 디버깅하고 보고할 때 주로 유용합니다.

개별 오류

푸시 티켓과 푸시 영수증 모두에서 오류 필드가 있는 세부 정보를 찾습니다. 있는 경우 다음 값 중 하나일 수 있으며 다음과 같이 오류를 처리해야 합니다.

Push ticket 오류

DeviceNotRegistered: 장치가 더 이상 푸시 알림을 받을 수 없으므로 해당 엑스포 푸시 토큰에 대한 메시지 전송을 중지해야 합니다.

푸시 receipt 오류

DeviceNotRegistered: 장치가 더 이상 푸시 알림을 받을 수 없으므로 해당 엑스포 푸시 토큰에 대한 메시지 전송을 중지해야 합니다.

  • MessageTooBig: 총 알림 payload가 너무 큽니다. 안드로이드와 iOS에서 총 페이로드는 최대 4096 bytes여야 합니다.
  • MessageRateExceeded: 지정된 장치에 너무 자주 메시지를 보내고 있습니다. exponential backoff 를 구현하고 메시지 전송을 천천히 다시 시도합니다.
  • MismatchSenderId: FCM 푸시 자격 증명에 문제가 있습니다. FCM 푸시 자격 증명에는 FCM 서버 키와 Google-services.json 파일 두 가지가 있습니다. 둘 다 동일한 발송인 ID와 연결되어야 합니다. 서버 키를 찾은 동일한 위치에서 발신인 ID를 찾을 수 있습니다. 서버 키가 Expo push:android:show를 실행한 후 반환된 키와 송신자 ID가 프로젝트의 Google-services.json 파일에 있는 키와 동일한지 확인합니다(project_number).
  • InvalidCredentials: 독립 실행형 앱에 대한 푸시 알림 자격 증명이 잘못되었습니다(예: 해당 자격 증명이 취소되었을 수 있음). 엑스포 build:ios -c를 실행하여 iOS에 대한 새 푸시 알림 자격 증명을 재생성합니다. APNs 키를 해지하면 해당 키를 대체하기 위해 새 키를 업로드할 때까지 해당 키에 의존하는 모든 앱이 더 이상 푸시 알림을 보내거나 받을 수 없습니다. 새 APNs 키를 업로드해도 사용자의 엑스포 푸시 토큰은 변경되지 않습니다.
    - 경우에 따라 이러한 오류에 InvalidProviderToken 에러를 주장하는 추가 세부 정보가 포함될 수 있습니다. 이는 실제로 APN 키 및 provisioning 프로필과 모두 관련이 있습니다. 이 오류를 해결하려면 앱을 다시 빌드하고 새 푸시 키 및 프로비저닝 프로파일을 다시 생성해야 합니다.

요청 오류

푸시 티켓 또는 푸시 수신 확인에 대한 전체 요청에 오류가 있는 경우 오류 개체는 다음 값 중 하나일 수 있으며 다음과 같은 오류를 처리해야 합니다.

  • PUSH_TOO_MANY_Experience_IDS: 푸시 알림을 다른 엑스포 experiences(예: @username/projectAAA과 @username/projectBBB.)으로 보내려고 합니다. 요청에서 관련 푸시 토큰에 대한 경험 이름 매핑에 대한 세부 정보 필드를 확인하고 다른 경험에서 경험 이름을 제거합니다.
  • PUSH_TOO_MANY_NOTIONTS: 하나의 요청으로 100개 이상의 푸시 알림을 보내려고 합니다. 각 요청에서 100개 이하의 알림만 전송하는지 확인합니다.
  • PUSH_TOO_MANY_RECEIPTs: 하나의 요청으로 1000개 이상의 푸시 수신을 받으려고 합니다. 푸시 영수증을 받으려면 1000개 이하의 티켓 ID 문자열만 발송해야 합니다.

추가 보안

푸시 요청을 사용자에게 전달하기 전에 유효한 액세스 토큰과 함께 전송하도록 요청할 수 있습니다. Expo Dashboard에서 강화된 푸시 보안을 활성화할 수 있습니다.
기본적으로 Expo 푸시 토큰과 메시지에 필요한 텍스트 또는 추가 데이터를 전송하여 사용자에게 통지를 보낼 수 있습니다. 이 방법은 설정하기가 쉽지만 토큰이 유출된 경우 악의적인 사용자가 앱을 가장하여 사용자에게 메시지를 보낼 수 있습니다. 이에 대한 인스턴스는 보고된 적이 없지만, 모범 보안 사례를 따르기 위해 추가 보안 계층으로 푸시 토큰과 함께 액세스 토큰을 사용할 수 있습니다.
expo-server-sdk-node를 사용하는 경우 v3.6.0 이상으로 업그레이드하고 액세스 권한을 전달합니다. 생성자의 옵션으로 토큰을 사용합니다. 그렇지 않으면 푸시 API에 대한 모든 요청을 포함하는 'Authorization':'Bearer${accessToken}'은(는) 머리글을 전달합니다
푸시 보안을 활성화한 후 유효한 액세스 토큰 없이 전송된 모든 요청에서 코드에 오류가 발생합니다.:UNAUTHORIZED

Push ticket format

{
  "data": [
    {
      "status": "error" | "ok",
      "id": string, // this is the Receipt ID
      // if status === "error"
      "message": string,
      "details": JSON
    },
    ...
  ],
  // only populated if there was an error with the entire request
  "errors": [{
    "code": number,
    "message": string
  }]
}

Push receipt request format

{
  "ids": string[]
}

Push receipt response format

{
  "data": {
    Receipt ID: {
      "status": "error" | "ok",
      // if status === "error"
      "message": string,
      "details": JSON
    },
    ...
  },
  // only populated if there was an error with the entire request
  "errors": [{
    "code": string,
    "message": string
  }]
}

1개의 댓글

comment-user-thumbnail
2022년 6월 13일

안녕하세요, 좋은 글 감사합니다.
한 가지 여쭙고 싶은 것이 있어 댓글 남깁니다.
현재 저는 react native를 이용하여 hybridapp을 만들고 있고, php-mysql로 백엔드 영역을 구성했습니다.
fetch를 사용하는 부분에서 token을 백엔드 코드로 보내고(저의 경우 php), 이것을 실행하면 푸시 서버(저의 경우 expo server)로 보낸 후 푸시가 오는 것 같은데, fetch에서 백엔드 코드로 token을 보내는 방법이 무엇인지(fetch 안에 있는 json값은 알아서 백엔드 코드로 넘어가는 건지?), 이렇게 받은 token들을 db에 저장하고 싶은데 어떻게 하면 되는지 말씀 좀 구할 수 있을까요??

답글 달기