@splunk/rum-cli
Version:
Tools for handling symbol and mapping files for symbolication
150 lines (149 loc) • 7.61 kB
JavaScript
;
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mockUploadFile = exports.uploadFile = exports.fetchAndroidMappingMetadata = exports.ErrorCategory = void 0;
exports.formatCLIErrorMessage = formatCLIErrorMessage;
const axios_1 = __importDefault(require("axios"));
const fs_1 = __importDefault(require("fs"));
const form_data_1 = __importDefault(require("form-data"));
const TOKEN_HEADER = 'X-SF-Token';
var ErrorCategory;
(function (ErrorCategory) {
ErrorCategory["RequestEntityTooLarge"] = "REQUEST_ENTITY_TOO_LARGE";
ErrorCategory["NetworkIssue"] = "NETWORK_ISSUE";
ErrorCategory["NoResponse"] = "NO_RESPONSE";
ErrorCategory["GeneralHttpError"] = "GENERAL_HTTP_ERROR";
ErrorCategory["Unexpected"] = "UNEXPECTED";
})(ErrorCategory || (exports.ErrorCategory = ErrorCategory = {}));
function formatCLIErrorMessage(error) {
var _a, _b, _c;
const entryIndent = ' ';
const detailEntries = [];
// core technical message
detailEntries.push(`${entryIndent}"message": "${error.message}"`);
// attempted url if available
if ((_a = error.details) === null || _a === void 0 ? void 0 : _a.url) {
detailEntries.push(`${entryIndent}"url": "${error.details.url}"`);
}
// http status code if available
if (((_b = error.details) === null || _b === void 0 ? void 0 : _b.status) !== undefined) {
detailEntries.push(`${entryIndent}"status": ${error.details.status}`);
}
// codes from error.details.data (e.g., 'ENOTFOUND') if available
if ((_c = error.details) === null || _c === void 0 ? void 0 : _c.data) {
const data = error.details.data; // data is now DetailDataObject | string | undefined
// Check if data is an object (and not null) before trying to access properties
if (typeof data === 'object' && data !== null) {
// Safely check if 'code' property exists and is a string
if ('code' in data && typeof data.code === 'string') {
detailEntries.push(`${entryIndent}"code": "${data.code}"`);
}
// can add other fields from data if needed, e.g.:
// if ('someOtherField' in data && typeof data.someOtherField === 'expectedType') {
// detailEntries.push(`${entryIndent}"someOtherField": "${data.someOtherField}"`);
// }
}
else if (typeof data === 'string') {
detailEntries.push(`${entryIndent}"responseData": "${data}"`);
}
}
const detailsBlockContent = detailEntries.length > 0 ? `\n${detailEntries.join(',\n')}\n` : '';
const detailsBlock = `details: [${detailsBlockContent}]`;
return `Error: ${error.userFriendlyMessage}\n${detailsBlock}`;
}
const fetchAndroidMappingMetadata = (_a) => __awaiter(void 0, [_a], void 0, function* ({ url, token, axiosInstance }) {
var _b, _c, _d;
const headers = {
'X-SF-Token': token,
'Accept': 'application/json',
};
try {
const client = axiosInstance || axios_1.default;
const response = yield client.get(url, { headers });
return response.data;
}
catch (error) {
if (axios_1.default.isAxiosError(error)) {
throw new Error(`HTTP ${(_b = error.response) === null || _b === void 0 ? void 0 : _b.status}: ${(_c = error.response) === null || _c === void 0 ? void 0 : _c.statusText}\nResponse Data: ${JSON.stringify((_d = error.response) === null || _d === void 0 ? void 0 : _d.data, null, 2)}`);
}
else {
throw error;
}
}
});
exports.fetchAndroidMappingMetadata = fetchAndroidMappingMetadata;
// This uploadFile method will be used by all the different commands that want to upload various types of
// symbolication files to o11y cloud. The url, file, and additional parameters are to be prepared by the
// calling method. Various errors, Error, axiosErrors and all should be handled by the caller of this method.
// Since the API contracts with the backend are not yet determined. This is subject to change
const uploadFile = (_a, axiosInstance_1) => __awaiter(void 0, [_a, axiosInstance_1], void 0, function* ({ url, file, token, parameters, onProgress }, axiosInstance) {
const formData = new form_data_1.default();
formData.append(file.fieldName, fs_1.default.createReadStream(file.filePath));
for (const [key, value] of Object.entries(parameters)) {
formData.append(key, value);
}
const fileSizeInBytes = fs_1.default.statSync(file.filePath).size;
yield (axiosInstance || axios_1.default).put(url, formData, {
headers: Object.assign(Object.assign({}, formData.getHeaders()), { [TOKEN_HEADER]: token }),
onUploadProgress: (progressEvent) => {
const loaded = progressEvent.loaded;
const total = progressEvent.total || fileSizeInBytes;
const progress = (loaded / total) * 100;
if (onProgress) {
onProgress({ progress, loaded, total });
}
},
});
});
exports.uploadFile = uploadFile;
// temporary function
// mockUploadFile can be used when the endpoint for the real uploadFile call is not ready
const mockUploadFile = (_a) => __awaiter(void 0, [_a], void 0, function* ({ file, onProgress }) {
const fileSizeInBytes = fs_1.default.statSync(file.filePath).size;
return new Promise((resolve) => {
const mbps = 25;
const bytes_to_megabits = (bytes) => bytes * 8 / 1000 / 1000;
// simulate axios progress events
const tick = 50;
let msElapsed = 0;
const intervalId = setInterval(() => {
msElapsed += tick;
const loaded = Math.floor((msElapsed / 1000) * mbps / 8 * 1024 * 1024);
const total = fileSizeInBytes;
const progress = (loaded / total) * 100;
onProgress === null || onProgress === void 0 ? void 0 : onProgress({ loaded, total, progress });
}, tick);
// simulate axios completion
setTimeout(() => {
clearInterval(intervalId);
resolve();
}, bytes_to_megabits(fileSizeInBytes) / mbps * 1000);
});
});
exports.mockUploadFile = mockUploadFile;