wiki-plugin-linkitylink 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -0
- package/factory.json +5 -0
- package/index.js +3 -0
- package/package.json +30 -0
- package/server/server.js +242 -0
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# wiki-plugin-linkitylink
|
|
2
|
+
|
|
3
|
+
Federated Wiki plugin that integrates Linkitylink - a privacy-first link page service.
|
|
4
|
+
|
|
5
|
+
## What This Plugin Does
|
|
6
|
+
|
|
7
|
+
This plugin integrates Linkitylink with Federated Wiki by:
|
|
8
|
+
- **Launching a dedicated linkitylink instance** for each wiki
|
|
9
|
+
- **Auto-configuring** that instance with the wiki's base URLs
|
|
10
|
+
- **Proxying requests** between wiki and linkitylink
|
|
11
|
+
- **Managing the lifecycle** of the linkitylink service
|
|
12
|
+
|
|
13
|
+
Each wiki gets its own linkitylink instance, enabling true forking and independent operation.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### 1. Install the plugin
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# In your wiki's node_modules directory
|
|
21
|
+
cd /path/to/wiki/node_modules
|
|
22
|
+
git clone https://github.com/planet-nine-app/wiki-plugin-linkitylink.git
|
|
23
|
+
cd wiki-plugin-linkitylink
|
|
24
|
+
npm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Install linkitylink
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Clone linkitylink where the plugin can find it
|
|
31
|
+
cd /path/to/planet-nine
|
|
32
|
+
git clone https://github.com/planet-nine-app/linkitylink.git
|
|
33
|
+
cd linkitylink
|
|
34
|
+
npm install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Use in your wiki
|
|
38
|
+
|
|
39
|
+
Create a page with a linkitylink item to trigger plugin loading. The plugin will automatically:
|
|
40
|
+
- Launch its own linkitylink instance
|
|
41
|
+
- Configure it with your wiki's base URLs
|
|
42
|
+
- Start proxying requests
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"type": "linkitylink",
|
|
47
|
+
"id": "unique-id",
|
|
48
|
+
"text": "My Link Page"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Routes
|
|
53
|
+
|
|
54
|
+
All routes are proxied from the wiki to the Linkitylink service:
|
|
55
|
+
|
|
56
|
+
- `/plugin/linkitylink/` → Linkitylink homepage
|
|
57
|
+
- `/plugin/linkitylink/create` → Create link page
|
|
58
|
+
- `/plugin/linkitylink/view/:emojicode` → View link page by emojicode
|
|
59
|
+
- `/plugin/linkitylink/t/:alphanumeric` → View link page by alphanumeric ID
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
### Environment Variables
|
|
64
|
+
|
|
65
|
+
**Required for multiple wikis on same machine:**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Wiki 1
|
|
69
|
+
export LINKITYLINK_PORT=3010
|
|
70
|
+
export LINKITYLINK_PATH=/path/to/linkitylink
|
|
71
|
+
wiki --port 3000
|
|
72
|
+
|
|
73
|
+
# Wiki 2 (different terminal)
|
|
74
|
+
export LINKITYLINK_PORT=3011
|
|
75
|
+
export LINKITYLINK_PATH=/path/to/linkitylink-copy
|
|
76
|
+
wiki --port 3001
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Variables:**
|
|
80
|
+
- `LINKITYLINK_PORT` - Port for this wiki's linkitylink instance (default: 3010)
|
|
81
|
+
- `LINKITYLINK_PATH` - Path to linkitylink installation (default: `../../linkitylink` relative to plugin)
|
|
82
|
+
|
|
83
|
+
### Base URLs (owner.json)
|
|
84
|
+
|
|
85
|
+
The plugin automatically configures linkitylink with base URLs from `~/.wiki/status/owner.json`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"fountURL": "http://localhost:3006",
|
|
90
|
+
"bdoURL": "http://localhost:3003",
|
|
91
|
+
"addieURL": "http://localhost:3005"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**For wiki federation, each wiki's owner.json should point to its own base:**
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
// Wiki A's owner.json
|
|
99
|
+
{
|
|
100
|
+
"fountURL": "http://base-a.example.com/plugin/allyabase/fount",
|
|
101
|
+
"bdoURL": "http://base-a.example.com/plugin/allyabase/bdo",
|
|
102
|
+
"addieURL": "http://base-a.example.com/plugin/allyabase/addie"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Wiki B's owner.json
|
|
106
|
+
{
|
|
107
|
+
"fountURL": "http://base-b.example.com/plugin/allyabase/fount",
|
|
108
|
+
"bdoURL": "http://base-b.example.com/plugin/allyabase/bdo",
|
|
109
|
+
"addieURL": "http://base-b.example.com/plugin/allyabase/addie"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## How It Works
|
|
114
|
+
|
|
115
|
+
### Plugin Startup Flow
|
|
116
|
+
|
|
117
|
+
1. **Plugin loads** when wiki page uses linkitylink type
|
|
118
|
+
2. **Reads configuration** from `~/.wiki/status/owner.json`
|
|
119
|
+
3. **Checks for running linkitylink** on configured port
|
|
120
|
+
4. **If not running:**
|
|
121
|
+
- Spawns linkitylink as child process
|
|
122
|
+
- Sets environment variables (PORT, FOUNT_BASE_URL, BDO_BASE_URL, ADDIE_BASE_URL)
|
|
123
|
+
- Waits for startup (3 seconds)
|
|
124
|
+
5. **If already running:**
|
|
125
|
+
- Sends configuration update via `POST /config`
|
|
126
|
+
6. **Creates proxy** for all `/plugin/linkitylink/*` routes
|
|
127
|
+
|
|
128
|
+
### Request Flow
|
|
129
|
+
|
|
130
|
+
1. User visits `http://your-wiki.com/plugin/linkitylink/view/🔗💎...`
|
|
131
|
+
2. Plugin strips `/plugin/linkitylink` prefix
|
|
132
|
+
3. Proxies to `http://localhost:{LINKITYLINK_PORT}/view/🔗💎...`
|
|
133
|
+
4. Linkitylink fetches data from configured base URLs
|
|
134
|
+
5. Returns beautiful SVG page to wiki
|
|
135
|
+
|
|
136
|
+
### Architecture Benefits
|
|
137
|
+
|
|
138
|
+
- **Independent Instances** - Each wiki runs its own linkitylink
|
|
139
|
+
- **Isolated Bases** - Wiki A connects to Base A, Wiki B connects to Base B
|
|
140
|
+
- **True Forking** - Fork a wiki page, get independent linkitylink data
|
|
141
|
+
- **Process Management** - Plugin manages linkitylink lifecycle
|
|
142
|
+
- **Automatic Configuration** - No manual setup required
|
|
143
|
+
|
|
144
|
+
## Dependencies
|
|
145
|
+
|
|
146
|
+
- `http-proxy`: ^1.18.1 - For proxying requests to Linkitylink service
|
|
147
|
+
- `node-fetch`: ^2.6.1 - For configuring linkitylink via HTTP
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
package/factory.json
ADDED
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wiki-plugin-linkitylink",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Linkitylink integration plugin for federated wiki",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wiki",
|
|
7
|
+
"federated",
|
|
8
|
+
"linkitylink",
|
|
9
|
+
"planet-nine"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/planet-nine-app/wiki-plugin-linkitylink#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/planet-nine-app/wiki-plugin-linkitylink/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/planet-nine-app/wiki-plugin-linkitylink.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "planetnineisaspaceship",
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"main": "index.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"http-proxy": "^1.18.1",
|
|
28
|
+
"node-fetch": "^2.6.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/server/server.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const httpProxy = require('http-proxy');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fetch = require('node-fetch');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
|
|
9
|
+
// Linkitylink configuration
|
|
10
|
+
const LINKITYLINK_PORT = process.env.LINKITYLINK_PORT || 3010;
|
|
11
|
+
const LINKITYLINK_PATH = process.env.LINKITYLINK_PATH || path.join(__dirname, '../../../linkitylink');
|
|
12
|
+
|
|
13
|
+
let linkitylinkProcess = null;
|
|
14
|
+
|
|
15
|
+
// Function to load wiki's owner.json for base configuration
|
|
16
|
+
function loadWikiConfig() {
|
|
17
|
+
try {
|
|
18
|
+
const ownerPath = path.join(process.env.HOME || '/root', '.wiki/status/owner.json');
|
|
19
|
+
if (fs.existsSync(ownerPath)) {
|
|
20
|
+
const ownerData = JSON.parse(fs.readFileSync(ownerPath, 'utf8'));
|
|
21
|
+
|
|
22
|
+
// Extract base URLs from owner.json
|
|
23
|
+
// Default to localhost if not specified
|
|
24
|
+
return {
|
|
25
|
+
fountURL: ownerData.fountURL || 'http://localhost:3006',
|
|
26
|
+
bdoURL: ownerData.bdoURL || 'http://localhost:3003',
|
|
27
|
+
addieURL: ownerData.addieURL || 'http://localhost:3005'
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
console.warn('[wiki-plugin-linkitylink] No owner.json found, using localhost defaults');
|
|
31
|
+
return {
|
|
32
|
+
fountURL: 'http://localhost:3006',
|
|
33
|
+
bdoURL: 'http://localhost:3003',
|
|
34
|
+
addieURL: 'http://localhost:3005'
|
|
35
|
+
};
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('[wiki-plugin-linkitylink] Error loading wiki config:', err);
|
|
38
|
+
return {
|
|
39
|
+
fountURL: 'http://localhost:3006',
|
|
40
|
+
bdoURL: 'http://localhost:3003',
|
|
41
|
+
addieURL: 'http://localhost:3005'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Function to check if linkitylink is running
|
|
47
|
+
async function checkLinkitylinkRunning() {
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(`http://localhost:${LINKITYLINK_PORT}/config`, {
|
|
50
|
+
method: 'GET',
|
|
51
|
+
timeout: 2000
|
|
52
|
+
});
|
|
53
|
+
return response.ok;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Function to launch linkitylink service
|
|
60
|
+
async function launchLinkitylink(wikiConfig) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
console.log('[wiki-plugin-linkitylink] 🚀 Launching linkitylink service...');
|
|
63
|
+
console.log(`[wiki-plugin-linkitylink] Path: ${LINKITYLINK_PATH}`);
|
|
64
|
+
console.log(`[wiki-plugin-linkitylink] Port: ${LINKITYLINK_PORT}`);
|
|
65
|
+
|
|
66
|
+
// Check if linkitylink directory exists
|
|
67
|
+
if (!fs.existsSync(LINKITYLINK_PATH)) {
|
|
68
|
+
console.error(`[wiki-plugin-linkitylink] ❌ Linkitylink not found at ${LINKITYLINK_PATH}`);
|
|
69
|
+
console.error('[wiki-plugin-linkitylink] Set LINKITYLINK_PATH environment variable to the correct location');
|
|
70
|
+
return reject(new Error('Linkitylink directory not found'));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check for server.js
|
|
74
|
+
const serverPath = path.join(LINKITYLINK_PATH, 'server.js');
|
|
75
|
+
if (!fs.existsSync(serverPath)) {
|
|
76
|
+
console.error(`[wiki-plugin-linkitylink] ❌ server.js not found at ${serverPath}`);
|
|
77
|
+
return reject(new Error('Linkitylink server.js not found'));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Set environment variables for this linkitylink instance
|
|
81
|
+
const env = {
|
|
82
|
+
...process.env,
|
|
83
|
+
PORT: LINKITYLINK_PORT.toString(),
|
|
84
|
+
FOUNT_BASE_URL: wikiConfig.fountURL,
|
|
85
|
+
BDO_BASE_URL: wikiConfig.bdoURL,
|
|
86
|
+
ADDIE_BASE_URL: wikiConfig.addieURL
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Spawn linkitylink process
|
|
90
|
+
linkitylinkProcess = spawn('node', ['server.js'], {
|
|
91
|
+
cwd: LINKITYLINK_PATH,
|
|
92
|
+
env: env,
|
|
93
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Log stdout
|
|
97
|
+
linkitylinkProcess.stdout.on('data', (data) => {
|
|
98
|
+
const lines = data.toString().split('\n').filter(line => line.trim());
|
|
99
|
+
lines.forEach(line => {
|
|
100
|
+
console.log(`[linkitylink:${LINKITYLINK_PORT}] ${line}`);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Log stderr
|
|
105
|
+
linkitylinkProcess.stderr.on('data', (data) => {
|
|
106
|
+
console.error(`[linkitylink:${LINKITYLINK_PORT}] ERROR: ${data.toString().trim()}`);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Handle process exit
|
|
110
|
+
linkitylinkProcess.on('exit', (code, signal) => {
|
|
111
|
+
console.log(`[wiki-plugin-linkitylink] Linkitylink process exited (code: ${code}, signal: ${signal})`);
|
|
112
|
+
linkitylinkProcess = null;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
linkitylinkProcess.on('error', (err) => {
|
|
116
|
+
console.error('[wiki-plugin-linkitylink] ❌ Failed to start linkitylink:', err);
|
|
117
|
+
reject(err);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Wait a bit for the service to start
|
|
121
|
+
setTimeout(async () => {
|
|
122
|
+
const isRunning = await checkLinkitylinkRunning();
|
|
123
|
+
if (isRunning) {
|
|
124
|
+
console.log('[wiki-plugin-linkitylink] ✅ Linkitylink service started successfully');
|
|
125
|
+
resolve();
|
|
126
|
+
} else {
|
|
127
|
+
console.error('[wiki-plugin-linkitylink] ⚠️ Linkitylink may not have started correctly');
|
|
128
|
+
resolve(); // Don't reject, let it try to work anyway
|
|
129
|
+
}
|
|
130
|
+
}, 3000);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Function to configure linkitylink with base URLs
|
|
135
|
+
async function configureLinkitylink(config) {
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetch(`http://localhost:${LINKITYLINK_PORT}/config`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
'Content-Type': 'application/json'
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify(config)
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const result = await response.json();
|
|
146
|
+
if (result.success) {
|
|
147
|
+
console.log('[wiki-plugin-linkitylink] ✅ Linkitylink configured with base URLs:');
|
|
148
|
+
console.log(` Fount: ${result.config.fountURL}`);
|
|
149
|
+
console.log(` BDO: ${result.config.bdoURL}`);
|
|
150
|
+
console.log(` Addie: ${result.config.addieURL}`);
|
|
151
|
+
} else {
|
|
152
|
+
console.error('[wiki-plugin-linkitylink] ❌ Failed to configure linkitylink:', result.error);
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error('[wiki-plugin-linkitylink] ❌ Error configuring linkitylink:', err.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function startServer(params) {
|
|
160
|
+
const app = params.app;
|
|
161
|
+
|
|
162
|
+
console.log('🔗 wiki-plugin-linkitylink starting...');
|
|
163
|
+
console.log(`📍 Linkitylink service on port ${LINKITYLINK_PORT}`);
|
|
164
|
+
|
|
165
|
+
// Load wiki configuration
|
|
166
|
+
const wikiConfig = loadWikiConfig();
|
|
167
|
+
|
|
168
|
+
// Check if linkitylink is already running
|
|
169
|
+
const isRunning = await checkLinkitylinkRunning();
|
|
170
|
+
|
|
171
|
+
if (!isRunning) {
|
|
172
|
+
console.log('[wiki-plugin-linkitylink] Linkitylink not detected, attempting to launch...');
|
|
173
|
+
try {
|
|
174
|
+
await launchLinkitylink(wikiConfig);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.error('[wiki-plugin-linkitylink] ❌ Failed to launch linkitylink:', err.message);
|
|
177
|
+
console.error('[wiki-plugin-linkitylink] You may need to start linkitylink manually');
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
console.log('[wiki-plugin-linkitylink] ✅ Linkitylink already running, configuring...');
|
|
181
|
+
await configureLinkitylink(wikiConfig);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Create proxy server
|
|
185
|
+
const proxy = httpProxy.createProxyServer({});
|
|
186
|
+
|
|
187
|
+
// Handle proxy errors
|
|
188
|
+
proxy.on('error', function(err, req, res) {
|
|
189
|
+
console.error('[LINKITYLINK PROXY ERROR]', err.message);
|
|
190
|
+
|
|
191
|
+
// Return JSON error response
|
|
192
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
193
|
+
res.end(JSON.stringify({
|
|
194
|
+
error: 'Linkitylink service not available',
|
|
195
|
+
message: err.message,
|
|
196
|
+
hint: 'Is linkitylink running on port ' + LINKITYLINK_PORT + '?'
|
|
197
|
+
}));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Log proxy requests
|
|
201
|
+
proxy.on('proxyReq', function(proxyReq, req, res, options) {
|
|
202
|
+
console.log(`[LINKITYLINK PROXY] ${req.method} ${req.url} -> http://localhost:${LINKITYLINK_PORT}${req.url}`);
|
|
203
|
+
|
|
204
|
+
// If body was parsed by Express, restream it
|
|
205
|
+
if (req.body && Object.keys(req.body).length > 0) {
|
|
206
|
+
let bodyData = JSON.stringify(req.body);
|
|
207
|
+
proxyReq.setHeader('Content-Type', 'application/json');
|
|
208
|
+
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
|
|
209
|
+
proxyReq.write(bodyData);
|
|
210
|
+
proxyReq.end();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Proxy all linkitylink routes
|
|
215
|
+
// Maps /plugin/linkitylink/* -> http://localhost:3010/*
|
|
216
|
+
app.all('/plugin/linkitylink/*', function(req, res) {
|
|
217
|
+
// Remove /plugin/linkitylink prefix
|
|
218
|
+
const targetPath = req.url.replace('/plugin/linkitylink', '');
|
|
219
|
+
req.url = targetPath;
|
|
220
|
+
|
|
221
|
+
proxy.web(req, res, {
|
|
222
|
+
target: `http://localhost:${LINKITYLINK_PORT}`,
|
|
223
|
+
changeOrigin: true
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Also handle root plugin path -> linkitylink root
|
|
228
|
+
app.all('/plugin/linkitylink', function(req, res) {
|
|
229
|
+
req.url = '/';
|
|
230
|
+
proxy.web(req, res, {
|
|
231
|
+
target: `http://localhost:${LINKITYLINK_PORT}`,
|
|
232
|
+
changeOrigin: true
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
console.log('✅ wiki-plugin-linkitylink ready!');
|
|
237
|
+
console.log('📍 Routes:');
|
|
238
|
+
console.log(' /plugin/linkitylink/* -> http://localhost:' + LINKITYLINK_PORT + '/*');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = { startServer };
|
|
242
|
+
}).call(this);
|