@dfinity/agent
Version:
JavaScript and TypeScript library to interact with the Internet Computer
111 lines • 4.79 kB
JavaScript
/**
* @experimental
* @module canister-env/api
*/
import { hexToBytes } from '@noble/hashes/utils';
import { InputError, InvalidRootKeyErrorCode, EmptyCookieErrorCode, MissingRootKeyErrorCode, MissingCookieErrorCode, } from "../errors.js";
const IC_ENV_COOKIE_NAME = 'ic_env';
const ENV_VAR_SEPARATOR = '&';
const ENV_VAR_ASSIGNMENT_SYMBOL = '=';
const IC_ROOT_KEY_VALUE_NAME = 'ic_root_key';
const IC_ROOT_KEY_ENV_NAME = 'IC_ROOT_KEY'; // the value must be the same as the `CanisterEnv.IC_ROOT_KEY` property name
const IC_ROOT_KEY_BYTES_LENGTH = 133;
/**
* Get the environment variables served by the asset canister via the cookie.
*
* The returned object always includes `IC_ROOT_KEY` property.
* You can extend the global `CanisterEnv` interface to add your own environment variables
* and have strong typing for them.
*
* In Node.js environment (or any other environment where `globalThis.document` is not available), this function will throw an error.
* Use {@link safeGetCanisterEnv} if you need a function that returns `undefined` instead of throwing errors.
* @param options The options for loading the canister environment variables
* @returns The environment variables for the asset canister, always including `IC_ROOT_KEY`
* @throws {TypeError} When `globalThis.document` is not available
* @throws {InputError} When the cookie is not found
* @throws {InputError} When the `IC_ROOT_KEY` is missing or has an invalid length
* @see The {@link https://js.icp.build/core/latest/canister-environment/ | Canister Environment Guide} for more details on how to use the canister environment in a frontend application
* @experimental
* @example
* ```ts
* type MyCanisterEnv = {
* readonly ['PUBLIC_CANISTER_ID:backend']: string;
* };
*
* const env = getCanisterEnv<MyCanisterEnv>();
*
* console.log(env.IC_ROOT_KEY); // always available (Uint8Array)
* console.log(env['PUBLIC_CANISTER_ID:backend']); // ✅ from generic parameter, TS passes
* console.log(env['PUBLIC_CANISTER_ID:frontend']); // ❌ TS will show an error
* ```
* Have a look at {@link CanisterEnv} for more details on how to extend the interface
*/
export function getCanisterEnv(options = {}) {
const { cookieName = IC_ENV_COOKIE_NAME } = options;
const cookie = getCookie(cookieName);
if (!cookie) {
throw InputError.fromCode(new MissingCookieErrorCode(cookieName));
}
const cookieValue = cookie.split('=')[1].trim();
if (!cookieValue) {
throw InputError.fromCode(new EmptyCookieErrorCode(cookieName));
}
const decodedCookieValue = decodeURIComponent(cookieValue);
const envVars = parseEnvVars(decodedCookieValue);
if (!envVars.IC_ROOT_KEY) {
throw InputError.fromCode(new MissingRootKeyErrorCode());
}
return envVars;
}
/**
* Safe version of {@link getCanisterEnv} that returns `undefined` instead of throwing errors.
* @param options The options for loading the asset canister environment variables
* @returns The environment variables for the asset canister, or `undefined` if any error occurs
* @see The {@link https://js.icp.build/core/latest/canister-environment/ | Canister Environment Guide} for more details on how to use the canister environment in a frontend application
* @experimental
* @example
* ```ts
* // in a browser environment with valid cookie
* const env = safeGetCanisterEnv();
* console.log(env); // { IC_ROOT_KEY: Uint8Array, ... }
*
* // in a Node.js environment
* const env = safeGetCanisterEnv();
* console.log(env); // undefined
*
* // in a browser without the environment cookie
* const env = safeGetCanisterEnv();
* console.log(env); // undefined
* ```
*/
export function safeGetCanisterEnv(options = {}) {
try {
return getCanisterEnv(options);
}
catch {
return undefined;
}
}
function getCookie(cookieName) {
return globalThis.document.cookie
.split(';')
.find(cookie => cookie.trim().startsWith(`${cookieName}=`));
}
function parseEnvVars(decoded) {
const entries = decoded.split(ENV_VAR_SEPARATOR).map(v => {
// we only want to split at the first occurrence of the assignment symbol
const symbolIndex = v.indexOf(ENV_VAR_ASSIGNMENT_SYMBOL);
const key = v.slice(0, symbolIndex);
const value = v.substring(symbolIndex + 1);
if (key === IC_ROOT_KEY_VALUE_NAME) {
const rootKey = hexToBytes(value);
if (rootKey.length !== IC_ROOT_KEY_BYTES_LENGTH) {
throw InputError.fromCode(new InvalidRootKeyErrorCode(rootKey, IC_ROOT_KEY_BYTES_LENGTH));
}
return [IC_ROOT_KEY_ENV_NAME, rootKey];
}
return [key, value];
});
return Object.fromEntries(entries);
}
//# sourceMappingURL=index.js.map