Skip to content

Project Guidelines

This document is describing the connectors guidelines for the project.

The EnvironmentAdapter class provides a unified interface for environment-specific operations across Google Apps Script and Node.js environments. It abstracts away platform differences and allows your code to work consistently in both environments.

  • Use singe connectors source code in both environments: Node.js and Apps Script
  • Environment detection: Automatically detects the runtime environment
  • Environment-specific code: Use environment-specific code for each environment
  • Unified HTTP requests: Consistent API for making HTTP requests
  • Response wrapping: Unified response interface across platforms
  • Utility functions: Date formatting, UUID generation, encoding, and more
  1. Key Features
  2. Getting Started
  3. Essential EnvironmentAdapter Rules for Data Analysts
  4. API Reference
  5. Usage Examples
  6. HTTP Requests
  7. Utility Methods
  8. Error Handling
  9. Troubleshooting
// The EnvironmentAdapter is available as a global class
// No explicit import needed in Google Apps Script
// In Node.js, ensure it's available through your bundle system

Make sure the following constants and exceptions are available:

// Required constants from CommonConstants.js
var ENVIRONMENT = {
UNKNOWN: 1,
APPS_SCRIPT: 2,
NODE: 3,
};
// Required exception class from Exceptions.js
class UnsupportedEnvironmentException extends AbstractException {}

EnvironmentAdapter is a required class that makes your connector work in both Google Apps Script and Node.js environments. You MUST use it for all HTTP requests and environment-specific operations.

❌ Never use fetch() directly
❌ Never use UrlFetchApp directly
❌ Never use Utilities directly
❌ Never skip error checking on API responses
❌ Never make requests without proper headers
❌ Never ignore rate limits

  1. Always use EnvironmentAdapter.fetch() for any HTTP request
  2. Always check response.getResponseCode() before processing data
  3. Always handle errors with try-catch blocks
  4. Always respect API rate limits with sleep() between requests
  5. Always include proper headers especially for authentication
  6. Always use EnvironmentAdapter utilities for date formatting, UUID generation, encoding, and more
// ✅ CORRECT - Use this for all API calls
const response = EnvironmentAdapter.fetch("https://api.example.com/data");
// ❌ WRONG - Never use direct fetch()
const response = fetch("https://api.example.com/data");
// ❌ WRONG - Never use direct UrlFetchApp.fetch()
const response = UrlFetchApp.fetch("https://api.example.com/data");
const response = EnvironmentAdapter.fetch(url, options);
// Check if request succeeded
if (response.getResponseCode() === 200) {
const data = response.getAsJson(); // For JSON APIs
// Process your data here
} else {
// Handle error
console.error("API request failed:", response.getResponseCode());
}
const options = {
method: "GET", // or "POST", "PUT", etc.
headers: {
"Authorization": "Bearer your-token",
"Content-Type": "application/json"
}
};
// For POST requests with data
const postOptions = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
payload: JSON.stringify({
// your data here
})
};
// Generate unique IDs
const id = EnvironmentAdapter.getUuid();
// Wait between API calls (to avoid rate limits)
EnvironmentAdapter.sleep(1000); // Wait 1 second
// Encode data
const encoded = EnvironmentAdapter.base64Encode("your-data");

Returns the current runtime environment.

static getEnvironment(): ENVIRONMENT

Returns: One of ENVIRONMENT.APPS_SCRIPT, ENVIRONMENT.NODE, or ENVIRONMENT.UNKNOWN

Makes HTTP requests with unified response interface.

static fetch(url: string, options: Object = {}): FetchResponse

Parameters:

  • url (string): The URL to fetch data from
  • options (Object): Request options (method, headers, payload, etc.)

Returns: FetchResponse object with unified methods

Throws: UnsupportedEnvironmentException if environment is not supported

Pauses execution for specified milliseconds.

static sleep(ms: number): void

Parameters:

  • ms (number): Milliseconds to sleep

Throws: UnsupportedEnvironmentException if environment is not supported

