Widget Development Guide
This guide explains how to build third-party JavaScript widgets for TimeLens v1.1.0.
Scope (v1.1.0)
Supported:
- Local directory loading (
manifest.json+ JS entry) - Runtime registry discovery from app data
widgetsdirectory - Render contract:
createWidget()ormount()export - Read-focused data APIs (screen time, active window, etc.)
- Permission-based access control
Not included yet:
- Remote marketplace / cloud distribution
- Signature verification
- Auto-update mechanism
Directory Layout
Each third-party widget lives in its own folder under the app data widgets directory:
%APPDATA%\com.timelens.app\widgets\
my-widget/
manifest.json
index.js
assets/
icon.png
styles.css
manifest.json
The manifest describes your widget to the TimeLens registry.
{
"widget_type": "sample_hello",
"name": "Sample Hello Widget",
"description": "Minimal third-party widget example",
"version": "1.0.0",
"author": "Your Name",
"entry": "index.js",
"default_size": {
"width": 360,
"height": 240
},
"permissions": [
"screen-time:read",
"active-window:subscribe"
]
}
Required Fields
| Field | Type | Description |
|---|---|---|
widget_type | string | Unique identifier ([a-zA-Z0-9_-]+) |
name | string | Display name in widget center |
entry | string | Entry JS file path (relative to manifest folder) |
Optional Fields
| Field | Type | Description |
|---|---|---|
description | string | Short description |
version | string | Semver version |
author | string | Author name or email |
icon | string | Icon path (relative) |
default_size.width | number | Default window width |
default_size.height | number | Default window height |
permissions | string[] | Required permissions (see below) |
Permission System
Third-party widgets run in a sandboxed iframe. They must declare required permissions in the manifest. Users are prompted to grant them when the widget is first created.
| Permission | Access |
|---|---|
screen-time:read | Read screen time statistics (today, hourly, trends) |
active-window:subscribe | Subscribe to active window change events |
goals:read | Read usage goals and progress |
focus:read | Read focus session status |
widgets:read | Read widget registry information |
Render Interface Contract
TimeLens loads your entry as an ESM module. Two export patterns are supported:
Pattern A — createWidget() factory
export function createWidget() {
let root;
return {
mount(container, context) {
root = document.createElement("div");
root.textContent = `Hello from ${context.widgetType}`;
container.appendChild(root);
},
unmount() {
if (root && root.parentNode) {
root.parentNode.removeChild(root);
}
},
};
}
Pattern B — direct mount() export
let root;
export function mount(container, context) {
root = document.createElement("div");
root.innerHTML = `
`;
container.appendChild(root);
}
export function unmount() {
if (root && root.parentNode) {
root.parentNode.removeChild(root);
}
}
Context Object
| Property | Type | Description |
|---|---|---|
widgetType | string | Widget type identifier |
displayName | string | Human-readable name |
widgetId | string | Unique instance ID |
permissions | string[] | Granted permissions |
Data Channel API
Widgets communicate with TimeLens core via the window.parent.postMessage API. The parent window provides a request/response bridge.
Request Format
window.parent.postMessage({
type: "timelens:request",
id: "req-123", // unique request ID
method: "getTodayAppTotals",
params: {}
}, "*");
Response Format
window.addEventListener("message", (e) => {
if (e.data.type === "timelens:response" && e.data.id === "req-123") {
console.log(e.data.result); // success
console.log(e.data.error); // if failed
}
});
Available Methods
All methods from API Reference with read permissions are available via the data channel. Common methods:
getTodayAppTotals— Today's app usage summarygetTodayHourly— Hourly distribution for todaygetRecentDailyTotals— Daily totals for last N daysgetMonitorStatus— Current monitoring statusgetGoalProgress— Goal progress list
Styling Guidelines
Widgets run inside an iframe with a transparent background. Follow these guidelines for visual consistency:
- Use
background: rgba(22, 27, 34, 0.8)withbackdrop-filter: blur(10px)for glassmorphism. - Primary text color:
#e6edf3 - Secondary text color:
#8b949e - Accent color:
#58a6ff - Border radius:
8pxor12px - Font family:
Inter, system-ui, sans-serif
Complete Example
See examples/third-party-widget-template/ for a working starter template.
// index.js
export function createWidget() {
let root;
let interval;
return {
mount(container, context) {
root = document.createElement("div");
root.style.cssText = `
padding: 16px;
color: #e6edf3;
font-family: Inter, system-ui, sans-serif;
`;
root.innerHTML = `${context.displayName}
Loading...`;
container.appendChild(root);
// Fetch data every 30s
const fetchData = async () => {
const reqId = "req-" + Date.now();
window.parent.postMessage({
type: "timelens:request",
id: reqId,
method: "getTodayAppTotals",
params: {}
}, "*");
};
const handler = (e) => {
if (e.data.type === "timelens:response") {
const list = e.data.result?.slice(0, 3) || [];
const html = list.map(s =>
`${s.app_name}: ${Math.round(s.total_seconds / 60)}m`
).join("");
const el = root.querySelector("#stats");
if (el) el.innerHTML = html || "No data";
}
};
window.addEventListener("message", handler);
fetchData();
interval = setInterval(fetchData, 30000);
},
unmount() {
clearInterval(interval);
if (root && root.parentNode) {
root.parentNode.removeChild(root);
}
}
};
}
Debugging Tips
- Open DevTools on the widget window (right-click → Inspect) to debug JavaScript.
- Check
Widget Center→ permissions tab if data requests fail. - Verify
manifest.jsonsyntax with any JSON validator. - Widget logs appear in the main window DevTools console when using
postMessage.
最后更新:2025-05-03 · TimeLens v1.1.0