diff --git a/API-Reference.md b/API-Reference.md new file mode 100644 index 0000000..0a78639 --- /dev/null +++ b/API-Reference.md @@ -0,0 +1,622 @@ +# API Reference + +Complete JavaScript API reference for Roster preprocessing and postprocessing scripts. + +## Overview + +Roster provides a JavaScript API accessible from [[Scripts]] for automating request modifications and response processing. + +**Language:** JavaScript (ES5+) +**Runtime:** GJS (GNOME JavaScript) +**Context:** Sandboxed, synchronous execution + +## Global Objects + +### Preprocessing Script Context + +Available in preprocessing scripts only: + +```javascript +request // Modifiable request object +roster // Roster API (with getVariable) +console // Console output +``` + +### Postprocessing Script Context + +Available in postprocessing scripts only: + +```javascript +response // Read-only response object +roster // Roster API (without getVariable) +console // Console output +``` + +## Request Object + +**Available in:** Preprocessing scripts only +**Type:** Mutable + +### Properties + +#### `request.method` + +**Type:** `string` +**Values:** `"GET"`, `"POST"`, `"PUT"`, `"DELETE"` +**Description:** HTTP method for the request + +**Example:** +```javascript +// Change method from GET to POST +request.method = "POST"; +console.log('Method changed to:', request.method); +``` + +#### `request.url` + +**Type:** `string` +**Description:** Complete URL for the request + +**Example:** +```javascript +// Add query parameter +request.url = request.url + '?timestamp=' + Date.now(); + +// Replace placeholder +const userId = roster.getVariable('user_id'); +request.url = request.url.replace('{userId}', userId); + +// Change domain +request.url = request.url.replace('localhost', 'api.example.com'); +``` + +#### `request.headers` + +**Type:** `object` +**Description:** HTTP headers as key-value pairs + +**Example:** +```javascript +// Add header +request.headers['Authorization'] = 'Bearer ' + token; + +// Modify existing header +request.headers['Content-Type'] = 'application/json'; + +// Delete header +delete request.headers['X-Old-Header']; + +// Read header +const contentType = request.headers['Content-Type']; +``` + +#### `request.body` + +**Type:** `string` +**Description:** Request body content + +**Example:** +```javascript +// Set body +request.body = JSON.stringify({ name: "John", age: 30 }); + +// Modify existing body +const data = JSON.parse(request.body); +data.timestamp = new Date().toISOString(); +request.body = JSON.stringify(data); + +// Clear body +request.body = ""; +``` + +## Response Object + +**Available in:** Postprocessing scripts only +**Type:** Immutable (read-only) + +### Properties + +#### `response.body` + +**Type:** `string` +**Description:** Response body content + +**Example:** +```javascript +// Parse JSON response +const data = JSON.parse(response.body); +console.log('User name:', data.name); + +// Check if response contains text +if (response.body.includes('error')) { + console.error('Response contains error'); +} + +// Get response length +console.log('Response size:', response.body.length, 'bytes'); +``` + +#### `response.headers` + +**Type:** `object` +**Description:** Response headers as key-value pairs (read-only) + +**Example:** +```javascript +// Read header +const contentType = response.headers['Content-Type']; +console.log('Content type:', contentType); + +// Check if header exists +if (response.headers['X-Rate-Limit-Remaining']) { + const remaining = response.headers['X-Rate-Limit-Remaining']; + console.log('Rate limit remaining:', remaining); +} + +// List all headers +for (const key in response.headers) { + console.log(key + ':', response.headers[key]); +} +``` + +#### `response.statusCode` + +**Type:** `number` +**Description:** HTTP status code (200, 404, 500, etc.) + +**Example:** +```javascript +// Check if successful +if (response.statusCode === 200) { + console.log('Success!'); +} else if (response.statusCode >= 400 && response.statusCode < 500) { + console.error('Client error:', response.statusCode); +} else if (response.statusCode >= 500) { + console.error('Server error:', response.statusCode); +} + +// Handle specific status codes +switch (response.statusCode) { + case 200: + console.log('OK'); + break; + case 201: + console.log('Created'); + break; + case 401: + console.error('Unauthorized'); + break; + case 404: + console.error('Not found'); + break; + default: + console.log('Status:', response.statusCode); +} +``` + +#### `response.statusText` + +**Type:** `string` +**Description:** HTTP status text ("OK", "Not Found", etc.) + +**Example:** +```javascript +console.log('Response:', response.statusCode, response.statusText); +// Output: "Response: 200 OK" + +if (response.statusText === "OK") { + // Process successful response +} +``` + +#### `response.responseTime` + +**Type:** `number` +**Description:** Response time in milliseconds + +**Example:** +```javascript +console.log('Response took', response.responseTime, 'ms'); + +if (response.responseTime > 1000) { + console.warn('Slow response:', response.responseTime, 'ms'); +} + +// Store for monitoring +roster.setVariable('last_response_time', response.responseTime.toString()); +``` + +## Roster API + +**Available in:** Both preprocessing and postprocessing scripts +**Namespace:** `roster` + +### Methods + +#### `roster.getVariable(name)` + +**Available in:** Preprocessing scripts only +**Returns:** `string` or `undefined` +**Description:** Get variable value from selected environment + +**Parameters:** +- `name` (string): Variable name + +**Example:** +```javascript +// Get variable +const apiKey = roster.getVariable('api_key'); +if (apiKey) { + request.headers['X-API-Key'] = apiKey; +} else { + console.error('API key not defined'); +} + +// Get with default value +const timeout = roster.getVariable('timeout') || '30'; +console.log('Using timeout:', timeout); +``` + +**Important Notes:** +- Returns `undefined` if variable doesn't exist +- Gets value from currently selected environment +- Can access both regular and [[Sensitive-Variables|sensitive variables]] +- Sensitive variable values are automatically decrypted from keyring + +#### `roster.setVariable(name, value)` + +**Available in:** Both preprocessing and postprocessing scripts +**Returns:** `undefined` +**Description:** Set or update variable in selected environment + +**Parameters:** +- `name` (string): Variable name (must match `/^\w+$/`) +- `value` (string): Variable value + +**Example:** +```javascript +// Set variable +roster.setVariable('user_id', '12345'); +console.log('Saved user ID'); + +// Update existing variable +const token = data.access_token; +roster.setVariable('auth_token', token); + +// Create new variable (auto-created if doesn't exist) +roster.setVariable('session_id', generateSessionId()); +``` + +**Important Notes:** +- Creates variable if it doesn't exist +- Updates value in currently selected environment +- Variable name must be alphanumeric + underscore +- If variable is marked as sensitive, value goes to keyring automatically +- Value is converted to string + +#### `roster.setVariables(object)` + +**Available in:** Both preprocessing and postprocessing scripts +**Returns:** `undefined` +**Description:** Set or update multiple variables at once (batch operation) + +**Parameters:** +- `object` (object): Key-value pairs of variable names and values + +**Example:** +```javascript +// Set multiple variables +roster.setVariables({ + user_id: data.user.id, + user_name: data.user.name, + user_email: data.user.email, + session_id: data.session_id +}); + +// Extract response data +const data = JSON.parse(response.body); +roster.setVariables({ + access_token: data.access_token, + refresh_token: data.refresh_token, + expires_at: data.expires_at +}); +console.log('Saved authentication tokens'); +``` + +**Important Notes:** +- All variables updated in single operation +- More efficient than multiple `setVariable()` calls +- Same validation rules as `setVariable()` +- Sensitive variables automatically routed to keyring + +### Properties + +#### `roster.project.name` + +**Available in:** Preprocessing scripts only +**Type:** `string` +**Description:** Current project name + +**Example:** +```javascript +const projectName = roster.project.name; +console.log('Project:', projectName); + +// Add project name to request +request.headers['X-Project'] = projectName; +``` + +#### `roster.project.environments` + +**Available in:** Preprocessing scripts only +**Type:** `string[]` (array of strings) +**Description:** Array of environment names in current project + +**Example:** +```javascript +const envs = roster.project.environments; +console.log('Available environments:', envs.join(', ')); +// Output: "Available environments: Production, Staging, Development" + +// Check if environment exists +if (envs.includes('Production')) { + console.log('Production environment available'); +} + +// Log count +console.log('Environment count:', envs.length); +``` + +## Console API + +**Available in:** Both preprocessing and postprocessing scripts +**Namespace:** `console` + +### Methods + +#### `console.log(...args)` + +**Returns:** `undefined` +**Description:** Print message to script output + +**Example:** +```javascript +console.log('Simple message'); +console.log('User ID:', userId); +console.log('Request sent to', request.url); +console.log('Response status:', response.statusCode, response.statusText); + +// Multiple arguments +const name = 'John'; +const age = 30; +console.log('User:', name, 'Age:', age); +``` + +**Output location:** +- Preprocessing: "Preprocessing" tab in request panel +- Postprocessing: "Postprocessing" tab in request panel + +#### `console.error(...args)` + +**Returns:** `undefined` +**Description:** Print error message to script output + +**Example:** +```javascript +if (response.statusCode !== 200) { + console.error('Request failed with status:', response.statusCode); +} + +if (!data.access_token) { + console.error('No access token in response'); +} + +try { + const data = JSON.parse(response.body); +} catch (e) { + console.error('JSON parse error:', e.message); +} +``` + +**Visual difference:** +- May be styled differently than `console.log()` in output +- Indicates errors or warnings + +## Built-in JavaScript Objects + +Standard JavaScript objects available in GJS: + +### Date + +```javascript +// Current timestamp +const now = new Date(); +console.log('Current time:', now.toISOString()); + +// Unix timestamp +const timestamp = Date.now(); +console.log('Timestamp:', timestamp); + +// Date arithmetic +const expiresAt = new Date(Date.now() + 3600000); // +1 hour +roster.setVariable('token_expires', expiresAt.toISOString()); +``` + +### JSON + +```javascript +// Parse JSON +const data = JSON.parse(response.body); +console.log('Parsed data:', data.name); + +// Stringify JSON +const body = { name: "John", age: 30 }; +request.body = JSON.stringify(body); + +// Pretty print +request.body = JSON.stringify(body, null, 2); +``` + +### Math + +```javascript +// Random number +const requestId = Math.floor(Math.random() * 1000000); +roster.setVariable('request_id', requestId.toString()); + +// Rounding +const responseTimeSeconds = Math.round(response.responseTime / 1000); +console.log('Response time:', responseTimeSeconds, 'seconds'); +``` + +### String + +```javascript +// String manipulation +const url = request.url.replace('http://', 'https://'); +const uppercase = data.name.toUpperCase(); +const trimmed = data.description.trim(); + +// String methods +if (response.body.includes('error')) { + console.error('Response contains error'); +} + +const parts = request.url.split('/'); +const lastPart = parts[parts.length - 1]; +``` + +### Array + +```javascript +// Array methods +const data = JSON.parse(response.body); +const itemIds = data.items.map(item => item.id); +console.log('Item IDs:', itemIds.join(', ')); + +// Filter +const active = data.items.filter(item => item.active); +console.log('Active items:', active.length); + +// Find +const firstItem = data.items.find(item => item.id === '123'); +if (firstItem) { + console.log('Found item:', firstItem.name); +} +``` + +### Object + +```javascript +// Object methods +const headers = Object.keys(response.headers); +console.log('Header count:', headers.length); + +// Merge objects +const defaults = { timeout: 30, retries: 3 }; +const config = Object.assign({}, defaults, userConfig); + +// Check property +if (data.hasOwnProperty('access_token')) { + roster.setVariable('auth_token', data.access_token); +} +``` + +## Error Handling + +Scripts should handle errors gracefully: + +```javascript +// Try-catch for parsing +try { + const data = JSON.parse(response.body); + roster.setVariable('user_id', data.user.id); +} catch (e) { + console.error('Failed to parse JSON:', e.message); + console.error('Response body:', response.body); +} + +// Check before accessing +if (data && data.user && data.user.id) { + roster.setVariable('user_id', data.user.id); +} else { + console.error('Invalid response structure'); +} + +// Validate response status +if (response.statusCode === 200) { + const data = JSON.parse(response.body); + // Process data +} else { + console.error('Request failed:', response.statusCode); +} +``` + +## Limitations + +### No Asynchronous Operations + +- No `setTimeout` / `setInterval` +- No `Promise` / `async` / `await` +- No `fetch()` / `XMLHttpRequest` +- Scripts execute synchronously + +### No External Libraries + +- Cannot import npm packages +- Cannot `require()` external modules +- Only built-in JavaScript objects available + +### No File System Access + +- Cannot read/write files +- Cannot execute shell commands +- Cannot access environment variables (use Roster variables instead) + +### No Network Access + +- Cannot make additional HTTP requests +- Use Roster's request system instead + +## Type Reference + +### Variable Name Pattern + +Variable names must match: `/^\w+$/` + +Valid: +- `api_key` +- `user_id` +- `access_token` +- `BASE_URL` +- `token123` + +Invalid: +- `api-key` (hyphen not allowed) +- `api.key` (dot not allowed) +- `api key` (space not allowed) +- `@token` (special character) + +### Type Conversions + +Variables are always stored as strings: + +```javascript +// Numbers must be converted +roster.setVariable('count', data.count.toString()); + +// Read as number +const count = parseInt(roster.getVariable('count'), 10); + +// Booleans must be converted +roster.setVariable('is_active', data.active.toString()); + +// Read as boolean +const isActive = roster.getVariable('is_active') === 'true'; +``` + +## Next Steps + +- [[Scripts]] - Practical examples and workflows +- [[Variables]] - Learn about environment variables +- [[Sensitive-Variables]] - Secure credential storage diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..7fbd8f4 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,260 @@ +# Frequently Asked Questions + +## General + +### What is Roster? + +Roster is a native GNOME HTTP client for testing and debugging APIs. It provides a clean interface for sending HTTP requests, viewing responses, and automating workflows. + +### Is Roster free? + +Yes! Roster is free and open-source software licensed under GPL-3.0-or-later. + +### What platforms does Roster support? + +Roster is designed for the GNOME desktop environment on Linux. It works on any Linux distribution with GNOME 40+. + +### Can I use Roster on Windows or macOS? + +Roster is specifically built for GNOME/Linux and is not currently available for Windows or macOS. + +## Installation + +### How do I install Roster? + +See [[Installation]] for complete instructions. You can: +- Build from source +- Install via Flatpak +- Use GNOME Builder + +### Will Roster be on Flathub? + +Support for Flathub is planned for future releases. + +### What dependencies does Roster need? + +Runtime: GTK 4, libadwaita 1, Python 3, libsoup3, libsecret, GJS + +See [[Installation]] for complete dependency list. + +## Variables and Environments + +### What's the difference between regular and sensitive variables? + +**Regular variables** are stored in plain text JSON files. Use for non-sensitive data like URLs. + +**Sensitive variables** are encrypted and stored in GNOME Keyring. Use for API keys, passwords, and tokens. + +See [[Sensitive-Variables]] for details. + +### Can I use variables in the request body? + +Yes! Use `{{variable_name}}` syntax in: +- URL +- Headers +- Request body + +### How do I switch between environments? + +Use the environment dropdown at the top of the request panel. Switching environments updates all variable values. + +### Can variables from one project be used in another? + +No. Variables are scoped to projects and cannot be shared between projects. + +## Security + +### Is it safe to store API keys in Roster? + +**If you use sensitive variables: Yes.** + +Mark variables as sensitive (click the lock icon), and values are encrypted in GNOME Keyring. + +**If you use regular variables: No.** + +Regular variables are stored in plain text. Always mark secrets as sensitive! + +### Where are my secrets stored? + +Sensitive variables are stored in GNOME Keyring at: +- Native: `~/.local/share/keyrings/` +- Encrypted with your login password +- Same security as browser passwords, WiFi credentials + +### Can I view my keyring secrets? + +Yes! Open "Passwords and Keys" application (Seahorse), navigate to "Login" keyring, and look for entries starting with "Roster:". + +### What happens if I forget to mark a variable as sensitive? + +You can click the lock icon at any time to mark it as sensitive. Values will be moved from JSON to keyring automatically. + +## Scripts + +### What language do scripts use? + +JavaScript (ES5+), executed via GJS (GNOME JavaScript runtime). + +### Can I use npm packages in scripts? + +No. Scripts run in a sandboxed environment without access to external libraries or npm packages. + +### Can scripts access the file system? + +No. Scripts cannot read/write files, execute shell commands, or access system resources. + +### Why can't I use async/await? + +Scripts execute synchronously. No async operations (setTimeout, Promises, async/await, fetch) are available. + +### Can I make HTTP requests from scripts? + +No. Use Roster's request system and chain requests using variables instead. + +See [[Scripts]] for complete documentation. + +## Requests and Responses + +### Where is request history stored? + +**Native:** `~/.local/share/cz.bugsy.roster/session.json` +**Flatpak:** `~/.var/app/cz.bugsy.roster/data/cz.bugsy.roster/session.json` + +### How long is history kept? + +History persists across sessions until you clear it manually. + +### Can I export requests? + +Yes! Click the export button and choose a format: +- cURL +- More formats may be added in future versions + +### Does Roster support file uploads? + +Multipart form uploads are not currently supported. Planned for future releases. + +### Can I test WebSocket connections? + +WebSocket support is not currently available. + +## Troubleshooting + +### Roster won't start + +**Check dependencies:** +```bash +# Verify GTK 4 and libadwaita are installed +pkg-config --modversion gtk4 libadwaita-1 +``` + +**Check logs:** +```bash +# Native +roster + +# Flatpak +flatpak run cz.bugsy.roster +``` + +### Cannot access GNOME Keyring + +**Ensure keyring is unlocked:** +1. Open "Passwords and Keys" (Seahorse) +2. Right-click "Login" keyring +3. Select "Unlock" + +**For Flatpak, check permissions:** +```bash +flatpak info --show-permissions cz.bugsy.roster +# Should include: --talk-name=org.freedesktop.secrets +``` + +### Variables not substituting + +**Check:** +1. Environment is selected (dropdown at top of request) +2. Variable names match exactly (case-sensitive) +3. Syntax is correct: `{{variable_name}}` +4. Variable is defined in selected environment + +### Scripts not executing + +**Check:** +1. Request is saved (scripts only work on saved requests) +2. Script has no syntax errors +3. Check script output tabs for errors +4. GJS (gjs package) is installed + +### Request failing with SSL errors + +**For self-signed certificates:** + +Roster uses system SSL certificates. Add your certificate to the system trust store or use development environments without SSL validation. + +## Data and Privacy + +### Does Roster collect any data? + +No. Roster does not collect, transmit, or share any data. All information stays on your local machine. + +### Can I backup my projects? + +Yes! Backup these files: +- `~/.local/share/cz.bugsy.roster/requests.json` (projects and variables) +- `~/.local/share/keyrings/` (sensitive variables) + +### How do I reset Roster? + +Delete the data directory: +```bash +# Native +rm -rf ~/.local/share/cz.bugsy.roster/ + +# Flatpak +rm -rf ~/.var/app/cz.bugsy.roster/data/cz.bugsy.roster/ +``` + +**Warning:** This deletes all projects, requests, and history! + +Sensitive variables in keyring must be deleted separately via Seahorse. + +## Contributing + +### How can I contribute? + +Contributions welcome! See [[Contributing]] for guidelines. + +### Where do I report bugs? + +Report issues at: https://git.bugsy.cz/beval/roster/issues + +### Can I request features? + +Yes! Open an issue with the "enhancement" label. + +## Comparison + +### How is Roster different from Postman? + +- **Roster**: Native GNOME app, lightweight, open-source, Linux-only +- **Postman**: Cross-platform, Electron-based, cloud sync, team features + +Roster focuses on being a simple, fast, native GNOME experience. + +### How is Roster different from Insomnia? + +Similar to Postman comparison. Roster is native GNOME with no cloud dependencies. + +### How is Roster different from HTTPie? + +- **Roster**: GUI application with project management +- **HTTPie**: Command-line tool + +Roster provides a visual interface while HTTPie is CLI-focused. + +## Still Have Questions? + +- Check the [[Home|Wiki]] for more documentation +- Open an issue: https://git.bugsy.cz/beval/roster/issues +- Read the source: https://git.bugsy.cz/beval/roster diff --git a/Getting-Started.md b/Getting-Started.md new file mode 100644 index 0000000..eee00ca --- /dev/null +++ b/Getting-Started.md @@ -0,0 +1,245 @@ +# Getting Started + +This guide walks you through sending your first HTTP request with Roster. + +## Launch Roster + +After [[Installation]], launch Roster from: +- Your application menu (search for "Roster") +- Command line: `roster` (or `flatpak run cz.bugsy.roster` for Flatpak) + +## Your First Request + +### Step 1: Create a Project + +Projects help organize related requests (e.g., "GitHub API", "My App API"). + +1. Click the **hamburger menu** (three lines) in the top-left +2. Select **"New Project"** +3. Enter a name: `My First Project` +4. Click **"Create"** + +### Step 2: Create a Request + +1. Click the **"+"** button in the header bar +2. Or: **Ctrl+T** keyboard shortcut + +A new request tab appears with default values. + +### Step 3: Configure the Request + +**Set the URL:** +``` +https://api.github.com/users/octocat +``` + +**Set the Method:** +- Use the dropdown menu +- Default is `GET` (perfect for this example) + +**Add Headers (Optional):** +1. Click the **"Headers"** tab +2. Add a header: + - Key: `User-Agent` + - Value: `Roster/0.5.0` + +### Step 4: Send the Request + +Click the **"Send"** button (or press **Ctrl+Return**) + +### Step 5: View the Response + +The response appears in the right panel: + +**Status Line:** +``` +200 OK (123 ms, 1.2 KB) +``` + +**Response Headers:** +Click the **"Headers"** dropdown to view all response headers. + +**Response Body:** +The JSON response from GitHub API showing user information. + +## Saving Requests + +### Save for Later + +1. Click the **"Save"** button in the header +2. Enter a name: `Get GitHub User` +3. Click **"Save"** + +The request is now saved to your project. + +### Access Saved Requests + +1. Click the **hamburger menu** +2. Your project shows saved requests +3. Click a request to open it in a new tab + +## Request History + +Every request you send is automatically saved to history. + +**View History:** +1. Click the **"History"** button in the header bar +2. Browse past requests +3. Click any entry to open it in a new tab + +**History includes:** +- Request details (URL, method, headers, body) +- Response details (status, headers, body, timing) +- Timestamp + +## Working with Tabs + +### Multiple Tabs + +Open multiple requests simultaneously: +- **Ctrl+T** - New request tab +- **Ctrl+W** - Close current tab +- **Ctrl+Tab** / **Ctrl+Shift+Tab** - Switch between tabs + +### Modified Indicator + +Unsaved changes are marked with a dot (•) on the tab label. + +## Next Example: POST Request + +Let's send some data to an API. + +### Step 1: New Request + +Create a new request tab (**Ctrl+T**) + +### Step 2: Configure POST Request + +**URL:** +``` +https://httpbin.org/post +``` + +**Method:** +``` +POST +``` + +**Headers:** +``` +Content-Type: application/json +``` + +**Body:** +1. Click the **"Body"** tab +2. Select **"JSON"** syntax highlighting +3. Enter JSON data: +```json +{ + "name": "Test User", + "email": "test@example.com" +} +``` + +### Step 3: Send and View + +Click **"Send"** and view the response. httpbin.org echoes back your request data. + +## Common Request Types + +### GET Request +``` +Method: GET +URL: https://api.example.com/users +Headers: (optional) +Body: (none) +``` + +### POST Request +``` +Method: POST +URL: https://api.example.com/users +Headers: Content-Type: application/json +Body: {"name": "John", "email": "john@example.com"} +``` + +### PUT Request +``` +Method: PUT +URL: https://api.example.com/users/123 +Headers: Content-Type: application/json +Body: {"name": "John Updated"} +``` + +### DELETE Request +``` +Method: DELETE +URL: https://api.example.com/users/123 +Headers: (optional) +Body: (none) +``` + +## Response Features + +### Syntax Highlighting + +Roster automatically detects and highlights: +- **JSON** responses +- **XML** responses +- **HTML** responses + +### Response Timing + +View how long the request took: +``` +200 OK (1.23 seconds, 5.4 KB) +``` + +### Response Size + +The status line shows the total response size including headers. + +## Keyboard Shortcuts + +Essential shortcuts to speed up your workflow: + +| Action | Shortcut | +|--------|----------| +| New request | **Ctrl+T** | +| Send request | **Ctrl+Return** | +| Close tab | **Ctrl+W** | +| Save request | **Ctrl+S** | +| Switch tabs | **Ctrl+Tab** | +| Quit | **Ctrl+Q** | + +See [[Keyboard-Shortcuts]] for the complete list. + +## Next Steps + +Now that you know the basics, explore more advanced features: + +- [[Projects-and-Environments]] - Organize requests and manage environments +- [[Variables]] - Use variables to avoid repetition +- [[Sensitive-Variables]] - Store API keys securely +- [[Scripts]] - Automate workflows with JavaScript +- [[Export]] - Export requests to cURL and other formats + +## Tips + +**Tip 1: Use httpbin.org for testing** +- `https://httpbin.org/get` - Test GET requests +- `https://httpbin.org/post` - Test POST requests +- `https://httpbin.org/headers` - See your request headers +- `https://httpbin.org/delay/3` - Test timeouts + +**Tip 2: Save frequently used requests** +- Save requests to projects for quick access +- Use descriptive names: "Login", "Get User Profile", etc. + +**Tip 3: Use syntax highlighting** +- Select JSON/XML/RAW in the body dropdown +- Makes reading requests and responses much easier + +**Tip 4: Check the history** +- History persists across sessions +- Great for debugging or comparing responses diff --git a/Home.md b/Home.md index 5d08b7b..b19e18e 100644 --- a/Home.md +++ b/Home.md @@ -1 +1,113 @@ -Welcome to the Wiki. \ No newline at end of file +# Roster Wiki + +Welcome to the **Roster** documentation! Roster is a modern HTTP client for GNOME, built with GTK 4 and libadwaita. + +## What is Roster? + +Roster is a native GNOME application for testing and debugging HTTP APIs. It provides a clean, intuitive interface for: + +- HTTP Requests - Send GET, POST, PUT, DELETE requests +- Custom Headers & Bodies - Full control over request configuration +- Response Viewing - View headers, body, status, and timing +- Request History - Persistent history with full request/response data +- Project Organization - Organize requests into projects +- Environment Variables - Manage different environments (dev, staging, prod) +- Sensitive Variables - Secure storage of API keys and tokens in GNOME Keyring +- JavaScript Scripts - Automate workflows with preprocessing/postprocessing +- Export - Export requests to cURL and other formats +- Beautiful UI - Native GNOME experience with libadwaita + +## Quick Links + +### Getting Started +- [[Installation]] - How to build and install Roster +- [[Getting-Started]] - Your first HTTP request +- [[Projects-and-Environments]] - Organize your work + +### Features +- [[Variables]] - Use variables in requests +- [[Sensitive-Variables]] - Secure storage with GNOME Keyring +- [[Scripts]] - Preprocessing and postprocessing automation +- [[Export]] - Export requests to other tools +- [[History]] - Track and replay requests + +### Reference +- [[API-Reference]] - Complete JavaScript API documentation +- [[Keyboard-Shortcuts]] - Speed up your workflow +- [[FAQ]] - Frequently asked questions + +### Development +- [[Contributing]] - How to contribute to Roster +- [[Development]] - Developer documentation +- [[Architecture]] - Technical overview + +## Key Features + +### Secure Credential Storage + +Store API keys, tokens, and passwords securely in GNOME Keyring with one-click encryption: + +``` +Variables: + base_url (unlocked) → Stored in JSON (plain text) + api_key (locked) → Stored in GNOME Keyring (encrypted) +``` + +Learn more: [[Sensitive-Variables]] + +### Powerful Automation + +Use JavaScript preprocessing and postprocessing scripts to: +- Extract authentication tokens from responses +- Add dynamic headers (timestamps, signatures) +- Chain requests together +- Validate responses + +```javascript +// Postprocessing: Extract token from login response +const data = JSON.parse(response.body); +roster.setVariable('auth_token', data.access_token); +console.log('Logged in successfully!'); +``` + +Learn more: [[Scripts]] + +### Multi-Environment Support + +Manage multiple environments with different variable values: + +| Variable | Production | Development | +|----------|-----------|-------------| +| base_url | `api.example.com` | `localhost:3000` | +| api_key | `prod-key-***` | `dev-key-***` | + +Switch environments with one click - all variables update automatically. + +Learn more: [[Variables]] + +## Platform + +Roster is built specifically for the GNOME desktop using: +- **GTK 4** - Modern UI toolkit +- **libadwaita** - GNOME design patterns +- **libsoup3** - HTTP networking (from GNOME Platform) +- **libsecret** - Secure credential storage +- **GJS** - JavaScript runtime for scripts + +## License + +Roster is free and open-source software licensed under **GPL-3.0-or-later**. + +## Support + +- **Issues**: https://git.bugsy.cz/beval/roster/issues +- **Source**: https://git.bugsy.cz/beval/roster +- **Wiki**: You are here! + +## Next Steps + +**New to Roster?** Start with [[Installation]] and [[Getting-Started]] + +**Need to store API keys?** Check out [[Sensitive-Variables]] + +**Want to automate workflows?** Learn about [[Scripts]] diff --git a/Installation.md b/Installation.md new file mode 100644 index 0000000..2a1e0e5 --- /dev/null +++ b/Installation.md @@ -0,0 +1,213 @@ +# Installation + +This guide covers how to build and install Roster from source. + +## Dependencies + +Roster requires the following dependencies: + +### Runtime Dependencies +- **GTK 4** (>= 4.0) +- **libadwaita 1** (>= 1.0) +- **Python 3** (>= 3.8) +- **libsoup3** - HTTP client library (provided by GNOME Platform) +- **libsecret** - Secure credential storage (provided by GNOME Platform) +- **GJS** - GNOME JavaScript runtime for script execution + +### Build Dependencies +- **Meson** (>= 1.0.0) +- **Ninja** +- **pkg-config** +- **gettext** - For internationalization +- **glib-compile-schemas** +- **glib-compile-resources** + +## Installation Methods + +### Method 1: Build from Source (Native) + +This method builds and installs Roster directly on your system. + +#### Step 1: Install Dependencies + +**Fedora:** +```bash +sudo dnf install meson ninja-build gtk4-devel libadwaita-devel \ + libsoup3-devel libsecret-devel gjs python3 \ + gettext desktop-file-utils appstream +``` + +**Ubuntu/Debian:** +```bash +sudo apt install meson ninja-build libgtk-4-dev libadwaita-1-dev \ + libsoup-3.0-dev libsecret-1-dev gjs python3 \ + gettext desktop-file-utils appstream +``` + +**Arch Linux:** +```bash +sudo pacman -S meson ninja gtk4 libadwaita libsoup3 libsecret gjs python3 gettext +``` + +#### Step 2: Clone Repository + +```bash +git clone https://git.bugsy.cz/beval/roster.git +cd roster +``` + +#### Step 3: Build + +```bash +meson setup builddir +meson compile -C builddir +``` + +#### Step 4: Install + +```bash +sudo meson install -C builddir +``` + +#### Step 5: Run + +```bash +roster +``` + +Or launch from your application menu. + +### Method 2: Build with Flatpak (Recommended) + +Flatpak provides a sandboxed environment with all dependencies included. + +#### Step 1: Install Flatpak Builder + +**Fedora:** +```bash +sudo dnf install flatpak-builder +``` + +**Ubuntu/Debian:** +```bash +sudo apt install flatpak-builder +``` + +#### Step 2: Add Flathub Remote + +```bash +flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo +``` + +#### Step 3: Install GNOME SDK + +```bash +flatpak install flathub org.gnome.Platform//49 org.gnome.Sdk//49 +``` + +#### Step 4: Build Flatpak + +```bash +git clone https://git.bugsy.cz/beval/roster.git +cd roster +flatpak-builder --user --install --force-clean build-dir cz.bugsy.roster.json +``` + +#### Step 5: Run + +```bash +flatpak run cz.bugsy.roster +``` + +### Method 3: GNOME Builder (For Development) + +GNOME Builder provides an integrated development environment. + +#### Step 1: Install GNOME Builder + +```bash +flatpak install flathub org.gnome.Builder +``` + +#### Step 2: Open Project + +1. Launch GNOME Builder +2. Click "Clone Repository" +3. Enter: `https://git.bugsy.cz/beval/roster.git` +4. Click "Clone" + +#### Step 3: Build and Run + +1. Click the "Build" button (hammer icon) +2. Click the "Run" button (play icon) + +## Uninstallation + +### Native Installation + +```bash +cd roster/builddir +sudo ninja uninstall +``` + +### Flatpak Installation + +```bash +flatpak uninstall cz.bugsy.roster +``` + +## File Locations + +### Native Installation + +- **Binary**: `/usr/local/bin/roster` (or `/usr/bin/roster`) +- **Application data**: `~/.local/share/cz.bugsy.roster/` +- **Requests/Projects**: `~/.local/share/cz.bugsy.roster/requests.json` +- **Session state**: `~/.local/share/cz.bugsy.roster/session.json` +- **Sensitive variables**: GNOME Keyring (encrypted) + +### Flatpak Installation + +- **Binary**: Managed by Flatpak +- **Application data**: `~/.var/app/cz.bugsy.roster/data/cz.bugsy.roster/` +- **Requests/Projects**: `~/.var/app/cz.bugsy.roster/data/cz.bugsy.roster/requests.json` +- **Session state**: `~/.var/app/cz.bugsy.roster/data/cz.bugsy.roster/session.json` +- **Sensitive variables**: GNOME Keyring (encrypted) + +## Troubleshooting + +### Build Errors + +**Error: "meson: command not found"** +- Install Meson build system (see dependencies above) + +**Error: "Package 'gtk4' not found"** +- Install GTK 4 development files (see dependencies above) + +**Error: "Package 'libadwaita-1' not found"** +- Install libadwaita development files (see dependencies above) + +### Runtime Errors + +**Error: "Failed to access Secret Service"** +- Ensure GNOME Keyring is installed and unlocked +- For Flatpak: Check that D-Bus permission is granted + +**Error: "Module 'gi' not found"** +- Ensure PyGObject is installed: `sudo dnf install python3-gobject` + +### Flatpak Specific + +**Error: "error: Nothing matches org.gnome.Platform"** +- Add Flathub remote: `flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo` +- Install GNOME SDK: `flatpak install flathub org.gnome.Platform//49 org.gnome.Sdk//49` + +**Application doesn't have network access** +- Check Flatpak permissions: `flatpak info --show-permissions cz.bugsy.roster` +- Should include: `--share=network` + +## Next Steps + +- [[Getting-Started]] - Learn how to use Roster +- [[Projects-and-Environments]] - Organize your API testing +- [[Variables]] - Use variables in requests diff --git a/Keyboard-Shortcuts.md b/Keyboard-Shortcuts.md new file mode 100644 index 0000000..241c2e4 --- /dev/null +++ b/Keyboard-Shortcuts.md @@ -0,0 +1,55 @@ +# Keyboard Shortcuts + +Complete list of keyboard shortcuts in Roster. + +## Request Management + +| Action | Shortcut | +|--------|----------| +| New request tab | **Ctrl+T** | +| Close current tab | **Ctrl+W** | +| Send request | **Ctrl+Return** | +| Save request | **Ctrl+S** | + +## Navigation + +| Action | Shortcut | +|--------|----------| +| Next tab | **Ctrl+Tab** | +| Previous tab | **Ctrl+Shift+Tab** | +| Focus URL field | **Ctrl+L** | + +## Application + +| Action | Shortcut | +|--------|----------| +| Show keyboard shortcuts | **Ctrl+?** | +| Quit application | **Ctrl+Q** | + +## Dialog Management + +| Action | Shortcut | +|--------|----------| +| Close dialog | **Escape** | +| Confirm action | **Return** or **Enter** | + +## Text Editing + +Standard text editing shortcuts work in all text fields: + +| Action | Shortcut | +|--------|----------| +| Select all | **Ctrl+A** | +| Copy | **Ctrl+C** | +| Cut | **Ctrl+X** | +| Paste | **Ctrl+V** | +| Undo | **Ctrl+Z** | +| Redo | **Ctrl+Shift+Z** | + +## Tips + +**Quick Send:** After editing request, press **Ctrl+Return** to send immediately without clicking. + +**Fast Tab Switching:** Use **Ctrl+Tab** to cycle through open requests. + +**Save Often:** Press **Ctrl+S** frequently to avoid losing work. diff --git a/Projects-and-Environments.md b/Projects-and-Environments.md new file mode 100644 index 0000000..b8d81cb --- /dev/null +++ b/Projects-and-Environments.md @@ -0,0 +1,255 @@ +# Projects and Environments + +Projects help organize related HTTP requests, while environments allow you to manage different configurations (development, staging, production). + +## Projects + +### What is a Project? + +A project is a container for: +- **Saved requests** - Your HTTP requests +- **Variables** - Shared variables across requests +- **Environments** - Different configurations (dev, prod, etc.) + +Example projects: +- "GitHub API" +- "My App Backend" +- "Payment Integration" +- "Authentication Service" + +### Creating a Project + +1. Click the **hamburger menu** (three lines) in top-left +2. Select **"New Project"** +3. Enter project name +4. Click **"Create"** + +A default "Default" environment is automatically created. + +### Accessing Projects + +Projects appear in the sidebar on the left side: +- Click a project to expand/collapse +- Shows saved requests within the project +- Active project highlighted + +### Renaming a Project + +1. Right-click the project in sidebar +2. Select **"Rename"** +3. Enter new name +4. Click **"Save"** + +### Deleting a Project + +1. Right-click the project in sidebar +2. Select **"Delete"** +3. Confirm deletion + +**Warning:** This deletes all requests, environments, and variables in the project! + +### Changing Project Icon + +1. Right-click the project in sidebar +2. Select **"Change Icon"** +3. Choose icon from grid +4. Icon updates immediately + +## Environments + +### What is an Environment? + +An environment is a set of variable values. Common environments: +- **Production** - Live API with real credentials +- **Staging** - Pre-production testing +- **Development** - Local development server +- **Testing** - Automated test environment + +### Managing Environments + +See [[Variables]] for complete environment and variable management documentation. + +**Quick access:** +1. Select a project +2. Click **"Environments"** button in header bar +3. Manage environments and variables in dialog + +### Default Environment + +Each project starts with a "Default" environment. You can: +- Rename it +- Add more environments +- Delete it (if you have at least one other environment) + +## Saved Requests + +### Saving a Request + +1. Configure your request (URL, method, headers, body) +2. Click **"Save"** button in header +3. Enter request name (e.g., "Get User Profile") +4. Click **"Save"** + +Request is now saved to the current project. + +### Opening a Saved Request + +1. Click the hamburger menu +2. Navigate to your project +3. Click on a saved request +4. Request opens in a new tab + +### Editing a Saved Request + +1. Open the request +2. Make changes +3. Click **"Save"** (or Ctrl+S) +4. Changes are saved + +**Modified indicator:** Unsaved changes show a dot (•) on the tab label. + +### Deleting a Saved Request + +1. Right-click the request in sidebar +2. Select **"Delete"** +3. Confirm deletion + +### Attaching Scripts + +See [[Scripts]] for complete documentation. + +1. Save a request first +2. Right-click the request in sidebar +3. Select **"Edit Scripts"** +4. Write preprocessing/postprocessing scripts +5. Click **"Save"** + +## Switching Between Projects + +### In Sidebar + +Simply click different projects in the sidebar to view their contents. + +### Request Association + +Each request tab remembers its project: +- Variables come from that project's environments +- Saving updates that project + +## Organization Tips + +### Group by Service + +Create separate projects for each external API: +``` +Projects: + GitHub API + - List Repositories + - Create Issue + - Get User + Stripe API + - Create Customer + - Create Payment + - List Charges +``` + +### Group by Feature + +Create projects for features in your app: +``` +Projects: + Authentication + - Login + - Logout + - Refresh Token + User Management + - Create User + - Update Profile + - Delete Account +``` + +### Use Descriptive Names + +**Good request names:** +- "Login with Email/Password" +- "Get User Profile by ID" +- "Create Payment Intent (Stripe)" +- "Update User Settings" + +**Bad request names:** +- "Request 1" +- "Test" +- "New Request" +- "GET" + +## Project Workflow Example + +### Step 1: Create Project + +Create "My API" project + +### Step 2: Add Environments + +Open Environments dialog, add: +- Production +- Development + +### Step 3: Define Variables + +Add variables: +- `base_url` + - Production: `https://api.example.com` + - Development: `http://localhost:3000` +- `api_key` (mark as sensitive) + - Production: `prod-key-***` + - Development: `dev-key-***` + +### Step 4: Create Requests + +Create and save requests: +- "Get Users" - `GET {{base_url}}/users` +- "Create User" - `POST {{base_url}}/users` +- "Delete User" - `DELETE {{base_url}}/users/{{user_id}}` + +### Step 5: Test Different Environments + +1. Open "Get Users" request +2. Select "Development" environment +3. Send (hits localhost) +4. Select "Production" environment +5. Send (hits production API) + +Same request, different targets! + +## Data Storage + +### File Location + +**Native:** +``` +~/.local/share/cz.bugsy.roster/requests.json +``` + +**Flatpak:** +``` +~/.var/app/cz.bugsy.roster/data/cz.bugsy.roster/requests.json +``` + +### Sensitive Variables + +Regular variables stored in JSON. +Sensitive variables stored in GNOME Keyring (encrypted). + +See [[Sensitive-Variables]] for details. + +### Backup + +To backup your projects: +1. Copy the `requests.json` file +2. For sensitive variables: backup your GNOME Keyring (see [[Sensitive-Variables]]) + +## Next Steps + +- [[Variables]] - Learn about variables and environments +- [[Sensitive-Variables]] - Secure credential storage +- [[Scripts]] - Automate workflows diff --git a/Scripts.md b/Scripts.md new file mode 100644 index 0000000..7b58c8e --- /dev/null +++ b/Scripts.md @@ -0,0 +1,552 @@ +# Scripts + +Roster supports JavaScript preprocessing and postprocessing scripts to automate request modifications and response data extraction. + +## Overview + +Scripts allow you to: +- **Modify requests** before they're sent (preprocessing) +- **Extract data** from responses after they're received (postprocessing) +- **Chain requests** together by passing data between them +- **Automate workflows** like authentication flows + +Scripts are executed using **GJS** (GNOME JavaScript), the same JavaScript runtime used throughout GNOME. + +## Script Types + +### Preprocessing Scripts + +**Run BEFORE the HTTP request is sent.** + +Use cases: +- Modify request headers, URL, body, or method +- Add dynamic values (timestamps, request IDs, signatures) +- Read environment variables +- Set/update environment variables +- Add authentication headers + +### Postprocessing Scripts + +**Run AFTER receiving the HTTP response.** + +Use cases: +- Extract data from response body +- Parse JSON/XML responses +- Store values in environment variables for use in subsequent requests +- Validate response data +- Chain requests together + +## Adding Scripts to Requests + +### Step 1: Save a Request + +Scripts are attached to saved requests. + +1. Configure your request +2. Click **"Save"** button +3. Enter request name +4. Click **"Save"** + +### Step 2: Open Script Editor + +1. Right-click the saved request in the sidebar +2. Select **"Edit Scripts"** +3. Script editor dialog opens with two tabs: + - **Preprocessing** tab + - **Postprocessing** tab + +### Step 3: Write Your Script + +1. Select the appropriate tab +2. Write JavaScript code +3. Click **"Save"** or **Ctrl+S** +4. Close dialog + +### Step 4: Run Request with Scripts + +1. Open the request +2. Click **"Send"** +3. Preprocessing script runs first (if present) +4. HTTP request sent +5. Postprocessing script runs after response received (if present) + +## Preprocessing API + +### Available Objects + +#### Request Object (Modifiable) + +```javascript +request.method // String: "GET", "POST", "PUT", "DELETE" +request.url // String: Full URL +request.headers // Object: Header key-value pairs +request.body // String: Request body +``` + +All properties can be modified. + +#### Roster API + +```javascript +// Variables +roster.getVariable(name) // Get variable from selected environment +roster.setVariable(name, value) // Set/update variable +roster.setVariables({key: value}) // Batch set variables + +// Project Information +roster.project.name // Current project name +roster.project.environments // Array of environment names +``` + +#### Console Output + +```javascript +console.log(message) // Output shown in preprocessing results tab +console.error(message) // Error output +``` + +### Examples + +#### Example 1: Add Dynamic Authentication Header + +```javascript +const token = roster.getVariable('auth_token'); +request.headers['Authorization'] = 'Bearer ' + token; +request.headers['X-Request-Time'] = new Date().toISOString(); +console.log('Added auth header'); +``` + +#### Example 2: Modify Request Based on Environment + +```javascript +const env = roster.getVariable('environment_name'); +if (env === 'production') { + request.url = request.url.replace('localhost', 'api.example.com'); + console.log('Switched to production URL'); +} else { + console.log('Using development URL'); +} +``` + +#### Example 3: Generate Request Signature + +```javascript +const apiKey = roster.getVariable('api_key'); +const timestamp = Date.now().toString(); +const requestId = Math.random().toString(36).substring(7); + +request.headers['X-API-Key'] = apiKey; +request.headers['X-Timestamp'] = timestamp; +request.headers['X-Request-ID'] = requestId; + +// Save for later reference +roster.setVariable('last_request_id', requestId); +console.log('Request ID:', requestId); +``` + +#### Example 4: Add HMAC Signature + +```javascript +// Note: GJS doesn't have crypto module, this is pseudocode +const apiKey = roster.getVariable('api_key'); +const secretKey = roster.getVariable('secret_key'); +const timestamp = Date.now().toString(); + +// You would use a crypto library here +const signature = generateHMAC(request.body + timestamp, secretKey); + +request.headers['X-API-Key'] = apiKey; +request.headers['X-Timestamp'] = timestamp; +request.headers['X-Signature'] = signature; +``` + +#### Example 5: Modify Request Body + +```javascript +// Parse existing body +const body = JSON.parse(request.body); + +// Add dynamic fields +body.timestamp = new Date().toISOString(); +body.requestId = Math.random().toString(36).substring(7); +body.version = '2.0'; + +// Update request body +request.body = JSON.stringify(body, null, 2); +console.log('Modified request body'); +``` + +## Postprocessing API + +### Available Objects + +#### Response Object (Read-Only) + +```javascript +response.body // String: Response body +response.headers // Object: Header key-value pairs +response.statusCode // Number: HTTP status code (200, 404, etc.) +response.statusText // String: Status text ("OK", "Not Found", etc.) +response.responseTime // Number: Response time in milliseconds +``` + +All properties are read-only. + +#### Roster API + +```javascript +// Variables +roster.setVariable(name, value) // Set/update variable +roster.setVariables({key: value}) // Batch set variables +``` + +Note: `roster.getVariable()` is NOT available in postprocessing. + +#### Console Output + +```javascript +console.log(message) // Output shown in postprocessing results tab +console.error(message) // Error output +``` + +### Examples + +#### Example 1: Extract Authentication Token + +```javascript +const data = JSON.parse(response.body); +if (data.access_token) { + roster.setVariable('auth_token', data.access_token); + console.log('Saved auth token'); +} else { + console.error('No access token in response'); +} +``` + +#### Example 2: Extract Multiple Values + +```javascript +const data = JSON.parse(response.body); +roster.setVariables({ + user_id: data.user.id, + user_name: data.user.name, + user_email: data.user.email, + session_id: data.session.id +}); +console.log('Extracted user:', data.user.name); +``` + +#### Example 3: Validate and Store Response + +```javascript +const data = JSON.parse(response.body); + +if (response.statusCode === 200 && data.items) { + roster.setVariable('item_count', data.items.length.toString()); + + if (data.items.length > 0) { + roster.setVariable('first_item_id', data.items[0].id); + roster.setVariable('first_item_name', data.items[0].name); + } + + console.log('Found', data.items.length, 'items'); +} else { + console.error('Error: Invalid response or no items'); +} +``` + +#### Example 4: Extract Pagination Token + +```javascript +const data = JSON.parse(response.body); + +if (data.next_page_token) { + roster.setVariable('next_page', data.next_page_token); + console.log('Next page token saved'); +} else { + roster.setVariable('next_page', ''); + console.log('No more pages'); +} +``` + +#### Example 5: Parse Response Headers + +```javascript +// Extract rate limit information from headers +const rateLimit = response.headers['X-RateLimit-Limit']; +const rateRemaining = response.headers['X-RateLimit-Remaining']; +const rateReset = response.headers['X-RateLimit-Reset']; + +if (rateLimit && rateRemaining && rateReset) { + roster.setVariables({ + rate_limit: rateLimit, + rate_remaining: rateRemaining, + rate_reset: rateReset + }); + console.log('Rate limit:', rateRemaining, '/', rateLimit); +} else { + console.log('No rate limit headers'); +} +``` + +## Complete Workflows + +### Workflow 1: OAuth Token Flow + +**Request 1: Login (POST /auth/login)** + +Postprocessing: +```javascript +// Extract and store tokens from login response +const data = JSON.parse(response.body); + +if (response.statusCode === 200) { + roster.setVariables({ + access_token: data.access_token, + refresh_token: data.refresh_token, + user_id: data.user_id, + expires_at: data.expires_at + }); + console.log('Logged in as user:', data.user_id); +} else { + console.error('Login failed:', response.statusText); +} +``` + +**Request 2: Get User Profile (GET /users/{userId})** + +Preprocessing: +```javascript +// Use stored token and user ID in request +const token = roster.getVariable('access_token'); +const userId = roster.getVariable('user_id'); + +// Add authentication +request.headers['Authorization'] = 'Bearer ' + token; + +// Substitute user ID in URL +request.url = request.url.replace('{userId}', userId); + +console.log('Making authenticated request for user:', userId); +``` + +### Workflow 2: Paginated API Requests + +**Request: Get Items with Pagination** + +Preprocessing: +```javascript +const nextPage = roster.getVariable('next_page'); + +if (nextPage) { + request.url = request.url + '?page_token=' + nextPage; + console.log('Fetching next page'); +} else { + console.log('Fetching first page'); +} +``` + +Postprocessing: +```javascript +const data = JSON.parse(response.body); + +// Store items count +roster.setVariable('items_count', data.items.length.toString()); + +// Store next page token for subsequent request +if (data.next_page_token) { + roster.setVariable('next_page', data.next_page_token); + console.log('Page loaded. More pages available.'); +} else { + roster.setVariable('next_page', ''); + console.log('Page loaded. No more pages.'); +} +``` + +### Workflow 3: Dynamic API Versioning + +**Preprocessing:** +```javascript +const apiVersion = roster.getVariable('api_version') || 'v1'; +request.url = request.url.replace('/api/', '/api/' + apiVersion + '/'); +request.headers['X-API-Version'] = apiVersion; +console.log('Using API version:', apiVersion); +``` + +**Postprocessing:** +```javascript +// Check if server suggests upgrading API version +const suggestedVersion = response.headers['X-Suggested-API-Version']; + +if (suggestedVersion) { + console.log('Server suggests API version:', suggestedVersion); + roster.setVariable('suggested_api_version', suggestedVersion); +} +``` + +## Script Execution Flow + +When you click "Send" on a request with scripts: + +``` +1. Load request from UI +2. IF preprocessing script exists: + 2a. Execute preprocessing script + 2b. Script can modify request object + 2c. Script output shown in "Preprocessing" tab +3. Apply variable substitution ({{variable_name}}) +4. Send HTTP request +5. Receive response +6. IF postprocessing script exists: + 6a. Execute postprocessing script + 6b. Script can read response, set variables + 6c. Script output shown in "Postprocessing" tab +7. Display response in UI +8. Save to history +``` + +## Error Handling + +### Script Errors + +If a script throws an error: +- Error message shown in script results tab +- Request still executes (preprocessing) or response still displays (postprocessing) +- Variable changes before the error are preserved + +### Example: Safe JSON Parsing + +```javascript +try { + const data = JSON.parse(response.body); + roster.setVariable('user_id', data.user.id); + console.log('Success'); +} catch (e) { + console.error('Failed to parse JSON:', e.message); +} +``` + +## Best Practices + +### Keep Scripts Simple + +Scripts should be short and focused: +- One clear purpose per script +- Avoid complex logic +- Use console.log for debugging + +### Error Handling + +Always handle potential errors: +```javascript +if (response.statusCode === 200) { + try { + const data = JSON.parse(response.body); + // ... process data + } catch (e) { + console.error('Parse error:', e.message); + } +} else { + console.error('Request failed:', response.statusText); +} +``` + +### Variable Naming + +Use clear, descriptive variable names: +```javascript +// Good +roster.setVariable('github_access_token', token); +roster.setVariable('user_profile_id', id); + +// Bad +roster.setVariable('token', token); +roster.setVariable('id', id); +``` + +### Logging + +Use console output to track script execution: +```javascript +console.log('Starting authentication...'); +// ... authentication logic +console.log('Authentication complete'); +``` + +### Sensitive Data + +**Never log sensitive data:** +```javascript +// BAD - logs the actual token +const token = roster.getVariable('api_key'); +console.log('Token:', token); + +// GOOD - confirms without revealing +const token = roster.getVariable('api_key'); +console.log('Token retrieved'); +``` + +## Limitations + +### No External Libraries + +GJS environment is sandboxed: +- Cannot import npm packages +- Cannot require() external modules +- Standard JavaScript built-ins available + +### No Asynchronous Operations + +Scripts execute synchronously: +- No setTimeout/setInterval +- No async/await +- No fetch() or XMLHttpRequest +- Use Roster's request system instead + +### Security Restrictions + +Scripts cannot: +- Access file system directly +- Make network requests (other than the main request) +- Execute shell commands +- Access system resources + +## Debugging Scripts + +### Use Console Output + +Liberally use console.log: +```javascript +console.log('Request URL before:', request.url); +// ... modify URL +console.log('Request URL after:', request.url); +``` + +### Check Script Results + +After sending request: +1. Look for "Preprocessing" tab (if preprocessing script exists) +2. Look for "Postprocessing" tab (if postprocessing script exists) +3. View console output and any errors + +### Test with Simple Scripts + +Start simple and build up: +```javascript +// Test 1: Verify script runs +console.log('Script is running!'); + +// Test 2: Verify variable access +const baseUrl = roster.getVariable('base_url'); +console.log('Base URL:', baseUrl); + +// Test 3: Verify request modification +request.headers['X-Test'] = 'hello'; +console.log('Added test header'); +``` + +## Next Steps + +- [[API-Reference]] - Complete API documentation +- [[Variables]] - Learn about environment variables +- [[Sensitive-Variables]] - Secure storage for tokens and keys diff --git a/Sensitive-Variables.md b/Sensitive-Variables.md new file mode 100644 index 0000000..953d370 --- /dev/null +++ b/Sensitive-Variables.md @@ -0,0 +1,377 @@ +# Sensitive Variables + +Sensitive variables provide secure storage for API keys, passwords, and tokens using GNOME Keyring encryption instead of plain text files. + +## Overview + +Regular [[Variables]] are stored in plain text JSON files at `~/.local/share/cz.bugsy.roster/requests.json`. This is fine for non-sensitive data like base URLs or timeout values, but **dangerous for secrets**. + +**Sensitive variables** solve this by storing values encrypted in GNOME Keyring, the same secure storage used by: +- Firefox and GNOME Web for passwords +- Evolution for email credentials +- Network Manager for WiFi passwords +- SSH for key passphrases + +## How It Works + +### Storage Comparison + +**Regular Variable (Insecure):** +``` +File: ~/.local/share/cz.bugsy.roster/requests.json +Format: Plain text (anyone can read) +Content: "api_key": "secret-abc123-visible-to-all" +``` + +**Sensitive Variable (Secure):** +``` +File: ~/.local/share/cz.bugsy.roster/requests.json +Format: Plain text +Content: "api_key": "" (empty placeholder) + +Keyring: GNOME Keyring (encrypted) +Format: Encrypted with your login password +Content: "secret-abc123-visible-to-all" (encrypted at rest) +``` + +## Usage + +### Step 1: Open Environments Dialog + +1. Select your project from the sidebar +2. Click the **"Environments"** button in the header bar +3. The variables table appears + +### Step 2: Identify the Lock Icon + +Each variable row has three buttons: +- Variable name entry +- **Lock icon** (toggle between secure/insecure storage) +- Delete button (trash icon) + +### Step 3: Mark Variable as Sensitive + +**Before entering secrets (recommended):** +1. Create variable (e.g., `api_key`) +2. Click the **lock icon** (appears as unlocked/gray initially) +3. Icon changes to locked state (with accent color) +4. Variable name becomes bold and colored +5. Now enter your secret value + +**After entering secrets (migration):** +1. Click the lock icon on existing variable +2. Values automatically move from JSON to keyring +3. JSON file now contains empty placeholders + +### Step 4: Enter Secret Values + +1. Click in the value entry field +2. Type your secret value +3. **You'll see bullets (••••••) instead of characters** +4. Value automatically saves to GNOME Keyring (encrypted) + +### Step 5: Use in Requests + +Sensitive variables work exactly like regular variables: + +``` +GET {{base_url}}/users +Authorization: Bearer {{api_key}} +``` + +When you send the request: +1. Roster retrieves `api_key` from GNOME Keyring +2. Decrypts it using your login password +3. Substitutes it into the request +4. Sends the request with the real value + +## Visual Indicators + +### Lock Icon States + +**Unlocked (channel-insecure-symbolic):** +- Gray/inactive appearance +- Variable stored in plain JSON +- Tooltip: "Not sensitive (stored in JSON) - Click to mark as sensitive" + +**Locked (channel-secure-symbolic):** +- Colored/active appearance (accent color) +- Variable stored in encrypted keyring +- Tooltip: "Sensitive (stored in keyring) - Click to make non-sensitive" + +### Variable Name Styling + +**Regular variables:** +- Normal text color +- Normal font weight + +**Sensitive variables:** +- Accent color (theme-dependent) +- Bold font weight +- Easy to identify at a glance + +### Value Entry Fields + +**Regular variables:** +- Show typed characters normally +- Standard text entry + +**Sensitive variables:** +- Show bullets (••••••) like password fields +- Entry purpose: PASSWORD +- Cannot see the value while typing + +## Viewing Secrets in Keyring + +To verify your secrets are encrypted: + +1. Open **"Passwords and Keys"** application (Seahorse) +2. Navigate to **"Login"** keyring +3. Look for entries labeled: `Roster: ProjectName/EnvironmentName/VariableName` + +Example: +``` +Login Keyring + Roster: GitHub API/Production/api_token + Roster: GitHub API/Development/api_token + Roster: Stripe API/Production/secret_key +``` + +Click any entry to: +- View the secret value (requires authentication) +- Edit the value manually +- Delete the secret + +## Making Variables Non-Sensitive + +If you marked a variable as sensitive by mistake: + +1. Click the lock icon again +2. Icon changes from locked to unlocked +3. Values move from keyring back to JSON +4. You can now see values in plain text + +**Warning:** Only do this for non-sensitive data! Once moved to JSON, the values are visible to anyone who can read your files. + +## Security + +### Encryption + +- Values encrypted at rest with your GNOME login password +- Uses same encryption as system passwords +- No plaintext secrets in file system + +### Auto-Unlock + +- Keyring unlocks automatically when you log in +- No need to enter password again +- Locked when you log out + +### Access Control + +- Only your user account can access +- Protected by OS-level security +- Sandboxed apps (Flatpak) require explicit permission + +### Separation + +Roster secrets are stored with a custom schema: +- Schema: `cz.bugsy.roster.EnvironmentVariable` +- Attributes: `project_id`, `environment_id`, `variable_name` +- **Won't clutter your personal passwords** +- Easy to identify in Seahorse + +## Best Practices + +### DO Mark as Sensitive + +- **API keys**: `api_key`, `github_token`, `stripe_key` +- **Passwords**: `password`, `db_password`, `admin_pass` +- **Tokens**: `bearer_token`, `oauth_token`, `access_token` +- **Secrets**: `client_secret`, `api_secret`, `webhook_secret` +- **Private keys**: Any cryptographic keys + +### DON'T Mark as Sensitive + +- **URLs**: `base_url`, `api_endpoint`, `webhook_url` +- **Configuration**: `timeout`, `max_retries`, `api_version` +- **Identifiers**: `env`, `region`, `datacenter`, `tenant_id` +- **Non-secret data**: Anything that's OK to be visible + +### Workflow Tips + +**Create variables first, then mark as sensitive:** +``` +1. Add variable "api_key" +2. Click lock icon (mark as sensitive) +3. Enter value (goes to keyring immediately) +``` + +**Use descriptive names:** +``` +Good: github_personal_token, stripe_secret_key +Bad: token1, key, secret +``` + +**Group related variables:** +``` +base_url (regular) +api_key (sensitive) +api_secret (sensitive) +timeout (regular) +``` + +## Automatic Cleanup + +When you delete variables, environments, or projects, Roster automatically cleans up keyring secrets: + +**Delete variable:** +- Removed from JSON +- All keyring secrets for that variable deleted (across all environments) + +**Delete environment:** +- Removed from JSON +- All keyring secrets for that environment deleted + +**Delete project:** +- Project removed from disk +- **All keyring secrets for that project deleted** + +**Rename variable:** +- Variable renamed in JSON +- All keyring secrets automatically renamed +- No data loss + +## Script Integration + +Scripts can access sensitive variables transparently: + +### Reading Sensitive Variables + +```javascript +// Preprocessing script +const apiKey = roster.getVariable('api_key'); // Retrieved from keyring +request.headers['X-API-Key'] = apiKey; +console.log('Using API key'); // DON'T log the actual value! +``` + +### Writing Sensitive Variables + +```javascript +// Postprocessing script +const data = JSON.parse(response.body); + +// If 'access_token' is marked as sensitive, it goes to keyring +roster.setVariable('access_token', data.access_token); +console.log('Saved access token to keyring'); +``` + +Scripts don't need to know whether variables are sensitive - storage is handled automatically. + +## Troubleshooting + +### Keyring Not Unlocked + +**Symptom:** Error accessing secrets + +**Solution:** +1. Ensure you're logged into GNOME +2. Keyring unlocks automatically on login +3. Manual unlock: Open "Passwords and Keys", right-click "Login", select "Unlock" + +### Flatpak Permission Denied + +**Symptom:** Cannot access keyring from Flatpak + +**Solution:** +1. Check `cz.bugsy.roster.json` includes: `--talk-name=org.freedesktop.secrets` +2. Rebuild Flatpak with correct permissions +3. Verify: `flatpak info --show-permissions cz.bugsy.roster` + +### Cannot View Secret in Seahorse + +**Symptom:** Secret exists but won't display + +**Solution:** +1. Right-click the secret in Seahorse +2. Select "Show password" +3. Enter your login password when prompted + +### Lost Secrets After Reinstall + +**Symptom:** Secrets missing after reinstalling Roster + +**Keyring persists across installs** - secrets should still be there. + +**Check:** +1. Open Seahorse +2. Look under "Login" keyring +3. Search for "Roster:" + +If missing, you'll need to re-enter them. + +## Advanced: Manual Keyring Management + +You can manually manage Roster secrets using Seahorse: + +### View All Roster Secrets + +1. Open Seahorse +2. Navigate to "Login" keyring +3. Search or filter for: `Roster` +4. All Roster secrets appear + +### Backup Secrets + +Seahorse can export keyring for backup: +1. File → Export +2. Select secrets to export +3. Choose secure location + +### Delete All Roster Secrets + +1. Search for "Roster" in Seahorse +2. Select all Roster entries +3. Right-click → Delete + +## Migration from Plain Variables + +If you have existing projects with secrets in plain text: + +```javascript +// Step 1: Identify secrets in your variables +// Look for: api_key, password, token, secret, private_key + +// Step 2: For each secret variable: +// - Open Environments dialog +// - Click lock icon +// - Values automatically move to keyring + +// Step 3: Verify in Seahorse +// - Open "Passwords and Keys" +// - Check secrets appear under "Login" keyring +``` + +Your JSON file will now have empty strings, and secrets are encrypted. + +## Platform Differences + +### Native Installation + +- Keyring: `~/.local/share/keyrings/` +- Direct access to system keyring +- No special permissions needed + +### Flatpak Installation + +- Keyring: Same system keyring (shared) +- Requires D-Bus permission: `--talk-name=org.freedesktop.secrets` +- Sandboxed but can access keyring with permission + +Both installations share the same keyring - secrets work across install methods! + +## Next Steps + +- [[Variables]] - Learn about regular variables +- [[Scripts]] - Use variables in automation scripts +- [[API-Reference]] - Complete JavaScript API documentation diff --git a/Variables.md b/Variables.md new file mode 100644 index 0000000..eb49543 --- /dev/null +++ b/Variables.md @@ -0,0 +1,356 @@ +# Variables + +Variables allow you to reuse values across multiple requests and switch between different environments (development, staging, production) with ease. + +## What are Variables? + +Variables are placeholder values that you can reference in your requests using the `{{variable_name}}` syntax. When you send a request, Roster automatically replaces these placeholders with the actual values from your selected environment. + +**Example:** +``` +URL: {{base_url}}/users +Headers: + Authorization: Bearer {{api_token}} +``` + +When sent with the "Production" environment: +``` +URL: https://api.example.com/users +Headers: + Authorization: Bearer prod-token-abc123 +``` + +## Environments + +Environments are collections of variable values. Common environment names: +- **Production** - Live API credentials +- **Staging** - Pre-production testing +- **Development** - Local development server + +Each project can have multiple environments, and each environment has its own set of variable values. + +## Creating Environments + +### Step 1: Open Environments Dialog + +1. Select a project from the sidebar +2. Click the **"Environments"** button in the header bar + +### Step 2: Add Environment + +1. Click **"Add Environment"** button +2. Enter name (e.g., "Production", "Development") +3. Click **"Add"** + +The environment appears as a new column in the table. + +### Step 3: Add Variables + +1. Click the **"+"** button at the bottom of the variable list +2. Enter variable name (e.g., `base_url`) +3. Click **"Add"** + +The variable appears as a new row in the table. + +### Step 4: Set Values + +Click in the cells to enter values for each environment: + +| Variable | Production | Development | +|----------|-----------|-------------| +| base_url | `https://api.example.com` | `http://localhost:3000` | +| timeout | `30` | `60` | + +Click **"Done"** to save. + +## Using Variables in Requests + +### Variable Syntax + +Use double curly braces: `{{variable_name}}` + +Variables work in: +- **URL**: `{{base_url}}/users` +- **Headers**: `Authorization: Bearer {{token}}` +- **Request Body**: `{"api_key": "{{api_key}}"}` + +### Select Environment + +1. Open a request tab +2. Use the **environment dropdown** (top of request panel) +3. Select environment (e.g., "Production") +4. All variables in the request will use values from this environment + +### Visual Indicators + +**Defined variables** - Normal appearance + +**Undefined variables** - Highlighted with a warning color +- Variable name exists in `{{...}}` syntax +- But not defined in current environment + +## Managing Variables + +### Rename Variable + +1. Open **Environments** dialog +2. Click on variable name +3. Edit the name +4. Press Enter + +The variable is renamed across all environments automatically. + +### Delete Variable + +1. Open **Environments** dialog +2. Click the **trash icon** next to variable name +3. Confirm deletion + +The variable is removed from all environments. + +### Rename Environment + +1. Open **Environments** dialog +2. Click the **edit icon** on environment column header +3. Enter new name +4. Click **"Save"** + +### Delete Environment + +1. Open **Environments** dialog +2. Click the **trash icon** on environment column header +3. Confirm deletion + +All variable values for that environment are deleted. + +**Note:** Each project must have at least one environment. + +## Common Variable Patterns + +### Base URL + +``` +Variable: base_url +Production: https://api.example.com +Development: http://localhost:3000 + +Usage: {{base_url}}/users/123 +``` + +### API Version + +``` +Variable: api_version +Production: v2 +Development: v2-beta + +Usage: {{base_url}}/{{api_version}}/users +``` + +### Timeout Values + +``` +Variable: timeout +Production: 30 +Development: 60 + +Usage: X-Timeout: {{timeout}} +``` + +### Environment Identifier + +``` +Variable: env +Production: prod +Development: dev + +Usage: X-Environment: {{env}} +``` + +### User ID for Testing + +``` +Variable: test_user_id +Production: 12345 +Development: 1 + +Usage: {{base_url}}/users/{{test_user_id}} +``` + +## Script Integration + +Variables can be accessed and modified from [[Scripts]]: + +### Reading Variables (Preprocessing) + +```javascript +const baseUrl = roster.getVariable('base_url'); +const apiKey = roster.getVariable('api_key'); + +console.log('Using base URL:', baseUrl); +request.headers['X-API-Key'] = apiKey; +``` + +### Writing Variables (Postprocessing) + +```javascript +// Extract token from response and save for next request +const data = JSON.parse(response.body); +roster.setVariable('auth_token', data.access_token); +roster.setVariable('user_id', data.user_id); +console.log('Saved token for user:', data.user_id); +``` + +### Batch Updates + +```javascript +// Update multiple variables at once +roster.setVariables({ + access_token: data.access_token, + refresh_token: data.refresh_token, + user_id: data.user.id, + user_name: data.user.name +}); +``` + +Scripts automatically update variables in the currently selected environment. + +## Variable Scoping + +**Project Level:** +- Variables are defined at the project level +- All requests in the project can access these variables + +**Environment Level:** +- Variable values are specific to each environment +- Switching environments switches all values at once + +**Not Global:** +- Variables from one project cannot be used in another project +- Each project has its own isolated set of variables + +## Best Practices + +### Use Descriptive Names + +Good: +- `base_url` +- `api_key` +- `github_token` +- `stripe_secret_key` + +Bad: +- `url1` +- `key` +- `token` + +### Group Related Variables + +``` +# API Configuration +base_url +api_version +timeout + +# Authentication +api_key +api_secret +auth_token + +# Test Data +test_user_id +test_email +``` + +### Keep Environments Consistent + +All environments should have the same variable names with different values: + +| Variable | Production | Development | +|----------|-----------|-------------| +| base_url | `api.example.com` | `localhost:3000` | +| api_key | `prod-key-***` | `dev-key-***` | +| timeout | `30` | `60` | + +### Use Sensitive Variables for Secrets + +**DO NOT** store secrets in regular variables - they are saved in plain text! + +Use [[Sensitive-Variables]] instead: +- API keys +- Passwords +- Tokens +- Secret keys + +## Undefined Variables + +When Roster encounters `{{variable_name}}` but the variable is not defined: + +**Visual Warning:** +- The entry field with undefined variable is highlighted +- Warning color indicates missing variable + +**Request Behavior:** +- The literal text `{{variable_name}}` is sent +- No error is thrown +- Check the console for warnings + +**Fix:** +1. Open **Environments** dialog +2. Add the missing variable +3. Set its value in current environment + +## Environment Switching + +Switch environments to test the same request against different APIs: + +**Example Workflow:** +1. Create request: `GET {{base_url}}/users` +2. Select "Development" environment +3. Send request (goes to `http://localhost:3000/users`) +4. Select "Production" environment +5. Send request (goes to `https://api.example.com/users`) + +Same request, different targets! + +## Advanced Topics + +### Variables in Request Body + +```json +{ + "api_key": "{{api_key}}", + "environment": "{{env}}", + "user_id": {{user_id}} +} +``` + +Note: Numeric values should not have quotes if you want them as numbers. + +### Nested Variable References + +Variables can contain other variables: +``` +Variable: full_url +Value: {{base_url}}/{{api_version}}/users +``` + +Both `{{base_url}}` and `{{api_version}}` will be substituted. + +### Dynamic Variables from Scripts + +Variables don't have to be manually created - scripts can create them: + +```javascript +// Preprocessing script +const timestamp = new Date().toISOString(); +roster.setVariable('request_timestamp', timestamp); + +// Now you can use {{request_timestamp}} in the request +``` + +## Next Steps + +- [[Sensitive-Variables]] - Store API keys and secrets securely +- [[Scripts]] - Automate variable management with JavaScript +- [[API-Reference]] - Complete API for working with variables