Formats a date according to environment capabilities.

static formatDate(date: Date, timezone: string, format: string): string

Parameters:

  • date (Date): Date to format
  • timezone (string): Timezone for formatting
  • format (string): Format pattern

Returns: Formatted date string

Note: In Node.js, only returns ISO date format regardless of parameters

Generates a UUID string.

static getUuid(): string

Returns: UUID in format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Encodes data to base64.

static base64Encode(data: string): string

Parameters:

  • data (string): Data to encode

Returns: Base64 encoded string

Computes HMAC signature.

static computeHmacSignature(algorithm: string, data: string, key: string): string

Parameters:

  • algorithm (string): HMAC algorithm (e.g., ‘sha256’, ‘sha1’)
  • data (string): Data to sign
  • key (string): Secret key

Returns: HMAC signature as hex string

Parses CSV string into array of arrays.

static parseCsv(csvString: string): Array<Array<string>>

Parameters:

  • csvString (string): The CSV string to parse

Returns: Array of arrays containing parsed CSV data

Note: Each inner array represents a row, with cells as string values

Unzips blob/buffer data.

static unzip(data: Blob|Buffer): Array<{getDataAsString: Function}>

Parameters:

  • data (Blob|Buffer): The data to unzip

Returns: Array of file-like objects with getDataAsString method

Note: In Node.js environment requires adm-zip package

try {
const response = EnvironmentAdapter.fetch("https://api.example.com/data");
// Check response status
if (response.getResponseCode() === 200) {
// Parse JSON response
const data = response.getAsJson();
console.log("Data received:", data);
// Or get raw text
const text = response.getContentText();
console.log("Response text:", text);
} else {
console.error("Request failed with status:", response.getResponseCode());
}
} catch (error) {
console.error("Error making request:", error.message);
}
const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your-token-here"
},
payload: JSON.stringify({
name: "John Doe",
email: "john@example.com"
})
};
try {
const response = EnvironmentAdapter.fetch("https://api.example.com/users", requestOptions);
const result = response.getAsJson();
console.log("User created:", result);
} catch (error) {
console.error("Error creating user:", error.message);
}
const options = {
method: "GET",
headers: {
"User-Agent": "MyApp/1.0",
"Accept": "application/json"
}
};
const response = EnvironmentAdapter.fetch("https://api.example.com/profile", options);
const headers = response.getHeaders();
console.log("Response headers:", headers);
const now = new Date();
// Format date (behavior differs by environment)
const formatted = EnvironmentAdapter.formatDate(now, "America/New_York", "yyyy-MM-dd");
console.log("Formatted date:", formatted);
const uniqueId = EnvironmentAdapter.getUuid();
console.log("Generated UUID:", uniqueId);
// Output: e.g., "f47ac10b-58cc-4372-a567-0e02b2c3d479"
const originalData = "Hello, World!";
const encoded = EnvironmentAdapter.base64Encode(originalData);
console.log("Encoded:", encoded);
// Output: "SGVsbG8sIFdvcmxkIQ=="
const message = "important data";
const secretKey = "my-secret-key";
const signature = EnvironmentAdapter.computeHmacSignature("sha256", message, secretKey);
console.log("HMAC signature:", signature);
console.log("Starting operation...");
EnvironmentAdapter.sleep(2000); // Wait 2 seconds
console.log("Operation completed after delay");
const csvString = "name,age,city\nJohn,30,New York\nJane,25,London";
const parsedData = EnvironmentAdapter.parseCsv(csvString);
console.log("Parsed CSV:", parsedData);
// Output: [
// ["name", "age", "city"],
// ["John", "30", "New York"],
// ["Jane", "25", "London"]
// ]
// Assuming you have zip data from an API response or file
const response = EnvironmentAdapter.fetch("https://example.com/data.zip");
const zipData = response.getContent();
const unzippedFiles = EnvironmentAdapter.unzip(zipData);
// Read contents of each file
unzippedFiles.forEach(file => {
const content = file.getDataAsString();
console.log("File content:", content);
});

