import { LogLevel } from '../enums/LogLevel.enum'; import { CodePushError, CodePushHttpError } from './CodePushApiSdk.errors'; import { type ApiSdkConfiguration, type ApiSdkDeployReportPackageInfo, type ApiSdkDownloadReportPackageInfo, type ApiSdkNativeUpdateNotification, type ApiSdkQueryUpdatePackageInfo, type ApiSdkRemotePackage, type CheckUpdateRequestInput, type CheckUpdateResponse, DeploymentStatus, FailedPhase, FailureErrorCode, type Http, type ReportDeployFailureInfo, type ReportDeployInput, type ReportDownloadInput, } from './CodePushApiSdk.types'; import { queryStringify } from './utils/queryStringify'; // TODO move this to @appzung/code-push-api-sdk export class CodePushApiSdk { private configuration: ApiSdkConfiguration; constructor( private readonly httpRequester: Http.Requester, private readonly log: (level: LogLevel, message: string) => void, configuration: ApiSdkConfiguration, ) { this.configuration = { ...configuration }; // if (this.configuration.serverUrl.slice(-1) !== '/') { // this.configuration.serverUrl += '/'; // } // this.configuration.serverUrl += 'v0.1/public/codepush'; } async queryUpdateWithCurrentPackage( currentPackageInfo: ApiSdkQueryUpdatePackageInfo, ): Promise { if (!this.configuration.dataTransmissionEnabled) { this.log(LogLevel.DEBUG, 'Skipped querying for update because data transmission is disabled'); return null; } const query: CheckUpdateRequestInput = { deployment_key: this.configuration.releaseChannelPublicId, app_version: currentPackageInfo.appVersion, package_hash: currentPackageInfo.packageHash, is_companion: this.configuration.ignoreAppVersion, label: currentPackageInfo.label, client_unique_id: this.configuration.deviceId, deviceId: this.configuration.deviceId, }; const url = `${this.configuration.serverUrl}/update_check?${queryStringify(query)}`; console.log('query', query); console.log('url', url); const response = await this.httpRequester.request('GET', url); if (response.statusCode < 200 || response.statusCode >= 300) { throw new CodePushHttpError(url, response.statusCode, response.body || 'No body'); } const responseObject: CheckUpdateResponse | Record = JSON.parse(response.body || '{}'); if (!responseObject.update_info) { throw new CodePushError('Missing update info in response'); } const { update_info: updateInfo } = responseObject; if (updateInfo.update_app_version) { return { updateAppVersion: true, appVersion: responseObject.update_info.target_binary_range, }; } if (!updateInfo.is_available) { return null; } return { releaseChannelPublicId: this.configuration.releaseChannelPublicId, description: updateInfo.description, label: updateInfo.label, appVersion: updateInfo.target_binary_range, isMandatory: updateInfo.is_mandatory, packageHash: updateInfo.package_hash, packageSize: updateInfo.package_size, downloadUrl: updateInfo.download_url, }; } async reportStatusDeploy( deployedPackageInfo: { package: ApiSdkDeployReportPackageInfo; status: DeploymentStatus } | null, previousLabelOrAppVersion: string | null, previousDeploymentKey: string | null, failureInfo?: ReportDeployFailureInfo | null, ): Promise { if (!this.configuration.dataTransmissionEnabled) { this.log(LogLevel.DEBUG, 'Skipped reporting deploy status because data transmission is disabled'); return; } if (!this.configuration.telemetryEnabled) { this.log(LogLevel.DEBUG, 'Skipped reporting deploy status because telemetry is disabled'); return; } const requestBody: ReportDeployInput = { app_version: this.configuration.appVersion, deployment_key: this.configuration.releaseChannelPublicId, client_unique_id: this.configuration.deviceId, }; if (deployedPackageInfo) { requestBody.label = deployedPackageInfo.package.label; requestBody.app_version = deployedPackageInfo.package.appVersion; requestBody.status = deployedPackageInfo.status; } if (previousLabelOrAppVersion) { requestBody.previous_label_or_app_version = previousLabelOrAppVersion; } if (previousDeploymentKey) { requestBody.previous_deployment_key = previousDeploymentKey; } if (failureInfo && requestBody.status === DeploymentStatus.FAILED) { const failedPhase = CodePushApiSdk.normalizeFailedPhase(failureInfo.failedPhase); const errorCode = CodePushApiSdk.truncate(failureInfo.errorCode, 64) ?? FailureErrorCode.UNKNOWN; const errorMessage = CodePushApiSdk.truncate(failureInfo.errorMessage, 500); const binaryAppVersion = CodePushApiSdk.truncate(failureInfo.binaryAppVersion, 40); requestBody.error_code = errorCode; requestBody.failed_phase = failedPhase; if (errorMessage) { requestBody.error_message = errorMessage; } if (binaryAppVersion) { requestBody.binary_app_version = binaryAppVersion; } } const url = `${this.configuration.serverUrl}/report_status/deploy`; const response = await this.httpRequester.request('POST', url, JSON.stringify(requestBody)); if (response.statusCode < 200 || response.statusCode >= 300) { throw new CodePushHttpError(url, response.statusCode, response.body || 'No body'); } } private static truncate(value: string | undefined | null, maxLen: number): string | undefined { if (typeof value !== 'string') return undefined; if (value.length === 0) return undefined; return value.length > maxLen ? value.slice(0, maxLen) : value; } private static normalizeFailedPhase(value: string | undefined | null): FailedPhase { switch (value) { case FailedPhase.DOWNLOAD: case FailedPhase.INSTALL: case FailedPhase.RUNTIME: return value; default: return FailedPhase.UNKNOWN; } } async reportStatusDownload(downloadedPackage: ApiSdkDownloadReportPackageInfo): Promise { if (!this.configuration.dataTransmissionEnabled) { this.log(LogLevel.DEBUG, 'Skipped reporting download because data transmission is disabled'); return; } if (!this.configuration.telemetryEnabled) { this.log(LogLevel.DEBUG, 'Skipped reporting download because telemetry is disabled'); return; } const requestBody: ReportDownloadInput = { client_unique_id: this.configuration.deviceId, deployment_key: this.configuration.releaseChannelPublicId, label: downloadedPackage.label, }; const url = `${this.configuration.serverUrl}/report_status/download`; const response = await this.httpRequester.request('POST', url, JSON.stringify(requestBody)); if (response.statusCode < 200 || response.statusCode >= 300) { throw new CodePushHttpError(url, response.statusCode, response.body || 'No body'); } } }