import grpc from '@grpc/grpc-js'; import { google } from '../../google/protobuf/timestamp'; import namespace from '../config/_namespace'; import { JetroutineFunctionReturnTypes, SymbolTable } from '../config/_symbols'; import { remote } from '../proto/runtime/v1alpha1/remote'; import { ApplicationError } from './_errors'; import { LocalRuntimeStub } from './_local'; import { getPostResultRequest, getRuntimeHost } from './_util'; const MAX_MESSAGE_LENGTH = 10485760; // 10MB const isLocalMode = () => ( process.env.JETPACK_LOCAL_MODE || !process.env.JETPACK_RUNTIME_SERVICE_HOST ); const getMetadata = () => { const metadata = new grpc.Metadata(); metadata.set('x-jetpack-sdk-version', 'node'); return metadata; }; export class RuntimeClient { stub?: remote.RemoteExecutorClient; apiKey?: string; metadata: grpc.Metadata; constructor(apiKey?: string) { this.apiKey = apiKey; this.metadata = getMetadata(); } address() { const host = this.apiKey ? getRuntimeHost(this.apiKey) : process.env.JETPACK_RUNTIME_SERVICE_HOST; const port: string = this.apiKey ? '443' : (process.env.JETPACK_RUNTIME_SERVICE_PORT || '80'); return `${host}:${port}`; } dial() { if (isLocalMode()) { return new LocalRuntimeStub(); } if (this.stub) { return this.stub; } const address = this.address(); let combinedCredentials = grpc.credentials.createInsecure(); if (this.apiKey) { const channelCredentials = grpc.credentials.createSsl(); const callCredentials = grpc.credentials.createFromMetadataGenerator( (_options, callback) => { const authMetadata = new grpc.Metadata(); authMetadata.set('Authorization', `Bearer ${this.apiKey}`); callback(null, authMetadata); }, ); combinedCredentials = grpc.credentials.combineChannelCredentials( channelCredentials, callCredentials, ); } const RemoteClient = remote.RemoteExecutorClient; this.stub = new RemoteClient(address, combinedCredentials, { 'grpc.max_send_message_length': MAX_MESSAGE_LENGTH, }); return this.stub; } registerApp() { const request = new remote.RegisterAppRequest({ namespace: namespace.get(), hostname: process.env.HOSTNAME, cron_jobs: SymbolTable.getAllCronjobs(), qualified_symbols: SymbolTable.definedSymbols(), }); return this.dial().RegisterApp(request, this.metadata); } createTask(uniqueName: string, args: any[], targetTime: Date, external: boolean) { if (!uniqueName) { throw new ApplicationError('Unable to create tasks: unique name must be set for jetroutine.'); } const encodedArgs = JSON.stringify(args); const targetTimestampInSeconds = Math.floor(targetTime.getTime() / 1000); const targetTimestamp = new google.protobuf.Timestamp({ seconds: targetTimestampInSeconds }); const task = new remote.Task({ namespace: namespace.get(), hostname: process.env.HOSTNAME, app_name: process.env.JETPACK_LABEL_APP_INSTANCE, project_id: process.env.JETPACK_LABEL_PROJECT_ID, qualified_symbol: uniqueName, target_time: targetTimestamp, encoded_args: Buffer.from(encodedArgs, 'utf8'), ...(external ? {} : { chart_revision: process.env.JETPACK_LABEL_CHART_REVISION }), }); const request = new remote.CreateTaskRequest({ task, }); return this.dial().CreateTask(request, this.metadata); } getTask(taskId: string) { const request = new remote.GetTaskRequest({ task_id: taskId }); return this.dial().GetTask(request, this.metadata); } postResult(execId: string, value?: JetroutineFunctionReturnTypes, error?: remote.Error) { const request = getPostResultRequest(execId, value, error); return this.dial().PostResult(request, this.metadata); } waitForResult(taskId: string) { const request = new remote.WaitForResultRequest({ task_id: taskId }); return this.dial().WaitForResult(request, this.metadata); } }