All HTTP requests return a FetchResponse object with these methods:

interface FetchResponse {
getHeaders(): Object; // Get response headers
getAsJson(): any; // Parse response as JSON
getContent(): string|Buffer; // Get raw content
getContentText(): string; // Get content as text
getResponseCode(): number; // Get HTTP status code
}

The options parameter for fetch() supports:

const options = {
method: "GET|POST|PUT|DELETE|PATCH",
headers: {
"Header-Name": "Header-Value"
},
payload: "request body data",
// Google Apps Script specific options
muteHttpExceptions: true,
followRedirects: true,
validateHttpsCertificates: true
};
function safeApiCall(url, options) {
try {
const response = EnvironmentAdapter.fetch(url, options);
const statusCode = response.getResponseCode();
if (statusCode >= 400) {
const errorText = response.getContentText();
throw new Error(`HTTP ${statusCode}: ${errorText}`);
}
return response.getAsJson();
} catch (error) {
if (error.message.includes("Invalid JSON")) {
console.error("Response is not valid JSON");
return null;
}
throw error;
}
}

⚠️ Important: Date formatting behavior differs between environments:

  • Google Apps Script: Full formatting support with timezone and format patterns
  • Node.js: Always returns ISO date format (YYYY-MM-DD) regardless of parameters
// Cross-platform date handling
function getFormattedDate(date) {
if (EnvironmentAdapter.getEnvironment() === ENVIRONMENT.APPS_SCRIPT) {
return EnvironmentAdapter.formatDate(date, "UTC", "yyyy-MM-dd");
} else {
// Node.js: implement custom formatting if needed
return date.toISOString().split('T')[0];
}
}

UUIDs are generated using:

  • Google Apps Script: Platform-specific UUID generation
  • Node.js: crypto.randomUUID()

Both platforms support:

  • Base64 encoding
  • HMAC signature computation with various algorithms (sha1, sha256, etc.)
try {
const response = EnvironmentAdapter.fetch(url);
} catch (error) {
if (error instanceof UnsupportedEnvironmentException) {
console.error("Environment not supported:", error.message);
} else {
console.error("Other error:", error.message);
}
}
function robustApiCall(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = EnvironmentAdapter.fetch(url);
if (response.getResponseCode() === 200) {
return response.getAsJson();
}
throw new Error(`HTTP ${response.getResponseCode()}`);
} catch (error) {
console.warn(`Attempt ${i + 1} failed:`, error.message);
if (i === retries - 1) throw error;
// Wait before retry
EnvironmentAdapter.sleep(1000 * Math.pow(2, i)); // Exponential backoff
}
}
}

Problem: Getting UnsupportedEnvironmentException error.

Solution:

  • Ensure you’re running in Google Apps Script or Node.js
  • Check that required dependencies are installed in Node.js
  • Verify ENVIRONMENT constants are properly defined

Problem: response.getAsJson() throws “Invalid JSON response” error.

Solution:

const response = EnvironmentAdapter.fetch(url);
const text = response.getContentText();
console.log("Raw response:", text); // Debug the actual response
try {
const json = response.getAsJson();
} catch (error) {
console.error("Not a JSON response, got:", text);
}

Problem: Requests hanging or timing out.

Solution:

// For Google Apps Script, add timeout options
const options = {
method: "GET",
muteHttpExceptions: true,
// Add other GAS-specific options as needed
};

Problem: request or deasync not found.

Solution:

Terminal window
npm install sync-request deasync

Enable detailed logging for troubleshooting:

function debugFetch(url, options = {}) {
console.log("Environment:", EnvironmentAdapter.getEnvironment());
console.log("Request URL:", url);
console.log("Request options:", options);
try {
const response = EnvironmentAdapter.fetch(url, options);
console.log("Response code:", response.getResponseCode());
console.log("Response headers:", response.getHeaders());
return response;
} catch (error) {
console.error("Request failed:", error);
throw error;
}
}