Skip to main content
Glama

mcp-google-sheets

helpers.ts12.1 kB
import { AuthenticationType, httpClient, HttpError, HttpMethod, QueryParams, } from '@activepieces/pieces-common'; import { ethers } from 'ethers'; import { ATTESTATION_API, DEVICE_DEFINIATION_API, IDENTITY_BASE_URL, Operator, TELEMETRY_BASE_URL, TOKEN_EXCHANGE_API, TriggerField, VEHICLE_EVENTS_API, } from './constants'; import { AttestationResponse, AuthRespone, CreateWebhookParams, DeviceDefinitionResponse, DeviceDefinitionsSearchResponse, SignatureChallenge, TokenExchangeResponse, VehicleEventTrigger, } from './types'; export interface DimoClientOptions { clientId: string; redirectUri: string; apiKey: string; } export class DimoClient { private clientId: string; private redirectUri: string; private apiKey: string; constructor(options: DimoClientOptions) { this.clientId = options.clientId; this.redirectUri = options.redirectUri; this.apiKey = options.apiKey; } async generateChallenge() { const response = await httpClient.sendRequest<SignatureChallenge>({ method: HttpMethod.POST, url: 'https://auth.dimo.zone/auth/web3/generate_challenge', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', }, queryParams: { client_id: this.clientId, domain: this.redirectUri, address: this.clientId, scope: 'openid email', response_type: 'code', }, }); return response.body; } async signChallenge(challenge: string): Promise<string> { const signer = new ethers.Wallet(this.apiKey); return await signer.signMessage(challenge); } async submitChallenge(state: string, signature: string) { const payload = `client_id=${this.clientId}&domain=${encodeURIComponent( this.redirectUri, )}&state=${state}&signature=${signature}&grant_type=authorization_code`; const response = await httpClient.sendRequest<AuthRespone>({ method: HttpMethod.POST, url: 'https://auth.dimo.zone/auth/web3/submit_challenge', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: payload, }); return response.body; } async getDeveloperJwt(): Promise<string> { const challange = await this.generateChallenge(); const sign = await this.signChallenge(challange.challenge); const submit = await this.submitChallenge(challange.state, sign); return submit.access_token; } async createVinVC(input: { vehicleJwt: string; tokenId: number }) { const response = await httpClient.sendRequest<AttestationResponse>({ method: HttpMethod.POST, url: ATTESTATION_API + `/v1/vc/vin/${input.tokenId}`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.vehicleJwt, }, }); return response.body; } async decodeVin(input: { developerJwt: string; countryCode: string; vin: string }) { const response = await httpClient.sendRequest<DeviceDefinitionResponse>({ method: HttpMethod.POST, url: DEVICE_DEFINIATION_API + '/device-definitions/decode-vin', authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, body: { countryCode: input.countryCode, vin: input.vin, }, }); return response.body; } async deviceSearch(input: { developerJwt: string; params: QueryParams }) { const response = await httpClient.sendRequest<DeviceDefinitionsSearchResponse>({ method: HttpMethod.GET, url: DEVICE_DEFINIATION_API + '/device-definitions/search', authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, queryParams: input.params, }); return response.body; } async sendIdentityGraphQLRequest(input: { query: string; variables: Record<string, any> }) { const response = await httpClient.sendRequest({ method: HttpMethod.POST, url: IDENTITY_BASE_URL + '/query', body: JSON.stringify({ query: input.query, variables: input.variables, }), }); return response.body; } async sendTelemetryGraphQLRequest(input: { vehiclejwt: string; query: string; variables: Record<string, any>; }) { const response = await httpClient.sendRequest({ method: HttpMethod.POST, url: TELEMETRY_BASE_URL + '/query', body: JSON.stringify({ query: input.query, variables: input.variables, }), authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.vehiclejwt, }, }); return response.body; } decodePermissions(permissionHex: string): number[] { const cleanHex = permissionHex.toLowerCase().replace('0x', ''); const permissionBits = BigInt('0x' + cleanHex); const grantedPermissions: number[] = []; for (let i = 0; i < 128; i++) { const bitPair = (permissionBits >> BigInt(i * 2)) & BigInt(0b11); if (bitPair === BigInt(0b11)) { grantedPermissions.push(i); } } return grantedPermissions; } async getVehiclePrivileges(input: { tokenId: number }) { const query = `{ vehicle(tokenId: ${input.tokenId}) { sacds(first:100) { nodes { permissions grantee } } } }`; const response = await this.sendIdentityGraphQLRequest({ query, variables: {} }); const nodes = response?.data?.vehicle?.sacds?.nodes; if (!nodes || !Array.isArray(nodes)) { throw new Error('Invalid response format: missing nodes array.'); } const matchingSacd = nodes.find( (sacd: any) => sacd.grantee.toLowerCase() === this.clientId.toLowerCase(), ); if (!matchingSacd) { throw new Error(`No permissions found for developer license: ${this.clientId}.`); } const decodedPermissions = this.decodePermissions(matchingSacd.permissions); return decodedPermissions.join(','); } async getVehicleJwt(input: { developerJwt: string; tokenId: number }): Promise<string> { const privilegesString = await this.getVehiclePrivileges({ tokenId: input.tokenId }); const privileges = privilegesString.split(',').map((p) => parseInt(p.trim(), 10)); const response = await httpClient.sendRequest<TokenExchangeResponse>({ method: HttpMethod.POST, url: TOKEN_EXCHANGE_API + '/v1/tokens/exchange', authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, body: { tokenId: input.tokenId, privileges, nftContractAddress: '0xbA5738a18d83D41847dfFbDC6101d37C69c9B0cF', }, }); return response.body.token; } async createWebhook(input: { developerJwt: string; params: CreateWebhookParams }) { const response = await httpClient.sendRequest<{ id: string }>({ method: HttpMethod.POST, url: VEHICLE_EVENTS_API + '/v1/webhooks', authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, body: { service: input.params.service, data: input.params.data, trigger: vehicleEventTriggerToText(input.params.trigger), setup: input.params.setup, description: input.params.description, target_uri: input.params.target_uri, status: input.params.status, verification_token: input.params.verification_token, }, }); return response.body; } async deleteWebhook(input: { developerJwt: string; webhookId: string }) { const response = await httpClient.sendRequest({ method: HttpMethod.DELETE, url: VEHICLE_EVENTS_API + `/v1/webhooks/${input.webhookId}`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, }); return response.body; } async subscribeVehicle(input: { developerJwt: string; webhookId: string; tokenId: string }) { const response = await httpClient.sendRequest({ method: HttpMethod.POST, url: VEHICLE_EVENTS_API + `/v1/webhooks/${input.webhookId}/subscribe/${input.tokenId}`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, }); return response.body; } async subscribeAllVehicles(input: { developerJwt: string; webhookId: string }) { const response = await httpClient.sendRequest({ method: HttpMethod.POST, url: VEHICLE_EVENTS_API + `/v1/webhooks/${input.webhookId}/subscribe/all`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, }); return response.body; } async unsubscribeAllVehicles(input: { developerJwt: string; webhookId: string }) { const response = await httpClient.sendRequest({ method: HttpMethod.DELETE, url: VEHICLE_EVENTS_API + `/v1/webhooks/${input.webhookId}/unsubscribe/all`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: input.developerJwt, }, }); return response.body; } } export async function sendIdentityGraphQLRequest(query: string, variables: Record<string, any>) { try { const response = await httpClient.sendRequest({ method: HttpMethod.POST, url: IDENTITY_BASE_URL + '/query', body: JSON.stringify({ query: query, variables: variables, }), headers: { 'Content-Type': 'application/json' }, }); return response.body; } catch (err) { const message = (err as HttpError).message; throw new Error(message); } } export function getNumberExpression(comparisonType: Operator, value: number): string { switch (comparisonType) { case Operator.EQUAL: return `valueNumber == ${value}`; case Operator.GREATER_THAN: return `valueNumber > ${value}`; case Operator.LESS_THAN: return `valueNumber < ${value}`; case Operator.GREATER_THAN_OR_EQUAL: return `valueNumber >= ${value}`; case Operator.LESS_THAN_OR_EQUAL: return `valueNumber <= ${value}`; default: throw new Error('Invalid comparison type'); } } export function getBooleanExpression(value: boolean): string { return `valueNumber == ${value ? 1 : 0}`; } export function vehicleEventTriggerToText(trigger: VehicleEventTrigger): string; export function vehicleEventTriggerToText( field: TriggerField, operator: Operator, triggerNumber?: number | null, triggerExpression?: boolean ): string; export function vehicleEventTriggerToText( arg1: VehicleEventTrigger | TriggerField, arg2?: Operator, arg3?: number | null, arg4?: boolean ): string { let triggerField: TriggerField; let triggerOperator: Operator; let triggerValue: number | boolean | null = null; if (typeof arg1 === 'object' && 'field' in arg1 && 'operator' in arg1) { triggerField = arg1.field; triggerOperator = arg1.operator as Operator; triggerValue = arg1.value; } else { triggerField = arg1; triggerOperator = arg2!; triggerValue = arg3 ?? (arg4 ? true : false); } if (typeof triggerValue === 'number') { return getNumberExpression(triggerOperator, triggerValue); } else if (typeof triggerValue === 'boolean') { return getBooleanExpression(triggerValue); } throw new Error('Unknown trigger type'); } export function isNumericField(field: TriggerField): boolean { const numericFields: TriggerField[] = [ TriggerField.Speed, TriggerField.PowertrainTransmissionTravelledDistance, TriggerField.PowertrainFuelSystemRelativeLevel, TriggerField.PowertrainFuelSystemAbsoluteLevel, TriggerField.PowertrainTractionBatteryCurrentPower, TriggerField.PowertrainTractionBatteryStateOfChargeCurrent, TriggerField.ChassisAxleRow1WheelLeftTirePressure, TriggerField.ChassisAxleRow1WheelRightTirePressure, TriggerField.ChassisAxleRow2WheelLeftTirePressure, TriggerField.ChassisAxleRow2WheelRightTirePressure, ]; return numericFields.includes(field); } export function isBooleanField(field: TriggerField): boolean { const booleanFields: TriggerField[] = [ TriggerField.PowertrainTractionBatteryChargingIsCharging, TriggerField.IsIgnitionOn, ]; return booleanFields.includes(field); } export const getTirePressurePositionLabel = (position: TriggerField): string => { switch (position) { case TriggerField.ChassisAxleRow1WheelLeftTirePressure: return 'Front Left'; case TriggerField.ChassisAxleRow1WheelRightTirePressure: return 'Front Right'; case TriggerField.ChassisAxleRow2WheelLeftTirePressure: return 'Rear Left'; case TriggerField.ChassisAxleRow2WheelRightTirePressure: return 'Rear Right'; default: return 'Unknown Position'; } };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server