NetworkAPI
Make HTTP requests with permission-controlled network access. Access via context.network.
Overview
The NetworkAPI allows plugins to make HTTP requests to external APIs and services. Network access requires the network permission in your plugin manifest.
Key Features:
- Standard fetch API interface
- Permission-based access control
- Support for all HTTP methods
- Request/response headers
- JSON and binary data
Permission Required
Add to your package.json:
{
"lokus": {
"permissions": ["network"]
}
}Methods
fetch(url, options?)
Make an HTTP request using the Fetch API.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | Request URL |
| options | RequestInit | No | Fetch options |
Options:
interface RequestInit {
method?: string; // GET, POST, PUT, DELETE, etc.
headers?: HeadersInit; // Request headers
body?: BodyInit; // Request body
mode?: RequestMode; // cors, no-cors, same-origin
credentials?: RequestCredentials; // omit, same-origin, include
cache?: RequestCache; // default, no-cache, reload, etc.
redirect?: RequestRedirect; // follow, error, manual
referrer?: string; // Referrer URL
signal?: AbortSignal; // Abort controller signal
}Returns: Promise resolving to Response object.
Response Interface:
interface Response {
ok: boolean;
status: number;
statusText: string;
headers: Headers;
// Body methods
json(): Promise<any>;
text(): Promise<string>;
blob(): Promise<Blob>;
arrayBuffer(): Promise<ArrayBuffer>;
}Example:
// Simple GET request
const response = await context.network.fetch('https://api.example.com/data');
const data = await response.json();
context.logger.info('Data:', data);
// POST request with JSON
const response = await context.network.fetch('https://api.example.com/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'New Item',
value: 42
})
});
// Check response status
if (response.ok) {
const result = await response.json();
context.ui.showInformationMessage('Success!');
} else {
context.ui.showErrorMessage(`Error: $\\{response.status\\}`);
}Complete Example
export default class APIPlugin {
private context: PluginContext;
private apiKey: string;
private baseUrl: string;
constructor(context: PluginContext) {
this.context = context;
this.apiKey = '';
this.baseUrl = 'https://api.example.com';
}
async activate(): Promise<void> {
// Load API key from storage
this.apiKey = await context.storage.get('apiKey') ?? '';
// Register commands
context.commands.register([
{
id: 'myPlugin.fetchData',
name: 'Fetch Data',
execute: () => this.fetchData()
},
{
id: 'myPlugin.postData',
name: 'Post Data',
execute: () => this.postData()
},
{
id: 'myPlugin.setApiKey',
name: 'Set API Key',
execute: () => this.setApiKey()
}
]);
}
async fetchData(): Promise<void> {
if (!this.apiKey) {
context.ui.showErrorMessage('API key not set');
return;
}
try {
const response = await context.network.fetch(`${this.baseUrl}/data`, {
headers: {
'Authorization': `Bearer $\\{this.apiKey\\}`
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: $\\{response.statusText\\}`);
}
const data = await response.json();
context.logger.info('Fetched data:', data);
context.ui.showInformationMessage('Data fetched successfully');
} catch (error) {
context.logger.error('Fetch failed:', error);
context.ui.showErrorMessage(`Failed: $\\{error.message\\}`);
}
}
async postData(): Promise<void> {
if (!this.apiKey) {
context.ui.showErrorMessage('API key not set');
return;
}
try {
const response = await context.network.fetch(`${this.baseUrl}/items`, {
method: 'POST',
headers: {
'Authorization': `Bearer $\\{this.apiKey\\}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: 'New Item',
timestamp: Date.now()
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: $\\{response.statusText\\}`);
}
const result = await response.json();
context.logger.info('Created item:', result);
context.ui.showInformationMessage(`Created: $\\{result.id\\}`);
} catch (error) {
context.logger.error('Post failed:', error);
context.ui.showErrorMessage(`Failed: $\\{error.message\\}`);
}
}
async setApiKey(): Promise<void> {
const key = await context.ui.showInputBox({
prompt: 'Enter API key',
placeholder: 'Your API key'
});
if (key) {
this.apiKey = key;
await context.storage.set('apiKey', key);
context.ui.showInformationMessage('API key saved');
}
}
async deactivate(): Promise<void> {
// Cleanup
}
}Advanced Example: API Client
export default class APIClientPlugin {
private context: PluginContext;
private client: APIClient;
constructor(context: PluginContext) {
this.context = context;
this.client = new APIClient(context);
}
async activate(): Promise<void> {
await this.client.initialize();
context.commands.register([
{
id: 'myPlugin.syncData',
name: 'Sync Data',
execute: () => this.syncData()
}
]);
}
async syncData(): Promise<void> {
await context.ui.withProgress(
{ title: 'Syncing data...', location: 'notification' },
async (progress, token) => {
try {
// Fetch remote data
progress.report({ message: 'Fetching remote data...' });
const remoteData = await this.client.getAll();
// Get local data
progress.report({ message: 'Loading local data...' });
const localData = await context.storage.get('data') ?? [];
// Merge
progress.report({ message: 'Merging data...' });
const merged = this.mergeData(localData, remoteData);
// Save locally
await context.storage.set('data', merged);
// Push changes
progress.report({ message: 'Pushing changes...' });
await this.client.updateBatch(merged);
context.ui.showInformationMessage('Sync complete');
} catch (error) {
context.ui.showErrorMessage(`Sync failed: $\\{error.message\\}`);
}
}
);
}
mergeData(local: any[], remote: any[]): any[] {
// Merge logic
return [...local, ...remote];
}
async deactivate(): Promise<void> {
await this.client.cleanup();
}
}
class APIClient {
private context: PluginContext;
private baseUrl: string;
private token?: string;
constructor(context: PluginContext) {
this.context = context;
this.baseUrl = 'https://api.example.com';
}
async initialize(): Promise<void> {
// Load auth token
this.token = await this.context.storage.get('auth_token');
// Authenticate if needed
if (!this.token) {
await this.authenticate();
}
}
async authenticate(): Promise<void> {
const response = await this.context.network.fetch(`${this.baseUrl}/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'user',
password: 'pass'
})
});
const { token } = await response.json();
this.token = token;
await this.context.storage.set('auth_token', token);
}
async request(endpoint: string, options: RequestInit = {}): Promise<any> {
const response = await this.context.network.fetch(`${this.baseUrl}$\\{endpoint\\}`, {
...options,
headers: {
'Authorization': `Bearer $\\{this.token\\}`,
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
if (response.status === 401) {
// Token expired, re-authenticate
await this.authenticate();
return this.request(endpoint, options);
}
throw new Error(`HTTP ${response.status}: $\\{response.statusText\\}`);
}
return response.json();
}
async getAll(): Promise<any[]> {
return this.request('/items');
}
async getById(id: string): Promise<any> {
return this.request(`/items/$\\{id\\}`);
}
async create(data: any): Promise<any> {
return this.request('/items', {
method: 'POST',
body: JSON.stringify(data)
});
}
async update(id: string, data: any): Promise<any> {
return this.request(`/items/$\\{id\\}`, {
method: 'PUT',
body: JSON.stringify(data)
});
}
async delete(id: string): Promise<void> {
await this.request(`/items/$\\{id\\}`, {
method: 'DELETE'
});
}
async updateBatch(items: any[]): Promise<void> {
await this.request('/items/batch', {
method: 'PUT',
body: JSON.stringify({ items })
});
}
async cleanup(): Promise<void> {
// Cleanup if needed
}
}Error Handling
try {
const response = await context.network.fetch(url);
// Check HTTP status
if (!response.ok) {
throw new Error(`HTTP ${response.status}: $\\{response.statusText\\}`);
}
const data = await response.json();
} catch (error) {
if (error.name === 'TypeError') {
// Network error (no connection, DNS failure, etc.)
context.ui.showErrorMessage('Network error: Check connection');
} else if (error.name === 'AbortError') {
// Request was aborted
context.ui.showWarningMessage('Request cancelled');
} else {
// Other errors
context.ui.showErrorMessage(`Error: $\\{error.message\\}`);
}
}Request Cancellation
// Create abort controller
const controller = new AbortController();
// Start request
const fetchPromise = context.network.fetch(url, {
signal: controller.signal
});
// Cancel after 5 seconds
setTimeout(() => {
controller.abort();
}, 5000);
try {
const response = await fetchPromise;
} catch (error) {
if (error.name === 'AbortError') {
context.logger.info('Request cancelled');
}
}Best Practices
-
Check Permissions: Verify network permission in manifest
{ "permissions": ["network"] } -
Handle Errors: Always wrap in try-catch
try { const response = await context.network.fetch(url); } catch (error) { context.logger.error('Request failed:', error); } -
Check Status Codes: Verify response.ok
if (!response.ok) { throw new Error(`HTTP $\\{response.status\\}`); } -
Use Timeouts: Prevent hanging requests
const controller = new AbortController(); setTimeout(() => controller.abort(), 10000); fetch(url, { signal: controller.signal }); -
Cache Responses: Store frequently accessed data
const cached = await context.storage.get('cache:data'); if (cached && Date.now() - cached.timestamp < 3600000) { return cached.data; } const data = await fetchFromAPI(); await context.storage.set('cache:data', { data, timestamp: Date.now() }); -
Secure Credentials: Store tokens safely
// Store in storage, not in code const token = await context.storage.get('api_token');
Security Notes
- Permission Required: Network access requires explicit permission
- CORS Applies: Browser CORS rules apply to requests
- HTTPS Recommended: Use HTTPS for sensitive data
- No Credentials in Code: Store API keys in storage
See Also
- StorageAPI Reference - Store API credentials
- UIAPI Reference - Progress indicators