diff --git a/package.json b/package.json index 12adfd2f6..87317618e 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "contract-tests", "packages/sdk/combined-browser", "packages/sdk/shopify-oxygen", - "packages/sdk/shopify-oxygen/contract-tests" + "packages/sdk/shopify-oxygen/contract-tests", + "packages/sdk/shopify-oxygen/example" ], "private": true, "scripts": { diff --git a/packages/sdk/shopify-oxygen/example/README.md b/packages/sdk/shopify-oxygen/example/README.md new file mode 100644 index 000000000..a81c77e44 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/README.md @@ -0,0 +1,30 @@ +# LaunchDarkly sample Shopify Oxygen application + +We've built a simple console application that demonstrates how this LaunchDarkly SDK works. + +Below, you'll find the build procedure. For more comprehensive instructions, you can visit your [Quickstart page](https://app.launchdarkly.com/quickstart#/). + + +This demo requires `Node >= 22.0.0` and `yarn@^3.4.1` + +## Build instructions + +1. Edit [`src/index.ts`](src/index.ts) and set the value of `sdkKey` to your LaunchDarkly SDK key. + ``` + const sdkKey = "1234567890abcdef"; + ``` + +2. If there is an existing boolean feature flag in your LaunchDarkly project that + you want to evaluate, set `flagKey` to the flag key: + ``` + const flagKey = "my-flag-key"; + ``` + > Otherwise, `sample-feature` will be used by default. + +3. On the command line, run `yarn start`, You should receive the message: + ``` + The {flagKey} feature flag evaluates to {flagValue}. + ``` +The application will run continuously and react to the flag changes in LaunchDarkly. \ No newline at end of file diff --git a/packages/sdk/shopify-oxygen/example/app.js b/packages/sdk/shopify-oxygen/example/app.js new file mode 100644 index 000000000..7bdf4a979 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/app.js @@ -0,0 +1,86 @@ +import {createMiniOxygen} from '@shopify/mini-oxygen'; + +/** + * This is script is a simple runner for our example app. This script will run + * the compiled example on a local worker implementation to emulate a Oxygen worker runtime. + * + * For the actual example implementation, see the src/index.ts file. + */ + +const printValueAndBanner = (flagKey, flagValue) => { + console.log(`*** The '${flagKey}' feature flag evaluates to ${flagValue}.`); + + if (flagValue) { + console.log( + ` ██ + ██ + ████████ + ███████ + ██ LAUNCHDARKLY █ + ███████ + ████████ + ██ + ██ + `, + ); + } +} + +const main = async () => { + // NOTE: you will see logging coming from mini-oxygen's default request hook. + // https://github.com/Shopify/hydrogen/blob/5a38948133766e358c5f357f52562f6fdcfe7969/packages/mini-oxygen/src/worker/index.ts#L225 + const miniOxygen = createMiniOxygen({ + debug: false, + workers: [ + { + name: 'main', + modules: true, + scriptPath: 'dist/index.js' + }, + ], + }); + + miniOxygen.ready.then(() => { + console.log('Oxygen worker is started...'); + console.log('Press "q" or Ctrl+C to quit...'); + + // Dispatch fetch every 5 seconds + const interval = setInterval(() => { + // NOTE: This is a bogus URL and will not be used in the actual fetch handler. + // please see the src/index.ts file for the actual fetch handler. + miniOxygen.dispatchFetch('https://localhost:8000') + .then(d => d.json()) + .then(({flagValue, flagKey}) => { + console.clear(); + printValueAndBanner(flagKey, flagValue); + console.log('Press "q" or Ctrl+C to quit...') + }).catch((err) => { + console.log('Error dispatching fetch:', err.message); + console.log('Press "q" or Ctrl+C to quit...') + }); + }, 1000); + + // Handle keypresses for cleanup + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding('utf8'); + + process.stdin.on('data', async (key) => { + // Handle Ctrl+C + if (key === '\u0003') { + clearInterval(interval); + await miniOxygen.dispose(); + process.exit(); + } + + // Handle 'q' key + if (key === 'q' || key === 'Q') { + clearInterval(interval); + await miniOxygen.dispose(); + process.exit(); + } + }); + }); +} + +main().catch(console.error); diff --git a/packages/sdk/shopify-oxygen/example/package.json b/packages/sdk/shopify-oxygen/example/package.json new file mode 100644 index 000000000..6938f6b97 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/package.json @@ -0,0 +1,17 @@ +{ + "name": "shopify-oxygen-sdk-example", + "packageManager": "yarn@3.4.1", + "scripts": { + "build": "tsup", + "clean": "rm -rf dist", + "start": "yarn clean && yarn build && yarn node app.js" + }, + "type": "module", + "dependencies": { + "@launchdarkly/shopify-oxygen-sdk": "latest", + "@shopify/mini-oxygen": "^4.0.0" + }, + "devDependencies": { + "tsup": "^8.5.1" + } +} diff --git a/packages/sdk/shopify-oxygen/example/src/index.ts b/packages/sdk/shopify-oxygen/example/src/index.ts new file mode 100644 index 000000000..c7e0d1f2d --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/src/index.ts @@ -0,0 +1,31 @@ +import { init } from '@launchdarkly/shopify-oxygen-sdk'; + +// Set sdkKey to your LaunchDarkly SDK key. +const sdkKey = 'sample-sdk-key'; + +// Set featureFlagKey to the feature flag key you want to evaluate. +const flagKey = 'sample-feature'; + +const context = { + kind: 'user', + key: 'example-user-key', + name: 'Sandy', +}; + +const sdkOptions = { + // See the README.md file for more information on the options. +}; + +export default { + async fetch() { + const ldClient = await init(sdkKey, sdkOptions); + await ldClient.waitForInitialization({ timeout: 10 }); + const flagValue = await ldClient.variation(flagKey, context, false); + + // Flush events and close the client + ldClient.flush(); + ldClient.close(); + + return new Response(JSON.stringify({ flagKey, flagValue }), { status: 200 }); + }, +}; diff --git a/packages/sdk/shopify-oxygen/example/tsconfig.json b/packages/sdk/shopify-oxygen/example/tsconfig.json new file mode 100644 index 000000000..4e0b21e2a --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "lib": ["es6"], + "module": "es2022", + "moduleResolution": "node", + "noImplicitOverride": true, + "outDir": "dist", + "resolveJsonModule": true, + "rootDir": ".", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "stripInternal": true, + "target": "ES2022", + }, + "exclude": ["tsup.config.ts"] +} diff --git a/packages/sdk/shopify-oxygen/example/tsup.config.ts b/packages/sdk/shopify-oxygen/example/tsup.config.ts new file mode 100644 index 000000000..28a2fa346 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/tsup.config.ts @@ -0,0 +1,16 @@ +// It is a dev dependency and the linter doesn't understand. +// @ts-ignore - tsup is a dev dependency installed at runtime +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { + index: 'src/index.ts', + }, + minify: true, + format: ['esm'], + splitting: false, + clean: true, + noExternal: ['@launchdarkly/shopify-oxygen-sdk'], + dts: true, +});