batch-request-internal.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. /*! firebase-admin v12.1.1 */
  2. "use strict";
  3. /*!
  4. * Copyright 2019 Google Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. exports.BatchRequestClient = void 0;
  20. const api_request_1 = require("../utils/api-request");
  21. const error_1 = require("../utils/error");
  22. const PART_BOUNDARY = '__END_OF_PART__';
  23. const TEN_SECONDS_IN_MILLIS = 15000;
  24. /**
  25. * An HTTP client that can be used to make batch requests. This client is not tied to any service
  26. * (FCM or otherwise). Therefore it can be used to make batch requests to any service that allows
  27. * it. If this requirement ever arises we can move this implementation to the utils module
  28. * where it can be easily shared among other modules.
  29. */
  30. class BatchRequestClient {
  31. /**
  32. * @param {HttpClient} httpClient The client that will be used to make HTTP calls.
  33. * @param {string} batchUrl The URL that accepts batch requests.
  34. * @param {object=} commonHeaders Optional headers that will be included in all requests.
  35. *
  36. * @constructor
  37. */
  38. constructor(httpClient, batchUrl, commonHeaders) {
  39. this.httpClient = httpClient;
  40. this.batchUrl = batchUrl;
  41. this.commonHeaders = commonHeaders;
  42. }
  43. /**
  44. * Sends the given array of sub requests as a single batch, and parses the results into an array
  45. * of HttpResponse objects.
  46. *
  47. * @param requests - An array of sub requests to send.
  48. * @returns A promise that resolves when the send operation is complete.
  49. */
  50. send(requests) {
  51. requests = requests.map((req) => {
  52. req.headers = Object.assign({}, this.commonHeaders, req.headers);
  53. return req;
  54. });
  55. const requestHeaders = {
  56. 'Content-Type': `multipart/mixed; boundary=${PART_BOUNDARY}`,
  57. };
  58. const request = {
  59. method: 'POST',
  60. url: this.batchUrl,
  61. data: this.getMultipartPayload(requests),
  62. headers: Object.assign({}, this.commonHeaders, requestHeaders),
  63. timeout: TEN_SECONDS_IN_MILLIS,
  64. };
  65. return this.httpClient.send(request).then((response) => {
  66. if (!response.multipart) {
  67. throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INTERNAL_ERROR, 'Expected a multipart response.');
  68. }
  69. return response.multipart.map((buff) => {
  70. return (0, api_request_1.parseHttpResponse)(buff, request);
  71. });
  72. });
  73. }
  74. getMultipartPayload(requests) {
  75. let buffer = '';
  76. requests.forEach((request, idx) => {
  77. buffer += createPart(request, PART_BOUNDARY, idx);
  78. });
  79. buffer += `--${PART_BOUNDARY}--\r\n`;
  80. return Buffer.from(buffer, 'utf-8');
  81. }
  82. }
  83. exports.BatchRequestClient = BatchRequestClient;
  84. /**
  85. * Creates a single part in a multipart HTTP request body. The part consists of several headers
  86. * followed by the serialized sub request as the body. As per the requirements of the FCM batch
  87. * API, sets the content-type header to application/http, and the content-transfer-encoding to
  88. * binary.
  89. *
  90. * @param request - A sub request that will be used to populate the part.
  91. * @param boundary - Multipart boundary string.
  92. * @param idx - An index number that is used to set the content-id header.
  93. * @returns The part as a string that can be included in the HTTP body.
  94. */
  95. function createPart(request, boundary, idx) {
  96. const serializedRequest = serializeSubRequest(request);
  97. let part = `--${boundary}\r\n`;
  98. part += `Content-Length: ${serializedRequest.length}\r\n`;
  99. part += 'Content-Type: application/http\r\n';
  100. part += `content-id: ${idx + 1}\r\n`;
  101. part += 'content-transfer-encoding: binary\r\n';
  102. part += '\r\n';
  103. part += `${serializedRequest}\r\n`;
  104. return part;
  105. }
  106. /**
  107. * Serializes a sub request into a string that can be embedded in a multipart HTTP request. The
  108. * format of the string is the wire format of a typical HTTP request, consisting of a header and a
  109. * body.
  110. *
  111. * @param request - The sub request to be serialized.
  112. * @returns String representation of the SubRequest.
  113. */
  114. function serializeSubRequest(request) {
  115. const requestBody = JSON.stringify(request.body);
  116. let messagePayload = `POST ${request.url} HTTP/1.1\r\n`;
  117. messagePayload += `Content-Length: ${requestBody.length}\r\n`;
  118. messagePayload += 'Content-Type: application/json; charset=UTF-8\r\n';
  119. if (request.headers) {
  120. Object.keys(request.headers).forEach((key) => {
  121. messagePayload += `${key}: ${request.headers[key]}\r\n`;
  122. });
  123. }
  124. messagePayload += '\r\n';
  125. messagePayload += requestBody;
  126. return messagePayload;
  127. }