@jay-framework/wix-server-client 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # @jay-framework/wix-server-client
2
+
3
+ A Wix SDK client for the Jay Framework with support for both server-side API Key authentication and client-side OAuth visitor authentication.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @jay-framework/wix-server-client
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ Create a configuration file at `./config/.wix.yaml` relative to your process execution path:
14
+
15
+ ```yaml
16
+ # Server-side API Key authentication (required)
17
+ apiKeyStrategy:
18
+ apiKey: 'your-api-key-here'
19
+ siteId: 'your-site-id-here'
20
+
21
+ # Client-side OAuth authentication (optional)
22
+ # Enables direct API calls from the browser
23
+ oauthStrategy:
24
+ clientId: 'your-oauth-client-id-here'
25
+ ```
26
+
27
+ ### Configuration Fields
28
+
29
+ **apiKeyStrategy** (required):
30
+
31
+ - `apiKey`: Your Wix API key (string, required)
32
+ - `siteId`: Your Wix site ID (string, required)
33
+
34
+ **oauthStrategy** (optional):
35
+
36
+ - `clientId`: OAuth client ID for visitor authentication
37
+
38
+ ## Server-Side Usage
39
+
40
+ ```typescript
41
+ import { getWixClient } from '@jay-framework/wix-server-client';
42
+ import { products } from '@wix/stores';
43
+
44
+ // Get the API Key authenticated client
45
+ const wixClient = getWixClient();
46
+
47
+ // Make server-side API calls
48
+ const productsClient = wixClient.use(products);
49
+ const myProducts = await productsClient.queryProducts().find();
50
+ ```
51
+
52
+ ## Client-Side OAuth Authentication
53
+
54
+ When `oauthStrategy.clientId` is configured, the plugin enables client-side API calls using OAuth visitor tokens. This is useful for:
55
+
56
+ - Add to cart without server round-trip
57
+ - Real-time search autocomplete
58
+ - Client-side cart management
59
+
60
+ ### How It Works
61
+
62
+ Based on the [Wix Headless Visitor Authentication Guide](https://dev.wix.com/docs/go-headless/develop-your-project/self-managed-headless/authentication/visitors/handle-visitors-using-the-js-sdk):
63
+
64
+ 1. **Server Init**: Passes OAuth `clientId` to the client via `setClientInitData`
65
+ 2. **Client Init**: Creates an OAuth-authenticated Wix client
66
+ 3. **Token Management**: Visitor tokens are stored in `localStorage` for session persistence
67
+ 4. **Session Resume**: On page reload, existing tokens are used to resume the visitor session
68
+
69
+ ```
70
+ ┌─────────────────────────────────────────────────────────────────────┐
71
+ │ SERVER (lib/init.ts - withServer) │
72
+ ├─────────────────────────────────────────────────────────────────────┤
73
+ │ Config: oauthStrategy.clientId = "abc123" │
74
+ │ return { oauthClientId: "abc123" } // typed data for client │
75
+ └─────────────────────────────────────────────────────────────────────┘
76
+
77
+ ▼ (embedded in HTML)
78
+ ┌─────────────────────────────────────────────────────────────────────┐
79
+ │ CLIENT (lib/init.ts - withClient) │
80
+ ├─────────────────────────────────────────────────────────────────────┤
81
+ │ 1. Receive typed data from withServer │
82
+ │ 2. Check localStorage for existing tokens │
83
+ │ 3. Create OAuthStrategy client with clientId │
84
+ │ 4. If no tokens: generateVisitorTokens() and store │
85
+ │ 5. If tokens exist: validate/refresh and resume session │
86
+ │ 6. Register WIX_CLIENT_CONTEXT for component access │
87
+ └─────────────────────────────────────────────────────────────────────┘
88
+ ```
89
+
90
+ ### Using the Client in Components
91
+
92
+ ```typescript
93
+ import { useContext } from '@jay-framework/runtime';
94
+ import { WIX_CLIENT_CONTEXT } from '@jay-framework/wix-server-client';
95
+
96
+ // In your interactive component
97
+ function MyComponent(props, refs) {
98
+ const wixClient = useContext(WIX_CLIENT_CONTEXT);
99
+
100
+ if (wixClient.isReady) {
101
+ // Make client-side API calls
102
+ const tokens = wixClient.getTokens();
103
+ console.log('Visitor session active');
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Token Storage
109
+
110
+ Tokens are automatically stored in `localStorage` under the key `wix_visitor_tokens`:
111
+
112
+ ```typescript
113
+ // Clear tokens (for logout or session reset)
114
+ import { clearStoredTokens } from '@jay-framework/wix-server-client';
115
+
116
+ clearStoredTokens();
117
+ ```
118
+
119
+ ## Plugin Initialization
120
+
121
+ This package uses the `makeJayInit` pattern for consolidated server/client initialization:
122
+
123
+ ### lib/init.ts
124
+
125
+ ```typescript
126
+ import { makeJayInit } from '@jay-framework/fullstack-component';
127
+ import { loadConfig } from './config-loader.js';
128
+ import { registerGlobalContext } from '@jay-framework/runtime';
129
+
130
+ export const init = makeJayInit()
131
+ .withServer(async () => {
132
+ const config = loadConfig();
133
+
134
+ // Pass OAuth client ID to client for visitor authentication
135
+ return {
136
+ oauthClientId: config.oauth?.clientId || null,
137
+ };
138
+ })
139
+ .withClient(async (data) => {
140
+ // Set up OAuth-authenticated Wix client
141
+ // Token management with localStorage
142
+ registerGlobalContext(WIX_CLIENT_CONTEXT, clientContext);
143
+ });
144
+ ```
145
+
146
+ ### Initialization Order
147
+
148
+ 1. `wix-server-client` must initialize before `wix-stores`
149
+ 2. Add `wix-server-client` as a dependency in your project's `package.json`
150
+ 3. Plugin dependencies in `package.json` determine initialization order
151
+
152
+ ## API Reference
153
+
154
+ ### Server-Side
155
+
156
+ #### `getWixClient(): WixClient`
157
+
158
+ Returns a server-side Wix client authenticated with API Key.
159
+
160
+ #### `getOAuthClientId(): string | undefined`
161
+
162
+ Returns the OAuth client ID from config, or undefined if not configured.
163
+
164
+ #### `loadConfig(): WixConfig`
165
+
166
+ Loads and validates the configuration from `./config/.wix.yaml`.
167
+
168
+ ### Client-Side
169
+
170
+ #### `WIX_CLIENT_CONTEXT`
171
+
172
+ Context marker for accessing the Wix client in components.
173
+
174
+ ```typescript
175
+ interface WixClientContext {
176
+ client: WixClient | null;
177
+ isReady: boolean;
178
+ getTokens(): Tokens | null;
179
+ generateVisitorTokens(): Promise<Tokens>;
180
+ refreshToken(): Promise<Tokens>;
181
+ }
182
+ ```
183
+
184
+ #### `getWixClientInstance(): WixClient | null`
185
+
186
+ Get the global Wix client instance for imperative access.
187
+
188
+ #### `clearStoredTokens(): void`
189
+
190
+ Clear stored visitor tokens from localStorage.
191
+
192
+ ## Error Handling
193
+
194
+ The `loadConfig()` function will throw descriptive errors if:
195
+
196
+ - The config file is not found at `./config/.wix.yaml`
197
+ - The YAML syntax is invalid
198
+ - Required fields (`apiKey`, `siteId`) are missing
199
+ - Required fields are empty strings
200
+
201
+ ## License
202
+
203
+ Apache-2.0
@@ -0,0 +1,73 @@
1
+ import { createJayContext, registerGlobalContext } from "@jay-framework/runtime";
2
+ import { createClient, OAuthStrategy } from "@wix/sdk";
3
+ import { makeJayInit } from "@jay-framework/fullstack-component";
4
+ const WIX_CLIENT_CONTEXT = createJayContext();
5
+ const TOKENS_STORAGE_KEY = "wix_visitor_tokens";
6
+ function storeTokens(tokens, oauthClientId) {
7
+ try {
8
+ localStorage.setItem(
9
+ TOKENS_STORAGE_KEY + oauthClientId,
10
+ JSON.stringify({ tokens, oauthClientId })
11
+ );
12
+ } catch (error) {
13
+ console.warn("[WixClient] Failed to store tokens:", error);
14
+ }
15
+ }
16
+ function getStoredTokens(oauthClientId) {
17
+ try {
18
+ const stored = localStorage.getItem(TOKENS_STORAGE_KEY + oauthClientId);
19
+ if (stored) {
20
+ return JSON.parse(stored);
21
+ }
22
+ } catch (error) {
23
+ console.warn("[WixClient] Failed to retrieve tokens:", error);
24
+ }
25
+ return null;
26
+ }
27
+ async function provideWixClientContext(oauthClientId) {
28
+ const storedTokens = getStoredTokens(oauthClientId);
29
+ console.log("[wix-server-client] createClient with tokens: ", void 0);
30
+ const wixClient = createClient({
31
+ auth: OAuthStrategy({
32
+ clientId: oauthClientId,
33
+ tokens: storedTokens?.oauthClientId === oauthClientId ? storedTokens.tokens : void 0
34
+ })
35
+ });
36
+ if (!storedTokens || !storedTokens?.oauthClientId) {
37
+ const tokens = await wixClient.auth.generateVisitorTokens();
38
+ wixClient.auth.setTokens(tokens);
39
+ storeTokens(tokens, oauthClientId);
40
+ }
41
+ const clientContext = {
42
+ client: wixClient,
43
+ isReady: true,
44
+ getTokens() {
45
+ return wixClient.auth.getTokens();
46
+ },
47
+ async generateVisitorTokens() {
48
+ const tokens = await wixClient.auth.generateVisitorTokens();
49
+ storeTokens(tokens, oauthClientId);
50
+ return tokens;
51
+ },
52
+ async refreshToken() {
53
+ const currentTokens = wixClient.auth.getTokens();
54
+ if (!currentTokens?.refreshToken) {
55
+ throw new Error("No refresh token available");
56
+ }
57
+ const tokens = await wixClient.auth.renewToken(currentTokens.refreshToken);
58
+ storeTokens(tokens, oauthClientId);
59
+ return tokens;
60
+ }
61
+ };
62
+ registerGlobalContext(WIX_CLIENT_CONTEXT, clientContext);
63
+ }
64
+ const init = makeJayInit().withClient(async (data) => {
65
+ console.log("[wix-server-client] Initializing client-side Wix client...");
66
+ const { oauthClientId } = data;
67
+ await provideWixClientContext(oauthClientId);
68
+ console.log("[wix-server-client] Client initialization complete");
69
+ });
70
+ export {
71
+ WIX_CLIENT_CONTEXT,
72
+ init
73
+ };
@@ -0,0 +1,62 @@
1
+ import * as _jay_framework_fullstack_component from '@jay-framework/fullstack-component';
2
+ import { WixClient, Tokens } from '@wix/sdk';
3
+ import * as _jay_framework_runtime from '@jay-framework/runtime';
4
+ import { PluginSetupContext, PluginSetupResult } from '@jay-framework/stack-server-runtime';
5
+
6
+ /**
7
+ * Configuration for API Key authentication (server-side)
8
+ */
9
+ interface ApiKeyConfig {
10
+ apiKey: string;
11
+ siteId: string;
12
+ }
13
+ /**
14
+ * Configuration for OAuth authentication (client-side)
15
+ */
16
+ interface OAuthConfig {
17
+ clientId: string;
18
+ }
19
+ /**
20
+ * Full Wix configuration
21
+ */
22
+ interface WixConfig {
23
+ /** API Key strategy for server-side auth (required) */
24
+ apiKey: ApiKeyConfig;
25
+ /** OAuth strategy for client-side auth (optional) */
26
+ oauth: OAuthConfig;
27
+ }
28
+ declare function loadConfig(): WixConfig;
29
+
30
+ interface WixClientService {
31
+ wixClient: WixClient;
32
+ }
33
+ declare const WIX_CLIENT_SERVICE: _jay_framework_fullstack_component.ServiceMarker<WixClientService>;
34
+
35
+ interface WixClientContext {
36
+ /** The Wix SDK client (null if OAuth not configured) */
37
+ client: WixClient | null;
38
+ /** Whether the client is ready for use */
39
+ isReady: boolean;
40
+ /** Get current tokens */
41
+ getTokens(): Tokens | null;
42
+ /** Generate new visitor tokens (creates new session) */
43
+ generateVisitorTokens(): Promise<Tokens>;
44
+ /** Refresh the access token using the refresh token */
45
+ refreshToken(): Promise<Tokens>;
46
+ }
47
+ declare const WIX_CLIENT_CONTEXT: _jay_framework_runtime.ContextMarker<WixClientContext>;
48
+
49
+ declare const init: _jay_framework_fullstack_component.JayInit<{
50
+ oauthClientId: string;
51
+ }>;
52
+
53
+ /**
54
+ * Setup handler for wix-server-client plugin (Design Log #87).
55
+ *
56
+ * Creates config/.wix.yaml template if missing, validates credentials.
57
+ * No reference data (credentials-only plugin).
58
+ */
59
+
60
+ declare function setupWixServerClient(ctx: PluginSetupContext): Promise<PluginSetupResult>;
61
+
62
+ export { type ApiKeyConfig, type OAuthConfig, WIX_CLIENT_CONTEXT, WIX_CLIENT_SERVICE, type WixClientContext, type WixClientService, type WixConfig, init, loadConfig, setupWixServerClient };
package/dist/index.js ADDED
@@ -0,0 +1,153 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as yaml from "js-yaml";
4
+ import { createClient, ApiKeyStrategy } from "@wix/sdk";
5
+ import { createJayService, makeJayInit } from "@jay-framework/fullstack-component";
6
+ import { registerService } from "@jay-framework/stack-server-runtime";
7
+ import { createJayContext } from "@jay-framework/runtime";
8
+ function loadConfig() {
9
+ const configPath = path.join(process.cwd(), "config", ".wix.yaml");
10
+ if (!fs.existsSync(configPath)) {
11
+ throw new Error(`Config file not found at: ${configPath}`);
12
+ }
13
+ const fileContents = fs.readFileSync(configPath, "utf8");
14
+ const config = yaml.load(fileContents);
15
+ if (!config) {
16
+ throw new Error("Config file is empty or invalid");
17
+ }
18
+ if (!config.apiKeyStrategy) {
19
+ throw new Error('Config validation failed: "apiKeyStrategy" section is required');
20
+ }
21
+ const strategy = config.apiKeyStrategy;
22
+ if (!strategy.apiKey) {
23
+ throw new Error('Config validation failed: "apiKeyStrategy.apiKey" is required');
24
+ }
25
+ if (typeof strategy.apiKey !== "string" || strategy.apiKey.trim() === "") {
26
+ throw new Error(
27
+ 'Config validation failed: "apiKeyStrategy.apiKey" must be a non-empty string'
28
+ );
29
+ }
30
+ if (!strategy.siteId) {
31
+ throw new Error('Config validation failed: "apiKeyStrategy.siteId" is required');
32
+ }
33
+ if (typeof strategy.siteId !== "string" || strategy.siteId.trim() === "") {
34
+ throw new Error(
35
+ 'Config validation failed: "apiKeyStrategy.siteId" must be a non-empty string'
36
+ );
37
+ }
38
+ if (!config.oauthStrategy) {
39
+ throw new Error('Config validation failed: "oauthStrategy" section is required');
40
+ }
41
+ const oauth = config.oauthStrategy;
42
+ if (!oauth.clientId || typeof oauth.clientId !== "string" || oauth.clientId.trim() === "") {
43
+ throw new Error(
44
+ 'Config validation failed: "oauthStrategy.clientId" must be a non-empty string'
45
+ );
46
+ }
47
+ return {
48
+ apiKey: {
49
+ apiKey: strategy.apiKey,
50
+ siteId: strategy.siteId
51
+ },
52
+ oauth: {
53
+ clientId: oauth.clientId
54
+ }
55
+ };
56
+ }
57
+ const WIX_CLIENT_SERVICE = createJayService("WixClientService");
58
+ function provideWixClientService(config) {
59
+ const instance = createClient({
60
+ auth: ApiKeyStrategy({
61
+ apiKey: config.apiKey.apiKey,
62
+ siteId: config.apiKey.siteId
63
+ }),
64
+ modules: {}
65
+ });
66
+ registerService(WIX_CLIENT_SERVICE, instance);
67
+ }
68
+ const WIX_CLIENT_CONTEXT = createJayContext();
69
+ const init = makeJayInit().withServer(async () => {
70
+ console.log("[wix-server-client] Initializing Wix client configuration...");
71
+ const config = loadConfig();
72
+ provideWixClientService(config);
73
+ return {
74
+ oauthClientId: config.oauth.clientId
75
+ };
76
+ });
77
+ const CONFIG_FILE_NAME = ".wix.yaml";
78
+ const CONFIG_TEMPLATE = `# Wix API Configuration
79
+ #
80
+ # This file contains credentials for connecting to your Wix site.
81
+ # Get these values from your Wix dashboard:
82
+ # - API Key: https://dev.wix.com/docs/rest/articles/getting-started/api-keys
83
+ # - Site ID: Found in your Wix dashboard URL or site settings
84
+ # - OAuth Client ID: Create an OAuth app in Wix Developers dashboard
85
+ #
86
+ # IMPORTANT: This file contains secrets. Add config/.wix.yaml to .gitignore.
87
+
88
+ # Server-side authentication (required)
89
+ apiKeyStrategy:
90
+ apiKey: "<your-api-key>"
91
+ siteId: "<your-site-id>"
92
+
93
+ # Client-side authentication (required for interactive features)
94
+ oauthStrategy:
95
+ clientId: "<your-oauth-client-id>"
96
+ `;
97
+ async function setupWixServerClient(ctx) {
98
+ const configPath = path.join(ctx.configDir, CONFIG_FILE_NAME);
99
+ if (!fs.existsSync(configPath)) {
100
+ if (!fs.existsSync(ctx.configDir)) {
101
+ fs.mkdirSync(ctx.configDir, { recursive: true });
102
+ }
103
+ fs.writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
104
+ return {
105
+ status: "needs-config",
106
+ configCreated: [`config/${CONFIG_FILE_NAME}`],
107
+ message: "Fill in your Wix API credentials and re-run: jay-stack setup wix-server-client"
108
+ };
109
+ }
110
+ try {
111
+ const configContent = fs.readFileSync(configPath, "utf-8");
112
+ const config = yaml.load(configContent);
113
+ if (!config) {
114
+ return {
115
+ status: "error",
116
+ message: `Config file is empty: config/${CONFIG_FILE_NAME}`
117
+ };
118
+ }
119
+ const apiKey = config.apiKeyStrategy?.apiKey || "";
120
+ const siteId = config.apiKeyStrategy?.siteId || "";
121
+ const clientId = config.oauthStrategy?.clientId || "";
122
+ const hasPlaceholders = apiKey.startsWith("<") || siteId.startsWith("<") || clientId.startsWith("<");
123
+ const isEmpty = !apiKey || !siteId;
124
+ if (hasPlaceholders || isEmpty) {
125
+ return {
126
+ status: "needs-config",
127
+ message: `Config has placeholder values. Fill in credentials in config/${CONFIG_FILE_NAME}`
128
+ };
129
+ }
130
+ if (ctx.initError) {
131
+ return {
132
+ status: "error",
133
+ message: `Credentials invalid or connection failed: ${ctx.initError.message}`
134
+ };
135
+ }
136
+ return {
137
+ status: "configured",
138
+ message: `Wix client connected (site: ${siteId.substring(0, 8)}...)`
139
+ };
140
+ } catch (error) {
141
+ return {
142
+ status: "error",
143
+ message: `Failed to read config: ${error.message}`
144
+ };
145
+ }
146
+ }
147
+ export {
148
+ WIX_CLIENT_CONTEXT,
149
+ WIX_CLIENT_SERVICE,
150
+ init,
151
+ loadConfig,
152
+ setupWixServerClient
153
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@jay-framework/wix-server-client",
3
+ "version": "0.15.0",
4
+ "type": "module",
5
+ "description": "Wix SDK client configuration and authentication for Jay Framework",
6
+ "license": "Apache-2.0",
7
+ "main": "dist/index.js",
8
+ "files": [
9
+ "dist",
10
+ "plugin.yaml"
11
+ ],
12
+ "exports": {
13
+ ".": "./dist/index.js",
14
+ "./client": "./dist/index.client.js",
15
+ "./plugin.yaml": "./plugin.yaml"
16
+ },
17
+ "scripts": {
18
+ "build": "npm run clean && npm run build:client && npm run build:server && npm run build:types",
19
+ "build:client": "vite build",
20
+ "build:server": "vite build --ssr",
21
+ "build:types": "tsup lib/index.ts --dts-only --format esm",
22
+ "build:check-types": "tsc",
23
+ "clean": "rimraf dist",
24
+ "confirm": "npm run clean && npm run build && npm run test",
25
+ "test": ":"
26
+ },
27
+ "dependencies": {
28
+ "@jay-framework/component": "^0.14.0",
29
+ "@jay-framework/fullstack-component": "^0.14.0",
30
+ "@jay-framework/reactive": "^0.14.0",
31
+ "@jay-framework/runtime": "^0.14.0",
32
+ "@jay-framework/secure": "^0.14.0",
33
+ "@jay-framework/stack-client-runtime": "^0.14.0",
34
+ "@jay-framework/stack-server-runtime": "^0.14.0",
35
+ "@wix/sdk": "^1.21.5",
36
+ "js-yaml": "^4.1.0"
37
+ },
38
+ "devDependencies": {
39
+ "@babel/core": "^7.23.7",
40
+ "@babel/preset-env": "^7.23.8",
41
+ "@babel/preset-typescript": "^7.23.3",
42
+ "@jay-framework/compiler-jay-stack": "^0.14.0",
43
+ "@jay-framework/jay-cli": "^0.14.0",
44
+ "@jay-framework/vite-plugin": "^0.14.0",
45
+ "@types/js-yaml": "^4.0.9",
46
+ "@types/node": "^20.11.0",
47
+ "nodemon": "^3.0.3",
48
+ "rimraf": "^5.0.5",
49
+ "tslib": "^2.6.2",
50
+ "tsup": "^8.5.1",
51
+ "typescript": "^5.3.3",
52
+ "vite": "^5.0.11",
53
+ "vite-plugin-inspect": "^0.8.1"
54
+ }
55
+ }
package/plugin.yaml ADDED
@@ -0,0 +1,9 @@
1
+ name: wix-server-client
2
+ # Wix SDK client configuration and authentication
3
+
4
+ # Plugin initialization uses makeJayInit pattern in lib/init.ts
5
+ # Export name defaults to 'init'
6
+
7
+ setup:
8
+ handler: setupWixServerClient
9
+ description: Configure Wix API credentials (API key, site ID, OAuth client ID)