Skip to content

Commit 62b6424

Browse files
Cache-busting loader script
1 parent 3314c76 commit 62b6424

9 files changed

Lines changed: 942 additions & 25 deletions

File tree

DEPLOYMENT.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# Cache-Busting Deployment Guide
2+
3+
This guide explains how to deploy the cache-busting solution for the Buzzwald widget.
4+
5+
## Overview
6+
7+
The cache-busting solution ensures users always get the latest widget version without changing their embed code. It works by:
8+
9+
1. **Loader Script**: A lightweight script that never changes and can be cached
10+
2. **Version Check**: Fetches current version info from `version.json`
11+
3. **Dynamic Loading**: Loads the actual widget with cache-busting parameters
12+
13+
## New User Embed Code
14+
15+
Users will now use this single script tag that never changes:
16+
17+
```html
18+
<script>
19+
window.BuzzwaldConfig = {
20+
id: 'your-assistant-id',
21+
token: '', // Optional - will be fetched automatically
22+
backgroundColor: '#FFFF00',
23+
iconColor: '#000000'
24+
};
25+
</script>
26+
<script src="https://cdn.jsdelivr.net/gh/StructuredLabs/buzzwald-client@latest/dist/buzzwald.js"></script>
27+
```
28+
29+
## Build Process
30+
31+
### Files Generated
32+
33+
After running `bun run build`, these files are created in `dist/`:
34+
35+
- `buzzwald.js` - The cache-busting loader (users embed this)
36+
- `buzzwald-widget.js` - The actual widget code
37+
- `version.json` - Version information for cache busting
38+
39+
### Version Info Structure
40+
41+
The `version.json` file contains:
42+
43+
```json
44+
{
45+
"version": "0.0.1",
46+
"commit": "abc1234",
47+
"timestamp": 1703123456789,
48+
"buildDate": "2023-12-21T10:30:45.123Z",
49+
"buildId": "0.0.1-abc1234-1703123456789",
50+
"cacheBuster": "v=0.0.1&t=1703123456789"
51+
}
52+
```
53+
54+
## Deployment Steps
55+
56+
### 1. Build the Project
57+
58+
```bash
59+
bun run build:release
60+
```
61+
62+
This creates the `dist/` directory with all necessary files.
63+
64+
### 2. Create GitHub Release
65+
66+
1. Go to your GitHub repository
67+
2. Click "Releases" → "Create a new release"
68+
3. Choose a tag version (e.g., `v0.0.2`)
69+
4. Upload the `dist/` folder contents as release assets, OR
70+
5. Let GitHub Actions build and attach the assets automatically
71+
72+
### 3. JSDelivr Automatic Updates
73+
74+
JSDelivr automatically updates `@latest` when you create a new GitHub release:
75+
76+
- `https://cdn.jsdelivr.net/gh/StructuredLabs/buzzwald-client@latest/dist/buzzwald.js`
77+
- `https://cdn.jsdelivr.net/gh/StructuredLabs/buzzwald-client@latest/dist/buzzwald-widget.js`
78+
- `https://cdn.jsdelivr.net/gh/StructuredLabs/buzzwald-client@latest/dist/version.json`
79+
80+
## How Cache Busting Works
81+
82+
### 1. Loader Script (`buzzwald.js`)
83+
- Users embed this script (it never changes)
84+
- Can be cached indefinitely by browsers
85+
- Responsible for loading the actual widget
86+
87+
### 2. Version Check
88+
- Loader fetches `version.json` with cache-busting headers
89+
- Cached locally for 5 minutes to avoid excessive requests
90+
- Contains timestamp and version info
91+
92+
### 3. Widget Loading
93+
- Widget is loaded with URL parameters: `?v=0.0.1&t=1703123456789`
94+
- Browser treats this as a new URL when version/timestamp changes
95+
- Bypasses browser cache when you deploy updates
96+
97+
### 4. Fallback Strategy
98+
- If version check fails, loads widget with current timestamp
99+
- Ensures widget always loads even if version service is down
100+
- Graceful degradation for network issues
101+
102+
## Cache Settings
103+
104+
### Version Cache
105+
- **Duration**: 5 minutes
106+
- **Purpose**: Avoid excessive version checks
107+
- **Storage**: localStorage
108+
109+
### Script Cache
110+
- **Duration**: 1 hour
111+
- **Purpose**: Avoid re-downloading same widget version
112+
- **Storage**: localStorage
113+
114+
## Testing
115+
116+
### Development Testing
117+
```bash
118+
bun run test:cache-busting
119+
```
120+
121+
Opens the cache-busting test page with tools to:
122+
- Simulate version updates
123+
- Clear cache
124+
- Monitor network requests
125+
- Check version info
126+
127+
### Production Testing
128+
After deployment, test with:
129+
1. Load widget on test page
130+
2. Clear browser cache
131+
3. Reload page - should get latest version
132+
4. Check network tab for cache-busting parameters
133+
134+
## Troubleshooting
135+
136+
### Widget Not Loading
137+
1. Check browser console for errors
138+
2. Verify version.json is accessible
139+
3. Check network tab for failed requests
140+
4. Test with cache disabled
141+
142+
### Users Not Getting Updates
143+
1. Confirm GitHub release was created
144+
2. Check JSDelivr cache (may take 1-2 minutes)
145+
3. Verify version.json has updated timestamp
146+
4. Test with different browser/incognito
147+
148+
### Performance Issues
149+
1. Monitor version check frequency
150+
2. Check localStorage usage
151+
3. Optimize version.json size if needed
152+
4. Consider CDN performance
153+
154+
## Migration from Old Embed Code
155+
156+
### Old Code (deprecated)
157+
```html
158+
<script src="https://cdn.jsdelivr.net/gh/StructuredLabs/buzzwald-client@latest/dist/buzzwald-widget.js"></script>
159+
```
160+
161+
### New Code (recommended)
162+
```html
163+
<script src="https://cdn.jsdelivr.net/gh/StructuredLabs/buzzwald-client@latest/dist/buzzwald.js"></script>
164+
```
165+
166+
### Migration Strategy
167+
1. Update documentation with new embed code
168+
2. Send notification to existing users
169+
3. Keep old method working for backward compatibility
170+
4. Monitor usage analytics
171+
5. Eventually deprecate old method
172+
173+
## Monitoring
174+
175+
### Key Metrics to Monitor
176+
- Version check success rate
177+
- Widget load time
178+
- Cache hit/miss ratios
179+
- Error rates by browser
180+
- User update lag time
181+
182+
### Analytics Implementation
183+
Consider adding analytics to track:
184+
- Loader script loads
185+
- Version check requests
186+
- Widget load success/failure
187+
- Cache effectiveness
188+
189+
## Security Considerations
190+
191+
### Version Endpoint Security
192+
- Version.json contains no sensitive information
193+
- Uses HTTPS for all requests
194+
- No user data in version checks
195+
196+
### Content Security Policy
197+
The loader script:
198+
- Doesn't use `eval()` or inline scripts
199+
- Fetches resources from same origin (JSDelivr)
200+
- Compatible with strict CSP policies
201+
202+
## Future Enhancements
203+
204+
### Possible Improvements
205+
1. **Delta Updates**: Only load changed parts of widget
206+
2. **Progressive Loading**: Load core features first, then enhancements
207+
3. **A/B Testing**: Load different widget versions for testing
208+
4. **Rollback Capability**: Quickly revert to previous version
209+
5. **Regional CDN**: Use geo-specific CDN endpoints

