Brak opisu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

apiv2.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Client = exports.setAccessToken = exports.setRefreshToken = void 0;
  4. const url_1 = require("url");
  5. const stream_1 = require("stream");
  6. const ProxyAgent = require("proxy-agent");
  7. const retry = require("retry");
  8. const abort_controller_1 = require("abort-controller");
  9. const node_fetch_1 = require("node-fetch");
  10. const util_1 = require("util");
  11. const auth = require("./auth");
  12. const error_1 = require("./error");
  13. const logger_1 = require("./logger");
  14. const responseToError_1 = require("./responseToError");
  15. const FormData = require("form-data");
  16. const pkg = require("../package.json");
  17. const CLI_VERSION = pkg.version;
  18. const GOOG_QUOTA_USER = "x-goog-quota-user";
  19. let accessToken = "";
  20. let refreshToken = "";
  21. function setRefreshToken(token = "") {
  22. refreshToken = token;
  23. }
  24. exports.setRefreshToken = setRefreshToken;
  25. function setAccessToken(token = "") {
  26. accessToken = token;
  27. }
  28. exports.setAccessToken = setAccessToken;
  29. function proxyURIFromEnv() {
  30. return (process.env.HTTPS_PROXY ||
  31. process.env.https_proxy ||
  32. process.env.HTTP_PROXY ||
  33. process.env.http_proxy ||
  34. undefined);
  35. }
  36. class Client {
  37. constructor(opts) {
  38. this.opts = opts;
  39. if (this.opts.auth === undefined) {
  40. this.opts.auth = true;
  41. }
  42. if (this.opts.urlPrefix.endsWith("/")) {
  43. this.opts.urlPrefix = this.opts.urlPrefix.substring(0, this.opts.urlPrefix.length - 1);
  44. }
  45. }
  46. get(path, options = {}) {
  47. const reqOptions = Object.assign(options, {
  48. method: "GET",
  49. path,
  50. });
  51. return this.request(reqOptions);
  52. }
  53. post(path, json, options = {}) {
  54. const reqOptions = Object.assign(options, {
  55. method: "POST",
  56. path,
  57. body: json,
  58. });
  59. return this.request(reqOptions);
  60. }
  61. patch(path, json, options = {}) {
  62. const reqOptions = Object.assign(options, {
  63. method: "PATCH",
  64. path,
  65. body: json,
  66. });
  67. return this.request(reqOptions);
  68. }
  69. put(path, json, options = {}) {
  70. const reqOptions = Object.assign(options, {
  71. method: "PUT",
  72. path,
  73. body: json,
  74. });
  75. return this.request(reqOptions);
  76. }
  77. delete(path, options = {}) {
  78. const reqOptions = Object.assign(options, {
  79. method: "DELETE",
  80. path,
  81. });
  82. return this.request(reqOptions);
  83. }
  84. async request(reqOptions) {
  85. if (!reqOptions.responseType) {
  86. reqOptions.responseType = "json";
  87. }
  88. if (reqOptions.responseType === "stream" && !reqOptions.resolveOnHTTPError) {
  89. throw new error_1.FirebaseError("apiv2 will not handle HTTP errors while streaming and you must set `resolveOnHTTPError` and check for res.status >= 400 on your own", { exit: 2 });
  90. }
  91. let internalReqOptions = Object.assign(reqOptions, {
  92. headers: new node_fetch_1.Headers(reqOptions.headers),
  93. });
  94. internalReqOptions = this.addRequestHeaders(internalReqOptions);
  95. if (this.opts.auth) {
  96. internalReqOptions = await this.addAuthHeader(internalReqOptions);
  97. }
  98. try {
  99. return await this.doRequest(internalReqOptions);
  100. }
  101. catch (thrown) {
  102. if (thrown instanceof error_1.FirebaseError) {
  103. throw thrown;
  104. }
  105. let err;
  106. if (thrown instanceof Error) {
  107. err = thrown;
  108. }
  109. else {
  110. err = new Error(thrown);
  111. }
  112. throw new error_1.FirebaseError(`Failed to make request: ${err.message}`, { original: err });
  113. }
  114. }
  115. addRequestHeaders(reqOptions) {
  116. if (!reqOptions.headers) {
  117. reqOptions.headers = new node_fetch_1.Headers();
  118. }
  119. reqOptions.headers.set("Connection", "keep-alive");
  120. if (!reqOptions.headers.has("User-Agent")) {
  121. reqOptions.headers.set("User-Agent", `FirebaseCLI/${CLI_VERSION}`);
  122. }
  123. reqOptions.headers.set("X-Client-Version", `FirebaseCLI/${CLI_VERSION}`);
  124. if (!reqOptions.headers.has("Content-Type")) {
  125. if (reqOptions.responseType === "json") {
  126. reqOptions.headers.set("Content-Type", "application/json");
  127. }
  128. }
  129. return reqOptions;
  130. }
  131. async addAuthHeader(reqOptions) {
  132. if (!reqOptions.headers) {
  133. reqOptions.headers = new node_fetch_1.Headers();
  134. }
  135. let token;
  136. if (isLocalInsecureRequest(this.opts.urlPrefix)) {
  137. token = "owner";
  138. }
  139. else {
  140. token = await this.getAccessToken();
  141. }
  142. reqOptions.headers.set("Authorization", `Bearer ${token}`);
  143. return reqOptions;
  144. }
  145. async getAccessToken() {
  146. if (accessToken) {
  147. return accessToken;
  148. }
  149. const data = (await auth.getAccessToken(refreshToken, []));
  150. return data.access_token;
  151. }
  152. requestURL(options) {
  153. const versionPath = this.opts.apiVersion ? `/${this.opts.apiVersion}` : "";
  154. return `${this.opts.urlPrefix}${versionPath}${options.path}`;
  155. }
  156. async doRequest(options) {
  157. var _a;
  158. if (!options.path.startsWith("/")) {
  159. options.path = "/" + options.path;
  160. }
  161. let fetchURL = this.requestURL(options);
  162. if (options.queryParams) {
  163. if (!(options.queryParams instanceof url_1.URLSearchParams)) {
  164. const sp = new url_1.URLSearchParams();
  165. for (const key of Object.keys(options.queryParams)) {
  166. const value = options.queryParams[key];
  167. sp.append(key, `${value}`);
  168. }
  169. options.queryParams = sp;
  170. }
  171. const queryString = options.queryParams.toString();
  172. if (queryString) {
  173. fetchURL += `?${queryString}`;
  174. }
  175. }
  176. const fetchOptions = {
  177. headers: options.headers,
  178. method: options.method,
  179. redirect: options.redirect,
  180. compress: options.compress,
  181. };
  182. if (this.opts.proxy) {
  183. fetchOptions.agent = new ProxyAgent(this.opts.proxy);
  184. }
  185. const envProxy = proxyURIFromEnv();
  186. if (envProxy) {
  187. fetchOptions.agent = new ProxyAgent(envProxy);
  188. }
  189. if (options.signal) {
  190. fetchOptions.signal = options.signal;
  191. }
  192. let reqTimeout;
  193. if (options.timeout) {
  194. const controller = new abort_controller_1.default();
  195. reqTimeout = setTimeout(() => {
  196. controller.abort();
  197. }, options.timeout);
  198. fetchOptions.signal = controller.signal;
  199. }
  200. if (typeof options.body === "string" || isStream(options.body)) {
  201. fetchOptions.body = options.body;
  202. }
  203. else if (options.body !== undefined) {
  204. fetchOptions.body = JSON.stringify(options.body);
  205. }
  206. const operationOptions = {
  207. retries: ((_a = options.retryCodes) === null || _a === void 0 ? void 0 : _a.length) ? 1 : 2,
  208. minTimeout: 1 * 1000,
  209. maxTimeout: 5 * 1000,
  210. };
  211. if (typeof options.retries === "number") {
  212. operationOptions.retries = options.retries;
  213. }
  214. if (typeof options.retryMinTimeout === "number") {
  215. operationOptions.minTimeout = options.retryMinTimeout;
  216. }
  217. if (typeof options.retryMaxTimeout === "number") {
  218. operationOptions.maxTimeout = options.retryMaxTimeout;
  219. }
  220. const operation = retry.operation(operationOptions);
  221. return await new Promise((resolve, reject) => {
  222. operation.attempt(async (currentAttempt) => {
  223. var _a;
  224. let res;
  225. let body;
  226. try {
  227. if (currentAttempt > 1) {
  228. logger_1.logger.debug(`*** [apiv2] Attempting the request again. Attempt number ${currentAttempt}`);
  229. }
  230. this.logRequest(options);
  231. try {
  232. res = await (0, node_fetch_1.default)(fetchURL, fetchOptions);
  233. }
  234. catch (thrown) {
  235. const err = thrown instanceof Error ? thrown : new Error(thrown);
  236. const isAbortError = err.name.includes("AbortError");
  237. if (isAbortError) {
  238. throw new error_1.FirebaseError(`Timeout reached making request to ${fetchURL}`, {
  239. original: err,
  240. });
  241. }
  242. throw new error_1.FirebaseError(`Failed to make request to ${fetchURL}`, { original: err });
  243. }
  244. finally {
  245. if (reqTimeout) {
  246. clearTimeout(reqTimeout);
  247. }
  248. }
  249. if (options.responseType === "json") {
  250. const text = await res.text();
  251. if (!text.length) {
  252. body = undefined;
  253. }
  254. else {
  255. try {
  256. body = JSON.parse(text);
  257. }
  258. catch (err) {
  259. this.logResponse(res, text, options);
  260. throw new error_1.FirebaseError(`Unable to parse JSON: ${err}`);
  261. }
  262. }
  263. }
  264. else if (options.responseType === "xml") {
  265. body = (await res.text());
  266. }
  267. else if (options.responseType === "stream") {
  268. body = res.body;
  269. }
  270. else {
  271. throw new error_1.FirebaseError(`Unable to interpret response. Please set responseType.`, {
  272. exit: 2,
  273. });
  274. }
  275. }
  276. catch (err) {
  277. return err instanceof error_1.FirebaseError ? reject(err) : reject(new error_1.FirebaseError(`${err}`));
  278. }
  279. this.logResponse(res, body, options);
  280. if (res.status >= 400) {
  281. if ((_a = options.retryCodes) === null || _a === void 0 ? void 0 : _a.includes(res.status)) {
  282. const err = (0, responseToError_1.responseToError)({ statusCode: res.status }, body) || undefined;
  283. if (operation.retry(err)) {
  284. return;
  285. }
  286. }
  287. if (!options.resolveOnHTTPError) {
  288. return reject((0, responseToError_1.responseToError)({ statusCode: res.status }, body));
  289. }
  290. }
  291. resolve({
  292. status: res.status,
  293. response: res,
  294. body,
  295. });
  296. });
  297. });
  298. }
  299. logRequest(options) {
  300. var _a, _b;
  301. let queryParamsLog = "[none]";
  302. if (options.queryParams) {
  303. queryParamsLog = "[omitted]";
  304. if (!((_a = options.skipLog) === null || _a === void 0 ? void 0 : _a.queryParams)) {
  305. queryParamsLog =
  306. options.queryParams instanceof url_1.URLSearchParams
  307. ? options.queryParams.toString()
  308. : JSON.stringify(options.queryParams);
  309. }
  310. }
  311. const logURL = this.requestURL(options);
  312. logger_1.logger.debug(`>>> [apiv2][query] ${options.method} ${logURL} ${queryParamsLog}`);
  313. const headers = options.headers;
  314. if (headers && headers.has(GOOG_QUOTA_USER)) {
  315. logger_1.logger.debug(`>>> [apiv2][(partial)header] ${options.method} ${logURL} x-goog-quota-user=${headers.get(GOOG_QUOTA_USER) || ""}`);
  316. }
  317. if (options.body !== undefined) {
  318. let logBody = "[omitted]";
  319. if (!((_b = options.skipLog) === null || _b === void 0 ? void 0 : _b.body)) {
  320. logBody = bodyToString(options.body);
  321. }
  322. logger_1.logger.debug(`>>> [apiv2][body] ${options.method} ${logURL} ${logBody}`);
  323. }
  324. }
  325. logResponse(res, body, options) {
  326. var _a;
  327. const logURL = this.requestURL(options);
  328. logger_1.logger.debug(`<<< [apiv2][status] ${options.method} ${logURL} ${res.status}`);
  329. let logBody = "[omitted]";
  330. if (!((_a = options.skipLog) === null || _a === void 0 ? void 0 : _a.resBody)) {
  331. logBody = bodyToString(body);
  332. }
  333. logger_1.logger.debug(`<<< [apiv2][body] ${options.method} ${logURL} ${logBody}`);
  334. }
  335. }
  336. exports.Client = Client;
  337. function isLocalInsecureRequest(urlPrefix) {
  338. const u = new url_1.URL(urlPrefix);
  339. return u.protocol === "http:";
  340. }
  341. function bodyToString(body) {
  342. if (isStream(body)) {
  343. return "[stream]";
  344. }
  345. else {
  346. try {
  347. return JSON.stringify(body);
  348. }
  349. catch (_) {
  350. return util_1.default.inspect(body);
  351. }
  352. }
  353. }
  354. function isStream(o) {
  355. return o instanceof stream_1.Readable || o instanceof FormData;
  356. }