### Start Fastly Compute Example Source: https://github.com/fanout/js-grip/blob/main/examples/fastly-compute/README.md This command starts the Fastly Compute example application using the Fastly CLI. It assumes Pushpin is running and configured. ```bash npm run start ``` -------------------------------- ### Install Node.js Dependencies Source: https://github.com/fanout/js-grip/blob/main/examples/remix/README.md Installs the necessary Node.js dependencies for the example application. This command should be run in the example's directory. ```bash npm install ``` -------------------------------- ### Install Dependencies Source: https://github.com/fanout/js-grip/blob/main/examples/fastly-compute/README.md Use this command to install the necessary Node.js dependencies for a Fastly Compute example. Ensure you have Node.js 16.0 or newer installed. ```bash npm install ``` -------------------------------- ### Install @fanoutio/grip Source: https://context7.com/fanout/js-grip/llms.txt Install the library using npm. ```bash npm install @fanoutio/grip ``` -------------------------------- ### Create Fastly Backend Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Adds the example application to your Fastly service as a backend named 'origin'. Replace `` with your actual hostname. ```bash fastly backend create --autoclone --version=active --name=origin --address= ``` -------------------------------- ### Start Remix Development Server Source: https://github.com/fanout/js-grip/blob/main/examples/remix/README.md Starts the local development server for the Remix application using '@remix-run/serve'. ```bash npm run start ``` -------------------------------- ### Fastly Fanout GRIP Configuration Example Source: https://github.com/fanout/js-grip/blob/main/README.md Example GRIP_URL and GRIP_VERIFY_KEY for use with Fastly Fanout. Replace placeholders with your actual service ID and API token. ```shell GRIP_URL='https://api.fastly.com/service/?key=&verify-iss=fastly:' GRIP_VERIFY_KEY='{"kty":"EC","crv":"P-256","x":"CKo5A1ebyFcnmVV8SE5On-8G81JyBjSvcrx4VLetWCg","y":"7gwJqaU6N8TP88--twjkwoB36f-pT3QsmI46nPhjO7M"}' ``` -------------------------------- ### Start Pushpin Server Source: https://github.com/fanout/js-grip/blob/main/examples/remix/README.md Starts the Pushpin server. By default, it listens on port 7999 for GRIP traffic and port 5561 for publishing. ```bash pushpin ``` -------------------------------- ### Start Node.js Server Source: https://github.com/fanout/js-grip/blob/main/examples/nodejs/README.md Starts the Node.js server and logs a confirmation message. Ensure the server object is properly initialized before calling listen. ```javascript server.listen(3000, '0.0.0.0'); console.log('Server running...'); ``` -------------------------------- ### Start Bun HTTP Server Source: https://github.com/fanout/js-grip/blob/main/examples/bun/README.md Sets up a Bun HTTP server with a request handler. The server listens on port 3000 by default. ```typescript const server = Bun.serve({ port: 3000, async fetch(request: Request) { const requestUrl = new URL(request.url); // handler code ... }, }); console.log(`Listening on http://127.0.0.1:${server.port} ...`); ``` -------------------------------- ### Start Deno Server Source: https://github.com/fanout/js-grip/blob/main/examples/deno/README.md Starts a Deno server listening on port 3000 with a specified request handler. ```typescript Deno.serve({ port: 3000 }, handler); ``` -------------------------------- ### GRIP Configuration with Pushpin Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Configures the GRIP client to connect to a local Pushpin instance. This is the default configuration when running examples locally. ```typescript let gripConfig: string | IGripConfig = 'http://127.0.0.1:5561/'; ``` -------------------------------- ### Build Fastly Fanout GRIP Configuration Source: https://context7.com/fanout/js-grip/llms.txt The `buildFanoutGripConfig` helper simplifies creating an `IGripConfig` for Fastly Fanout. It automatically embeds the necessary JWK for `Grip-Sig` verification. This example shows minimal usage and an example with a custom fetch backend for Fastly Compute. ```typescript import { Publisher } from '@fanoutio/grip'; import { buildFanoutGripConfig } from '@fanoutio/grip/fastly-fanout'; // Minimal usage — uses built-in Fastly Fanout public key const gripConfig = buildFanoutGripConfig({ serviceId: process.env.FANOUT_SERVICE_ID!, // e.g. 'aBcDeFgHiJkLmNop' apiToken: process.env.FANOUT_API_TOKEN!, // Fastly API token with 'global' scope }); const publisher = new Publisher(gripConfig); // Or on Fastly Compute where a custom fetch backend is required const computePublisher = new Publisher( buildFanoutGripConfig({ serviceId: 'aBcDeFgHiJkLmNop', apiToken: 'fastly-token-here', }), { fetch(input, init) { return fetch(input, { ...init, backend: 'fanout-publisher' }); }, } ); await computePublisher.publishHttpStream('live-scores', 'Goal! 2-1\n'); ``` -------------------------------- ### Initialize Publisher with TypeScript Types Source: https://github.com/fanout/js-grip/blob/main/README.md Import and initialize the Publisher class from the @fanoutio/grip package. This example demonstrates usage with TypeScript, leveraging its type definitions. ```javascript import { Publisher, IGripConfig } from '@fanoutio/grip'; const pub = new Publisher({control_uri: ""}); // IGripConfig is a type declaration. ``` -------------------------------- ### Initialize Fastly Compute Application Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Initializes a new Fastly Compute application from a starter kit template. ```bash fastly compute init --from=https://github.com/fastly/compute-starter-kit-javascript-fanout-forward ``` -------------------------------- ### Publisher Class Initialization with IGripConfig Source: https://context7.com/fanout/js-grip/llms.txt Demonstrates how to initialize the Publisher class with an IGripConfig object, which can include details for connecting to and authenticating with a GRIP proxy. It also shows how to configure multiple proxies. ```APIDOC ## Publisher Class Initialization with IGripConfig ### Description Initializes the `Publisher` class with GRIP proxy configuration. The `IGripConfig` interface specifies connection and authentication details. Supports configuration for single or multiple GRIP proxies. ### Usage ```typescript import { Publisher } from '@fanoutio/grip'; import type { IGripConfig } from '@fanoutio/grip'; // Direct object configuration for a single proxy const config: IGripConfig = { control_uri: 'http://pushpin.myproject.com/', control_iss: 'my-service', // JWT `iss` claim for publishing auth key: 'secret-signing-key', // Shared secret for JWT signing verify_iss: 'pushpin', // Expected `iss` in incoming Grip-Sig verify_key: 'base64:dGVzdA==', // Key used to verify Grip-Sig JWT }; const publisher = new Publisher(config); // Multiple proxies — publish to all in parallel const multiPublisher = new Publisher([ { control_uri: 'http://proxy1.internal:5561/' }, { control_uri: 'http://proxy2.internal:5561/' }, ]); ``` ### Parameters * **config** (IGripConfig | IGripConfig[]) - Configuration object or an array of configuration objects for the GRIP proxy(ies). ### `IGripConfig` Interface Fields: * **control_uri** (string) - Required - The URI of the GRIP proxy's control endpoint. * **control_iss** (string) - Optional - The JWT `iss` claim for publishing authentication. * **key** (string) - Optional - The shared secret for JWT signing. * **verify_iss** (string) - Optional - The expected `iss` claim in incoming `Grip-Sig` JWTs. * **verify_key** (string) - Optional - The key used to verify incoming `Grip-Sig` JWTs. ``` -------------------------------- ### Instantiate the Publisher object Source: https://github.com/fanout/js-grip/blob/main/README.md Demonstrates various ways to instantiate the Publisher object using GRIP configuration, including direct GRIP_URL, parsed configuration, or a control URI. ```APIDOC ## Instantiate the `Publisher` object Instantiate the `Publisher` object by passing the GRIP configuration object to its constructor. ```javascript import { Publisher } from '@fanoutio/grip'; const publisher = new Publisher({ control_uri: 'http://pushpin.myproject.com/', // Control URI of your Pushpin instance }); // or const gripURL = process.env.GRIP_URL || 'http://127.0.0.1:5561/'; const gripVerifyKey = process.env.GRIP_VERIFY_KEY; const gripConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey }); const publisher = new Publisher(gripConfig); // You can pass a gripConfig if you've already parsed it // or const publisher = new Publisher(process.env.GRIP_URL); // You can even pass a GRIP_URL directly ``` > NOTE: If your origin application is running on Fastly Compute, then you'll need to further configure the > `Publisher`. See [Overriding fetch](#overriding-fetch) below. ``` -------------------------------- ### Instantiate Publisher with Parsed GRIP Configuration Source: https://github.com/fanout/js-grip/blob/main/README.md Instantiate the Publisher object using a pre-parsed GRIP configuration object, which may include a verify key. ```javascript const gripURL = process.env.GRIP_URL || 'http://127.0.0.1:5561/'; const gripVerifyKey = process.env.GRIP_VERIFY_KEY; const gripConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey }); const publisher = new Publisher(gripConfig); // You can pass a gripConfig if you've already parsed it ``` -------------------------------- ### Get WebSocket Context from Node.js Request Source: https://github.com/fanout/js-grip/blob/main/examples/nodejs/websocket/README.md Obtain the WebSocket context for proxied WebSocket-over-HTTP requests in Node.js. Requires the '@fanoutio/grip/node' module. ```javascript let wsContext = null; if (gripStatus.isProxied && isNodeReqWsOverHttp(req)) { wsContext = await getWebSocketContextFromNodeReq(req); } ``` -------------------------------- ### Set up HTTP streaming subscription Source: https://github.com/fanout/js-grip/blob/main/README.md Configure a GripInstruct object for HTTP streaming subscriptions. This is used for continuous data streams. ```javascript const gripInstruct = new GripInstruct(); gripInstruct.addChannel(''); gripInstruct.setHoldStream(); return new Response( 'Body', { status: 200, headers: { ...gripInstruct.toHeaders(), 'Content-Type': 'text/plain', } } ); ``` -------------------------------- ### Instantiate GRIP Publisher with Custom Fetch Source: https://github.com/fanout/js-grip/blob/main/examples/fastly-compute/README.md Initializes the Publisher class with the GRIP configuration and a custom fetch function. The custom fetch specifies the 'publisher' backend for outgoing requests in Fastly Compute. ```javascript const publisher = new Publisher(gripConfig, { fetch(input, init) { return fetch(String(input), { ...init, backend: 'publisher' }); }, }); ``` -------------------------------- ### Get WebSocket Context Source: https://github.com/fanout/js-grip/blob/main/examples/bun/websocket/README.md Obtain the WebSocket context from an incoming HTTP request if it's proxied and a WebSocket connection. This is necessary for handling WebSocket-over-HTTP. ```typescript let wsContext = null; if (gripStatus.isProxied && isWsOverHttp(req)) { wsContext = await getWebSocketContextFromReq(req); } ``` -------------------------------- ### Configure Publisher with GRIP_URL Environment Variable Source: https://github.com/fanout/js-grip/blob/main/README.md Configure a Publisher instance by providing the GRIP_URL environment variable. This is a convenient way to set up the publisher with pre-defined connection details. ```javascript const publisher = new Publisher(process.env.GRIP_URL); // A GRIP_URL representing your GRIP proxy ``` -------------------------------- ### Handle WebSocket-over-HTTP with Node.js IncomingMessage Source: https://context7.com/fanout/js-grip/llms.txt Utilizes Node.js-specific functions `isNodeReqWsOverHttp` and `getWebSocketContextFromNodeReq` to handle WebSocket-over-HTTP requests with `IncomingMessage` objects. This example shows setting up an HTTP server to process these requests. ```javascript import http from 'node:http'; import { Publisher, isNodeReqWsOverHttp, getWebSocketContextFromNodeReq, encodeWebSocketEvents, WebSocketMessageFormat, } from '@fanoutio/grip'; // the "node" export condition is automatically applied by Node.js const publisher = new Publisher(process.env.GRIP_URL ?? 'http://127.0.0.1:5561/'); const server = http.createServer(async (req, res) => { const gripStatus = await publisher.validateGripSig(req.headers['grip-sig']); if (gripStatus.isProxied && isNodeReqWsOverHttp(req)) { const wsContext = await getWebSocketContextFromNodeReq(req); if (wsContext.isOpening()) { wsContext.accept(); wsContext.subscribe('chat'); } while (wsContext.canRecv()) { const msg = wsContext.recv(); if (msg === null) { wsContext.close(); break; } wsContext.send(`echo: ${msg}`); } const body = encodeWebSocketEvents(wsContext.getOutgoingEvents()); res.writeHead(200, wsContext.toHeaders()); res.end(body); return; } res.writeHead(404); res.end('Not found'); }); server.listen(3000); ``` -------------------------------- ### Create Streaming Instruct Source: https://context7.com/fanout/js-grip/llms.txt Construct GRIP headers for HTTP streaming. Subscribe to multiple channels and set metadata for the proxy. Ensure the response headers are correctly applied. ```typescript // --- HTTP Streaming --- // Proxy holds the connection open and forwards each published chunk const streamInstruct = new GripInstruct(['news', 'alerts']); // subscribe to multiple channels streamInstruct.setHoldStream(); streamInstruct.meta = { userId: 'user-123' }; // stored as Grip-Set-Meta const streamResponse = new Response('[stream open]\n', { status: 200, headers: { ...streamInstruct.toHeaders(), 'Content-Type': 'text/plain', }, }); ``` -------------------------------- ### Initialize Publisher with GRIP_URL Source: https://context7.com/fanout/js-grip/llms.txt Instantiate the Publisher class using a GRIP_URL environment variable. Ensure the GRIP_URL is correctly set. ```typescript import { Publisher } from '@fanoutio/grip'; // From a GRIP_URL environment variable const publisher = new Publisher(process.env.GRIP_URL ?? 'http://127.0.0.1:5561/'); ``` -------------------------------- ### Set Up GRIP Streaming Instruction Source: https://github.com/fanout/js-grip/blob/main/examples/deno/http-stream/README.md Configure a GRIP instruction to hold the current connection open as a streaming connection, listening to a specified channel. ```typescript const gripInstruct = new GripInstruct('test'); gripInstruct.setHoldStream(); ``` -------------------------------- ### Create HTTP Server and Request Handler Source: https://github.com/fanout/js-grip/blob/main/examples/nodejs/README.md Sets up an HTTP server and defines the request handler function. This handler processes incoming requests, including those proxied via GRIP. ```javascript const server = http.createServer(async (req, res) => { const requestUrl = new URL(req.url, (req.socket.encrypted ? 'https://' : 'http://') + req.headers['host']); // handler code ... }); ``` -------------------------------- ### Instantiate Publisher with channel prefix Source: https://github.com/fanout/js-grip/blob/main/README.md Configure the Publisher with a prefix to namespace channel names. Messages published to a channel will be sent to the prefixed channel name. ```javascript const publisher = new Publisher(process.env.GRIP_URL, { prefix: 'foo_' }); await publisher.publishHttpStream('test', 'Test Publish!'); // Message is sent to channel named 'foo_test' ``` -------------------------------- ### Publisher Constructor Source: https://github.com/fanout/js-grip/blob/main/README.md Creates a Publisher instance, configuring it based on the specified GRIP settings. Accepts either a single IGripConfig object or an array of IGripConfig objects. ```APIDOC ## Publisher Constructor ### Description Create a `Publisher` instance, configuring it based on the specified GRIP settings. ### Parameters - `configs` (IGripConfig | IGripConfig[]) - Configuration for the GRIP proxy. - `options` (object) - Optional configuration options. ``` -------------------------------- ### Subscribing to Channels Source: https://github.com/fanout/js-grip/blob/main/README.md Details how to use GRIP instructions to subscribe connections to channels, involving the `GripInstruct` object and response headers. ```APIDOC ## Subscribing Once you've verified that your request is proxied behind GRIP, your origin application can, as part of its execution, decide to have the GRIP proxy hold the connection and subscribe it to channels. With an HTTP transport such as long-polling and streaming, your origin application includes HTTP headers known as GRIP instructions along with the response. These instructions indicate the action that the GRIP proxy is to take, and is abstracted as a `GripInstruct` object. To set GRIP instructions, instantiate `GripInstruct` and call its functions. When it comes time to return the response, include the GRIP instructions with the response by calling `toHeaders()` on them and including them with the response headers. ``` -------------------------------- ### Instantiate Publisher with GRIP URL Directly Source: https://github.com/fanout/js-grip/blob/main/README.md Instantiate the Publisher object by passing the GRIP URL directly to the constructor. The library will handle parsing. ```javascript const publisher = new Publisher(process.env.GRIP_URL); // You can even pass a GRIP_URL directly ``` -------------------------------- ### Initialize Publisher with Custom Options Source: https://context7.com/fanout/js-grip/llms.txt Configure the Publisher with a channel prefix and a custom fetch function, which is necessary for environments like Fastly Compute. ```typescript import { Publisher } from '@fanoutio/grip'; // With a channel prefix and a custom fetch (required on Fastly Compute) const publisherWithPrefix = new Publisher( { control_uri: 'http://pushpin.local:5561/', key: 'my-secret', control_iss: 'myapp' }, { prefix: 'app_', // all channels prefixed → 'app_news', 'app_chat', … fetch(input, init) { // override fetch for Fastly Compute backend routing return fetch(input, { ...init, backend: 'grip-publisher' }); }, } ); ``` -------------------------------- ### Configure Publisher with Merged GRIP URI and Verify Key Source: https://github.com/fanout/js-grip/blob/main/README.md Configure a Publisher instance by parsing a GRIP URL and merging additional parameters like a verification key. This allows for dynamic configuration based on environment variables. ```javascript const gripURL = process.env.GRIP_URL || 'http://127.0.0.1:5561/'; const gripVerifyKey = process.env.GRIP_VERIFY_KEY; const gripConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey }); // Merge a key into the GRIP_URL const publisher = new Publisher(gripConfig); ``` -------------------------------- ### GripInstruct Class Source: https://context7.com/fanout/js-grip/llms.txt Builds the set of `Grip-*` HTTP response headers to instruct the GRIP proxy on how to handle a client connection, supporting long-polling, streaming, and other features. ```APIDOC ## `GripInstruct` — Instruct the GRIP Proxy to Hold Connections Builds the set of `Grip-*` HTTP response headers that tell the proxy how to handle a client connection. Supports long-polling (`setHoldLongPoll`), streaming (`setHoldStream`), keep-alive pings, next-link chaining, and metadata. ```typescript import { GripInstruct } from '@fanoutio/grip'; // --- HTTP Long-Poll --- // Proxy holds the connection and releases it when a message is published to 'updates' const longPollInstruct = new GripInstruct('updates'); longPollInstruct.setHoldLongPoll(30); // 30-second timeout longPollInstruct.setKeepAlive(' ', 20); // keep-alive space character every 20 s // Use with a 304 workaround (some platforms drop headers on 304) longPollInstruct.setStatus(304); // embedded status in Grip-Status header const longPollResponse = new Response('', { status: 200, headers: { ...longPollInstruct.toHeaders(), 'Content-Type': 'text/plain', }, }); // --- HTTP Streaming --- // Proxy holds the connection open and forwards each published chunk const streamInstruct = new GripInstruct(['news', 'alerts']); // subscribe to multiple channels streamInstruct.setHoldStream(); streamInstruct.meta = { userId: 'user-123' }; // stored as Grip-Set-Meta const streamResponse = new Response('[stream open]\n', { status: 200, headers: { ...streamInstruct.toHeaders(), 'Content-Type': 'text/plain', }, }); ``` ``` -------------------------------- ### Respond with GRIP Streaming Headers Source: https://github.com/fanout/js-grip/blob/main/examples/nodejs/http-stream/README.md Write the HTTP response, including the GRIP instruction headers, to establish the streaming connection. ```javascript res.writeHead(200, { ...gripInstruct.toHeaders(), 'Content-Type': 'text/plain', }); res.end('[stream open]\n'); ``` -------------------------------- ### Configure Publisher with Control URI Source: https://github.com/fanout/js-grip/blob/main/README.md Configure a Publisher instance using a direct control URI for your Pushpin instance. Ensure the URI points to your Pushpin control endpoint. ```javascript import { Publisher } from '@fanoutio/grip'; const publisher = new Publisher({ control_uri: 'http://pushpin.myproject.com/', // Control URI of your Pushpin instance }); ``` -------------------------------- ### Connect to WebSocket via GRIP Proxy Source: https://github.com/fanout/js-grip/blob/main/examples/bun/websocket/README.md Use `wscat` to establish a WebSocket connection to the GRIP proxy. This connects to the internal 'test' channel. ```bash wscat -c http://127.0.0.1:7999/api/websocket ``` -------------------------------- ### Instantiate GRIP Publisher Source: https://github.com/fanout/js-grip/blob/main/examples/bun/README.md Initializes the GRIP Publisher with the determined configuration. This instance is reused across requests. ```typescript const publisher = new Publisher(gripConfig); ``` -------------------------------- ### Configure GRIP Publisher with Environment Variable Source: https://github.com/fanout/js-grip/blob/main/examples/nodejs/README.md Configures the GRIP Publisher using a GRIP URL and verify key from environment variables. This allows for dynamic configuration based on the deployment environment. ```javascript const gripUrl = process.env.GRIP_URL; if (gripUrl) { gripConfig = parseGripUri(gripUrl, { 'verify-key': process.env.GRIP_VERIFY_KEY }); } ``` -------------------------------- ### Configure GRIP using Fanout Credentials Source: https://github.com/fanout/js-grip/blob/main/examples/fastly-compute/README.md Builds GRIP configuration using Fanout service ID and API token from the Secret Store. This is an alternative to direct GRIP URL configuration. ```javascript const fanoutServiceId = (await secretStore.get('FANOUT_SERVICE_ID'))?.plaintext(); const fanoutApiToken = (await secretStore.get('FANOUT_API_TOKEN'))?.plaintext(); if (fanoutServiceId != null && fanoutApiToken != null) { gripConfig = buildFanoutGripConfig({ serviceId: fanoutServiceId, apiToken: fanoutApiToken, }); } ``` -------------------------------- ### Configure GRIP Publisher with Pushpin URL Source: https://github.com/fanout/js-grip/blob/main/examples/bun/README.md Sets the GRIP configuration to point to a local Pushpin instance. This is the default configuration. ```typescript let gripConfig: string | IGripConfig = 'http://127.0.0.1:5561/'; ``` -------------------------------- ### GRIP Configuration with Environment Variables Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Configures the GRIP client using environment variables for the GRIP URL and verification key. This allows for dynamic configuration, especially when deploying to different environments. ```typescript let gripConfig: string | IGripConfig = 'http://127.0.0.1:5561/'; const gripUrl = env.GRIP_URL; if (gripUrl) { gripConfig = parseGripUri(gripUrl, { 'verify-key': env.GRIP_VERIFY_KEY }); } ``` -------------------------------- ### Configure GRIP Publisher with Fastly Fanout Credentials Source: https://github.com/fanout/js-grip/blob/main/examples/bun/README.md Builds the GRIP configuration using Fastly Fanout credentials from environment variables FANOUT_SERVICE_ID and FANOUT_API_TOKEN. ```typescript const fanoutServiceId = Bun.env.FANOUT_SERVICE_ID; const fanoutApiToken = Bun.env.FANOUT_API_TOKEN; if (fanoutServiceId != null && fanoutApiToken != null) { gripConfig = buildFanoutGripConfig({ serviceId: fanoutServiceId, apiToken: fanoutApiToken, }); } ``` -------------------------------- ### Configure GRIP Publisher with Fastly Fanout Credentials Source: https://github.com/fanout/js-grip/blob/main/examples/remix/README.md Builds the GRIP configuration using Fastly Fanout credentials (service ID and API token) if they are present in the environment variables. ```typescript const fanoutServiceId = process.env.FANOUT_SERVICE_ID; const fanoutApiToken = process.env.FANOUT_API_TOKEN; if (fanoutServiceId != null && fanoutApiToken != null) { gripConfig = buildFanoutGripConfig({ serviceId: fanoutServiceId, apiToken: fanoutApiToken, }); } ``` -------------------------------- ### Create Directory for Fastly Fanout Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Creates a new directory for the Fastly Fanout Forwarding application and navigates into it. ```bash mkdir fastly-fanout-forward cd fastly-fanout-forward ``` -------------------------------- ### Import Core js-grip Components Source: https://github.com/fanout/js-grip/blob/main/README.md Import necessary functions, classes, and interfaces from the @fanoutio/grip package for use in your project. ```javascript import { createWebSocketControlMessage, Publisher, Format, Item } from '@fanoutio/grip'; ``` -------------------------------- ### applyConfigs Source: https://github.com/fanout/js-grip/blob/main/README.md Advanced method to apply additional clients based on specified GRIP configurations. This method is synchronous. ```APIDOC ## applyConfigs ### Description Advanced: Apply additional clients based on specified GRIP configs. ### Method `applyConfigs(configs) ### Parameters - `configs` (IGripConfig | IGripConfig[]) - The GRIP configurations. ``` -------------------------------- ### Configure GRIP Proxy Connection with IGripConfig Source: https://context7.com/fanout/js-grip/llms.txt Configure connection and authentication details for a GRIP proxy using the IGripConfig interface. The control_uri is mandatory; authentication fields are optional. Supports direct object configuration or multiple proxies. ```typescript import { Publisher } from '@fanoutio/grip'; import type { IGripConfig } from '@fanoutio/grip'; // Direct object configuration const config: IGripConfig = { control_uri: 'http://pushpin.myproject.com/', control_iss: 'my-service', // JWT `iss` claim for publishing auth key: 'secret-signing-key', // Shared secret for JWT signing verify_iss: 'pushpin', // Expected `iss` in incoming Grip-Sig verify_key: 'base64:dGVzdA==', // Key used to verify Grip-Sig JWT }; const publisher = new Publisher(config); // Multiple proxies — publish to all in parallel const multiPublisher = new Publisher([ { control_uri: 'http://proxy1.internal:5561/' }, { control_uri: 'http://proxy2.internal:5561/' }, ]); ``` -------------------------------- ### Configure Pushpin Routes Source: https://github.com/fanout/js-grip/blob/main/examples/fastly-compute/README.md This configuration snippet is used to set up Pushpin routes for local development. It directs all traffic to the local development server running on port 3000. ```text * 127.0.0.1:3000 ``` -------------------------------- ### applyConfig Source: https://github.com/fanout/js-grip/blob/main/README.md Advanced method to apply an additional GRIP proxy configuration. This method is synchronous. ```APIDOC ## applyConfig ### Description Advanced: Apply an additional GRIP proxy based on the specified GRIP config. ### Method `applyConfig(configs) ### Parameters - `configs` (IGripConfig | IGripConfig[]) - The GRIP proxy configuration. ``` -------------------------------- ### Configure GRIP URL Locally Source: https://github.com/fanout/js-grip/blob/main/examples/fastly-compute/README.md Sets a default GRIP configuration URL for local development. This can be overridden by environment variables. ```javascript let gripConfig = 'http://127.0.0.1:5561/'; ``` -------------------------------- ### GRIP Configuration with Fastly Fanout Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Configures the GRIP client for Fastly Fanout using service ID and API token. This is an alternative to using a local Pushpin instance. ```typescript const fanoutServiceId = env.FANOUT_SERVICE_ID; const fanoutApiToken = env.FANOUT_API_TOKEN; if (fanoutServiceId != null && fanoutApiToken != null) { gripConfig = buildFanoutGripConfig({ serviceId: fanoutServiceId, apiToken: fanoutApiToken, }); } ``` -------------------------------- ### Instantiate GRIP Publisher Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Instantiates the GRIP Publisher class with the determined GRIP configuration. This object is used to send messages via GRIP. ```typescript const publisher = new Publisher(gripConfig); ``` -------------------------------- ### HTTP streaming subscription Source: https://github.com/fanout/js-grip/blob/main/README.md This snippet shows how to establish an HTTP streaming subscription to a GRIP channel. ```APIDOC ## HTTP streaming subscription ### Description Establishes an HTTP streaming subscription to a GRIP channel. ### Method ```javascript const gripInstruct = new GripInstruct(); gripInstruct.addChannel(''); gripInstruct.setHoldStream(); return new Response( 'Body', { status: 200, headers: { ...gripInstruct.toHeaders(), 'Content-Type': 'text/plain', } } ); ``` ``` -------------------------------- ### Configure GRIP Publisher with GRIP_URL Source: https://github.com/fanout/js-grip/blob/main/examples/deno/README.md Configures the GRIP Publisher using the GRIP_URL environment variable, optionally merging a verify key. This allows overriding the default Pushpin URL. ```typescript const gripUrl = Deno.env.GRIP_URL; if (gripUrl) { gripConfig = parseGripUri(gripUrl, { 'verify-key': Deno.env.GRIP_VERIFY_KEY }); } ``` -------------------------------- ### Enable Fanout on Fastly Service Source: https://github.com/fanout/js-grip/blob/main/examples/cloudflare-workers/README.md Enables the Fanout product on your Fastly service. Allow a moment for deployment. ```bash fastly products --enable=fanout ``` -------------------------------- ### Set up HTTP long-polling subscription Source: https://github.com/fanout/js-grip/blob/main/README.md Configure a GripInstruct object for HTTP long-polling subscriptions. Optionally set a hold timeout value. ```javascript const gripInstruct = new GripInstruct(); gripInstruct.addChannel(''); gripInstruct.setHoldLongPoll(); // To optionally set a timeout value in seconds: // gripInstruct.setHoldLongPoll(); return new Response( 'Body', { status: 200, headers: { ...gripInstruct.toHeaders(), 'Content-Type': 'text/plain', } } ); ``` -------------------------------- ### Accept WebSocket Connection and Subscribe Source: https://github.com/fanout/js-grip/blob/main/examples/bun/websocket/README.md Accept an incoming WebSocket connection by queuing an OPEN message and subscribe the WebSocket to a specified channel. This should be done when wsContext.isOpening() is true. ```typescript if (wsContext.isOpening()) { wsContext.accept(); wsContext.subscribe('test'); } ``` -------------------------------- ### Build Fastly Fanout GRIP Config Source: https://context7.com/fanout/js-grip/llms.txt A convenience helper to construct a signed and verified `IGripConfig` for Fastly Fanout, automatically embedding the Fanout public JWK for `Grip-Sig` verification. ```APIDOC ## `buildFanoutGripConfig()` — Build a Fastly Fanout GRIP Config Convenience helper exported from `@fanoutio/grip/fastly-fanout` that constructs a properly signed and verified `IGripConfig` for Fastly Fanout from a service ID and API token, automatically embedding the Fanout public JWK for `Grip-Sig` verification. ```typescript import { Publisher } from '@fanoutio/grip'; import { buildFanoutGripConfig } from '@fanoutio/grip/fastly-fanout'; // Minimal usage — uses built-in Fastly Fanout public key const gripConfig = buildFanoutGripConfig({ serviceId: process.env.FANOUT_SERVICE_ID!, // e.g. 'aBcDeFgHiJkLmNop' apiToken: process.env.FANOUT_API_TOKEN!, // Fastly API token with 'global' scope }); const publisher = new Publisher(gripConfig); // Or on Fastly Compute where a custom fetch backend is required const computePublisher = new Publisher( buildFanoutGripConfig({ serviceId: 'aBcDeFgHiJkLmNop', apiToken: 'fastly-token-here', }), { fetch(input, init) { return fetch(input, { ...init, backend: 'fanout-publisher' }); }, } ); await computePublisher.publishHttpStream('live-scores', 'Goal! 2-1\n'); ``` ``` -------------------------------- ### Configure Publisher with Fastly Fanout Public Key Source: https://context7.com/fanout/js-grip/llms.txt Manually configure a Publisher instance using exported JWK or PEM constants for Fastly Fanout's public key. Ensure the correct key format is used based on the deployment environment. ```typescript import { PUBLIC_KEY_FASTLY_FANOUT_JWK, PUBLIC_KEY_FASTLY_FANOUT_PEM, } from '@fanoutio/grip/fastly-fanout'; // Manual full config using the exported JWK constant const publisher = new Publisher({ control_uri: `https://api.fastly.com/service/${process.env.FANOUT_SERVICE_ID}`, key: process.env.FANOUT_API_TOKEN, // Bearer token for publishing verify_iss: `fastly:${process.env.FANOUT_SERVICE_ID}`, verify_key: PUBLIC_KEY_FASTLY_FANOUT_JWK, // or PUBLIC_KEY_FASTLY_FANOUT_PEM (not supported on Fastly Compute) }); const { isProxied, isSigned } = await publisher.validateGripSig( incomingRequest.headers.get('Grip-Sig') ); // isProxied === true, isSigned === true when the signature is valid ``` -------------------------------- ### Configure GRIP Publisher with GRIP_URL Environment Variable Source: https://github.com/fanout/js-grip/blob/main/examples/bun/README.md Allows overriding the default GRIP configuration using the GRIP_URL environment variable. It also merges the GRIP_VERIFY_KEY if present. ```typescript const gripUrl = Bun.env.GRIP_URL; if (gripUrl) { gripConfig = parseGripUri(gripUrl, { 'verify-key': Bun.env.GRIP_VERIFY_KEY }); } ``` -------------------------------- ### Configure Pushpin Routes Source: https://github.com/fanout/js-grip/blob/main/examples/remix/README.md Sets up the Pushpin routing configuration to direct all traffic to the local Remix application running on port 3000. ```nginx * 127.0.0.1:3000 ``` -------------------------------- ### Build Fastly Fanout GRIP Config using buildFanoutGripConfig Source: https://github.com/fanout/js-grip/blob/main/README.md This function simplifies building the GRIP configuration object for Fastly Fanout. It requires your Fastly service ID and an API token with global scope. ```javascript import { buildFanoutGripConfig } from '@fanoutio/grip/fastly-compute'; import { Publisher } from '@fanoutio/grip'; const gripConfig = buildFanoutGripConfig({ serviceId: '', // Service of GRIP proxy apiToken: '', // API token that has 'global' scope on above service }); const publisher = new Publisher(gripConfig); ``` -------------------------------- ### Validate GRIP Signature and Set Up Stream Source: https://github.com/fanout/js-grip/blob/main/examples/nextjs/http-stream/README.md This code snippet validates the incoming GRIP signature and configures the server to hold the connection open as a streaming connection, listening to a specified channel. It's crucial for ensuring secure proxying and establishing the stream. ```typescript if (!gripStatus.isProxied) { // emit an error } ``` ```typescript const gripInstruct = new GripInstruct('test'); gripInstruct.setHoldStream(); ``` ```typescript return new Response( '[stream open]\n', { status: 200, headers: { ...gripInstruct.toHeaders(), 'Content-Type': 'text/plain', }, }, ); ``` -------------------------------- ### Return GRIP Streaming Response Source: https://github.com/fanout/js-grip/blob/main/examples/deno/http-stream/README.md Generate and return a response that includes the GRIP instruction headers, signaling the proxy to maintain the streaming connection. ```typescript return new Response( '[stream open]\n', { status: 200, headers: { ...gripInstruct.toHeaders(), 'Content-Type': 'text/plain', }, }, ); ``` -------------------------------- ### Customizing Publisher Prefixes Source: https://github.com/fanout/js-grip/blob/main/README.md This section explains how to customize the `Publisher` by setting a prefix for channel names, which is useful for namespacing. ```APIDOC ## Customizing `Publisher` Prefixes ### Description Allows customization of the `Publisher` by setting a `prefix` for channel names, useful for namespacing. ### Configuration Set the `prefix` property in the configuration parameter when instantiating the `Publisher`. ### Example ```javascript const publisher = new Publisher(process.env.GRIP_URL, { prefix: 'foo_' }); await publisher.publishHttpStream('test', 'Test Publish!'); // Message is sent to channel named 'foo_test' ``` ``` -------------------------------- ### Validating Incoming Requests Source: https://github.com/fanout/js-grip/blob/main/README.md Explains how to validate incoming client requests using the `validateGripSig` method to ensure they originate from the GRIP proxy. ```APIDOC ## Validating Incoming Requests When an incoming client request arrives at the GRIP proxy over HTTP, the proxy forwards the request to your origin application and adds the `Grip-Sig` header to the proxied request. It's highly recommended that your origin application validate this `Grip-Sig` to make sure it's coming from your GRIP proxy. To do this, call `publisher.validateGripSig()`: ```javascript // publisher instantiated above const gripSig = req.headers.get('Grip-Sig'); const { isProxied, isSigned } = await publisher.validateGripSig(gripSig); ``` If your publisher requires validation (i.e., is configured with a `verify_key`), then the signature of `Grip-Sig` will be checked with that key. If the key was able to successfully able to verify the signature (including checking for expiry), then both `isSigned` and `isProxied` will be `true`. Otherwise, they will both be `false`. If your publisher does not require validation, then the signature is not checked. `isSigned` will be `false`, and `isProxied` will be `true` if `Grip-Sig` is present, and `false` if it is not present. > NOTE: For backwards-compatibility reasons, if JWT authorization is used with a symmetric secret (`control_iss` and `key` are both provided, and `key` is not a private key) and `verify_key` is not provided, then `key` will be used as the `verify_key` value as well. ``` -------------------------------- ### parseGripUri() Function Source: https://context7.com/fanout/js-grip/llms.txt Parses a GRIP URL string into an IGripConfig object, optionally merging additional parameters. This is useful for constructing configuration from environment variables or other sources. ```APIDOC ## parseGripUri() Function ### Description Parses a compact `GRIP_URL` string (control URI + query params) into an `IGripConfig` object. It accepts an optional second argument to merge additional parameters, which is useful for large values like `GRIP_VERIFY_KEY` that might be stored separately. ### Usage ```typescript import { parseGripUri, Publisher } from '@fanoutio/grip'; // Basic parse const config = parseGripUri('http://127.0.0.1:5561/'); // => { control_uri: 'http://127.0.0.1:5561' } // With JWT authentication params embedded in URL const configWithAuth = parseGripUri( 'http://pushpin.example.com/?iss=myapp&key=mysecret' ); // => { control_uri: 'http://pushpin.example.com', control_iss: 'myapp', key: 'mysecret' } // Merging a separately stored verify-key const gripURL = process.env.GRIP_URL ?? 'http://127.0.0.1:5561/'; const gripVerifyKey = process.env.GRIP_VERIFY_KEY; // may be undefined const mergedConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey }); const publisher = new Publisher(mergedConfig); ``` ### Parameters * **gripURL** (string) - The GRIP URL string to parse. * **additionalParams** (object, optional) - An object containing additional parameters to merge with the parsed configuration. ### Returns * **IGripConfig** - An object representing the GRIP proxy configuration. ``` -------------------------------- ### Parse GRIP URL with parseGripUri() Source: https://context7.com/fanout/js-grip/llms.txt Parse a GRIP URL string into an IGripConfig object. Accepts an optional second argument to merge additional parameters, useful for large values like verify-keys. ```typescript import { parseGripUri, Publisher } from '@fanoutio/grip'; // Basic parse const config = parseGripUri('http://127.0.0.1:5561/'); // => { control_uri: 'http://127.0.0.1:5561' } // With JWT authentication params embedded in URL const configWithAuth = parseGripUri( 'http://pushpin.example.com/?iss=myapp&key=mysecret' ); // => { control_uri: 'http://pushpin.example.com', control_iss: 'myapp', key: 'mysecret' } // Merging a separately stored verify-key (e.g. a public key too large for an env var alongside GRIP_URL) const gripURL = process.env.GRIP_URL ?? 'http://127.0.0.1:5561/'; const gripVerifyKey = process.env.GRIP_VERIFY_KEY; // may be undefined const mergedConfig = parseGripUri(gripURL, { 'verify-key': gripVerifyKey }); const publisher = new Publisher(mergedConfig); ``` -------------------------------- ### Return Publishing Success Response Source: https://github.com/fanout/js-grip/blob/main/examples/deno/http-stream/README.md Send a simple success response after successfully publishing a message to a GRIP channel. ```typescript return new Response( 'Ok\n', { status: 200, headers: { 'Content-Type': 'text/plain', }, }, ); ```