Successfully Implemented farmOS Field Module System with FieldKit PWA

:tada: Successfully Implemented farmOS Field Module System with FieldKit PWA
Just got the farmOS Field Module system fully operational with our FieldKit Progressive Web App! This enables dynamic loading of custom field modules from farmOS into a mobile-friendly interface. Here’s how we made it work:

:building_construction: Architecture Overview
Components:

farmOS 3.x (Drupal 10) hosting Field Module JavaScript files
FieldKit PWA (Vue.js) - mobile-first field data collection interface
Custom REST endpoint in farmOS to list available modules
Dynamic module loading via window.lib.mountFieldModule()
Integration Flow:

FieldKit authenticates to farmOS via OAuth2
Fetches module list from /api/fieldkit/modules REST endpoint
Dynamically loads JavaScript from farmOS at runtime
Modules register themselves and appear in FieldKit navigation
:key: Key Implementation Details

  1. farmOS REST Resource Plugin
    Created custom REST resource at:

web/profiles/farm/modules/core/fieldkit/src/Plugin/rest/resource/FieldModuleListResource.php
Critical: Return full URL in uri field (not relative path):

<?php'uri' => $request->getSchemeAndHttpHost() . '/fieldkit/js/' . $module_id . '/index.js', 2. Field Module JavaScript Structure Modules live at /fieldkit/js/{module-name}/index.js: (function (mountFieldModule) { const config = { name: 'QR Scanner', routes: [{ name: 'qr-scanner', path: '/qr-scanner', component: { /* Vue component */ } }] }; mountFieldModule(config);})(window.lib?.mountFieldModule); 3. THE CRITICAL FIX: CORS Configuration 🚨 Problem: Browser blocked cross-origin script loading even though files were accessible (ERR_FAILED with 200 status). Solution: Configure CORS headers on farmOS nginx for /fieldkit/js/ path. For Plesk setups, edit: /var/www/vhosts/system/farmos.yourdomain.com/conf/vhost_nginx.conf Add: # CORS headers for FieldKit Field Module loadinglocation /fieldkit/js/ { add_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type' always; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type' always; add_header 'Content-Length' 0; add_header 'Content-Type' 'text/plain'; return 204; }} Then rebuild and reload: /usr/local/psa/admin/bin/httpdmng --reconfigure-domain farmos.yourdomain.comsystemctl reload nginx Verify CORS headers: curl -I -H "Origin: https://fieldkit.yourdomain.com" \ "https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js" | grep access-control 4. FieldKit Module Loading In FieldKit's module loader: // Use uri directly from API (full URL)const script = document.createElement('script');script.src = module.uri; // Already includes https://farmos.domain.com/...script.onload = () => resolve();script.onerror = () => reject(new Error(`Failed to load ${module.name}`));document.head.appendChild(script); 🐛 Common Pitfalls We Hit URL Doubling: Don't concatenate base URL if API already returns full URL Relative Paths: Returning /fieldkit/js/... instead of full https://... URL CORS Headers Missing: Most critical - browser blocks execution even though file downloads IIFE Syntax: Module code must be wrapped properly with mountFieldModule check ✅ Verification Steps Check API endpoint: curl -H "Authorization: Bearer YOUR_TOKEN" \ https://farmos.yourdomain.com/api/fieldkit/modules Should return: [{ "id": "qr-scanner", "name": "QR Scanner", "description": "Scan QR codes...", "uri": "https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js"}] Verify file accessibility: curl https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js Check CORS headers (critical!): curl -I -H "Origin: https://fieldkit.yourdomain.com" \ https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js Must see: access-control-allow-origin: https://fieldkit.yourdomain.com Browser test: Open FieldKit, check console - should see "✓ Module loaded successfully!" 📱 Result Working Field Module system with: ✅ Dynamic module discovery via REST API ✅ Runtime JavaScript loading from farmOS ✅ CORS properly configured ✅ Modules appearing in FieldKit navigation ✅ Full OAuth2 authentication integration First module: QR Scanner for asset identification in the field. 🔗 Resources farmOS REST API docs: https://farmos.org/development/api/ Field Module concept inspired by farmOS Field Kit (React version) Our implementation: Vue 3 + Vite PWA architecture Happy to share more implementation details if anyone's building similar integrations! The CORS configuration was the final puzzle piece that took ages to debug. 😅
1 Like

Thanks for sharing this @MiddleWorld! Unfortunately, it looks like the formatting was messed up (maybe you pasted from another source)? It’s a bit hard to read…

Sorry about the formatting on the original, can’t delet it and have another go or edit it?

Just got the farmOS Field Module system fully operational with our FieldKit Progressive Web App!
This enables dynamic loading of custom field modules from farmOS into a mobile-friendly interface.

Here’s how we made it work :backhand_index_pointing_down:


:building_construction: Architecture Overview

Components

  • farmOS 3.x (Drupal 10) hosting Field Module JavaScript files
  • FieldKit PWA (Vue.js) – mobile-first field data collection interface
  • Custom REST endpoint in farmOS to list available modules
  • Dynamic module loading via window.lib.mountFieldModule()