dist/buzzwald.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/version.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"version": "0.0.1",
3+
"commit": "3314c76",
4+
"timestamp": 1752740190610,
5+
"buildDate": "2025-07-17T08:16:30.610Z",
6+
"buildId": "0.0.1-3314c76-1752740190610",
7+
"cacheBuster": "v=0.0.1&t=1752740190610"
8+
}

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44
"version": "0.0.1",
55
"type": "module",
66
"description": "Buzzwald phone widget - embeddable voice call widget",
7-
"main": "dist/widget.iife.js",
7+
"main": "dist/buzzwald-widget.js",
88
"files": [
99
"dist/"
1010
],
1111
"scripts": {
1212
"dev": "vite",
13-
"build": "vite build",
13+
"build": "bun run build:clean && bun run build:widget && bun run build:loader",
14+
"build:clean": "rm -rf dist",
15+
"build:widget": "vite build",
16+
"build:loader": "BUILD_TARGET=loader vite build",
1417
"preview": "vite preview",
15-
"test": "echo 'Widget test page available at test.html'"
18+
"test": "echo 'Widget test page available at test.html'",
19+
"test:cache-busting": "vite --open test-cache-busting.html",
20+
"build:release": "bun run build && echo 'Build complete! Remember to create a GitHub release to deploy.'"
1621
},
1722
"keywords": [
1823
"widget",

scripts/build-version.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Build Version Generator
5+
* Creates a version.json file with current version and timestamp for cache busting
6+
*/
7+
8+
import fs from 'fs';
9+
import path from 'path';
10+
import { fileURLToPath } from 'url';
11+
12+
const __filename = fileURLToPath(import.meta.url);
13+
const __dirname = path.dirname(__filename);
14+
15+
// Get project root
16+
const projectRoot = path.resolve(__dirname, '..');
17+
const packageJsonPath = path.join(projectRoot, 'package.json');
18+
const distPath = path.join(projectRoot, 'dist');
19+
const versionFilePath = path.join(distPath, 'version.json');
20+
21+
/**
22+
* Get version from package.json
23+
*/
24+
function getPackageVersion() {
25+
try {
26+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
27+
return packageJson.version;
28+
} catch (error) {
29+
console.error('Failed to read package.json:', error);
30+
return '0.0.0';
31+
}
32+
}
33+
34+
/**
35+
* Get git commit hash if available
36+
*/
37+
function getGitCommit() {
38+
try {
39+
const { execSync } = require('child_process');
40+
const commit = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
41+
return commit;
42+
} catch (error) {
43+
console.warn('Could not get git commit hash:', error.message);
44+
return null;
45+
}
46+
}
47+
48+
/**
49+
* Generate version information
50+
*/
51+
function generateVersionInfo() {
52+
const version = getPackageVersion();
53+
const commit = getGitCommit();
54+
const timestamp = Date.now();
55+
const buildDate = new Date().toISOString();
56+
57+
return {
58+
version,
59+
commit,
60+
timestamp,
61+
buildDate,
62+
// Add build metadata
63+
buildId: `${version}-${commit || 'unknown'}-${timestamp}`,
64+
// Cache busting query params
65+
cacheBuster: `v=${version}&t=${timestamp}`
66+
};
67+
}
68+
69+
/**
70+
* Ensure dist directory exists
71+
*/
72+
function ensureDistDirectory() {
73+
if (!fs.existsSync(distPath)) {
74+
fs.mkdirSync(distPath, { recursive: true });
75+
}
76+
}
77+
78+
/**
79+
* Write version file
80+
*/
81+
function writeVersionFile(versionInfo) {
82+
ensureDistDirectory();
83+
84+
const versionJson = JSON.stringify(versionInfo, null, 2);
85+
fs.writeFileSync(versionFilePath, versionJson, 'utf8');
86+
87+
console.log('Version file generated:', versionFilePath);
88+
console.log('Version info:', versionInfo);
89+
}
90+
91+
/**
92+
* Main function
93+
*/
94+
function main() {
95+
try {
96+
const versionInfo = generateVersionInfo();
97+
writeVersionFile(versionInfo);
98+
99+
console.log('✅ Version file created successfully');
100+
} catch (error) {
101+
console.error('❌ Failed to generate version file:', error);
102+
process.exit(1);
103+
}
104+
}
105+
106+
// Run if called directly
107+
if (import.meta.url === `file://${process.argv[1]}`) {
108+
main();
109+
}
110+
111+
export { generateVersionInfo, writeVersionFile };

0 commit comments

Comments
 (0)