Integration Flow

  1. FieldKit authenticates to farmOS via OAuth2
  2. Fetches module list from /api/fieldkit/modules REST endpoint
  3. Dynamically loads JavaScript from farmOS at runtime
  4. Modules register themselves and appear in FieldKit navigation

:key: Key Implementation Details

1. farmOS REST Resource Plugin

Created a custom REST resource at:

web/profiles/farm/modules/core/fieldkit/src/Plugin/rest/resource/FieldModuleListResource.php

powershell
Copy code

Critical: return a full URL in the uri field (not a relative path):

'uri' => $request->getSchemeAndHttpHost()
  . '/fieldkit/js/' . $module_id . '/index.js',
2. Field Module JavaScript Structure
Modules live at:

bash
Copy code
/fieldkit/js/{module-name}/index.js
Each module must self-register:

js
Copy code
(function (mountFieldModule) {
  const config = {
    name: 'QR Scanner',
    routes: [
      {
        name: 'qr-scanner',
        path: '/qr-scanner',
        component: {
          /* Vue component */
        }
      }
    ]
  };

  mountFieldModule(config);
})(window.lib?.mountFieldModule);
🚨 THE CRITICAL FIX: CORS Configuration
❌ Problem
Browser blocked cross-origin script loading even though files were accessible
(ERR_FAILED with HTTP 200).

✅ Solution
Configure CORS headers on farmOS nginx for the /fieldkit/js/ path.

Plesk setup
Edit:

swift
Copy code
/var/www/vhosts/system/farmos.yourdomain.com/conf/vhost_nginx.conf
Add:

nginx
Copy code
# CORS headers for FieldKit Field Module loading
location /fieldkit/js/ {
  add_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always;
  add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
  add_header 'Access-Control-Allow-Headers' 'Content-Type' always;

  if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' 'https://fieldkit.yourdomain.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Content-Type' always;
    add_header 'Content-Length' 0;
    add_header 'Content-Type' 'text/plain';
    return 204;
  }
}
Rebuild and reload:

bash
Copy code
/usr/local/psa/admin/bin/httpdmng --reconfigure-domain farmos.yourdomain.com
systemctl reload nginx
Verify headers:

bash
Copy code
curl -I -H "Origin: https://fieldkit.yourdomain.com" \
https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js | grep access-control
3. FieldKit Module Loading
In FieldKit’s module loader:

js
Copy code
// Use uri directly from API (already a full URL)
const script = document.createElement('script');
script.src = module.uri;
script.onload = () => resolve();
script.onerror = () =>
  reject(new Error(`Failed to load ${module.name}`));

document.head.appendChild(script);
🐛 Common Pitfalls We Hit
URL doubling – don’t concatenate base URL if API already returns full URL

Relative paths – /fieldkit/js/... will break cross-origin loading

Missing CORS headers – browser blocks execution even if file downloads

IIFE syntax – module must be wrapped and call mountFieldModule safely

✅ Verification Steps
Check API endpoint
bash
Copy code
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://farmos.yourdomain.com/api/fieldkit/modules
Expected response:

json
Copy code
[
  {
    "id": "qr-scanner",
    "name": "QR Scanner",
    "description": "Scan QR codes...",
    "uri": "https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js"
  }
]
Verify file accessibility
bash
Copy code
curl https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js
Check CORS headers (critical!)
bash
Copy code
curl -I -H "Origin: https://fieldkit.yourdomain.com" \
https://farmos.yourdomain.com/fieldkit/js/qr-scanner/index.js
Must include:

arduino
Copy code
access-control-allow-origin: https://fieldkit.yourdomain.com
Browser test
Open FieldKit → DevTools console → should see:

lua
Copy code
✓ Module loaded successfully!
📱 Result
Working Field Module system with:

✅ Dynamic module discovery via REST API

✅ Runtime JavaScript loading from farmOS

✅ CORS properly configured

✅ Modules appearing in FieldKit navigation

✅ Full OAuth2 authentication integration

First module: QR Scanner for asset identification in the field.

🔗 Resources
farmOS REST API docs: https://farmos.org/development/api/

Field Module concept inspired by farmOS Field Kit (React version)

Our implementation: Vue 3 + Vite PWA

Happy to share more details if anyone’s building similar integrations —
the CORS config was the final puzzle piece that took ages to debug 😅

@MiddleWorld I’m a bit confused… Can you help me understand what you’re doing and why? Does this add new functionality or fix an issue that you encountered?

This reads a bit like it was AI-generated, and it’s hard to tell if it’s real or not… (no offense intended by this observation - but in this new age we do need to be wary of this, unfortunately).

I hope that you do have some valuable lessons to contribute! But I’m inclined to ask you to delete this post and start over. Because it’s apparent that you didn’t review your second comment before posting it either. It is also a mess of formatting.

Apart from the formatting it is just hard to follow the reasoning, and it sounds like you’re doing things that farmOS already does - like providing field module discovery. If you ran into an issue with that, we would like to know that first, because that would be a bug and you shouldn’t have to re-implement that in a different way.

I apologize if your intentions are good, and we welcome your participation here! But it’s hard to understand what you’re trying to communicate here.