No description
Find a file
Vadym Samoilenko 67fcb017cc Prepare for production: remove hardcoded credentials and fix bugs
- Move service account credentials to .env (loaded server-side only)
- server.js: inject credentials in proxy, strip any client-provided creds,
  replace deprecated url.parse with new URL
- auth.js / dashboard.js: remove all hardcoded passwords from client code
- dashboard.js: remove broken category filter, fix redundant user.info call
  (use stored userId), add HTML escaping against XSS
- login.html: remove unused password field
- dashboard.html: remove broken category filter UI
- Add .gitignore to exclude .env and node_modules
- Add .env.example as configuration template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 20:17:49 +00:00
.env.example Prepare for production: remove hardcoded credentials and fix bugs 2026-03-11 20:17:49 +00:00
.gitignore Prepare for production: remove hardcoded credentials and fix bugs 2026-03-11 20:17:49 +00:00
api.js Add portal source files 2026-03-11 20:08:17 +00:00
auth.js Prepare for production: remove hardcoded credentials and fix bugs 2026-03-11 20:17:49 +00:00
CLAUDE.md Add portal source files 2026-03-11 20:08:17 +00:00
dashboard.html Prepare for production: remove hardcoded credentials and fix bugs 2026-03-11 20:17:49 +00:00
dashboard.js Prepare for production: remove hardcoded credentials and fix bugs 2026-03-11 20:17:49 +00:00
editor.html Add portal source files 2026-03-11 20:08:17 +00:00
login.html Prepare for production: remove hardcoded credentials and fix bugs 2026-03-11 20:17:49 +00:00
login.js Add portal source files 2026-03-11 20:08:17 +00:00
package.json Add portal source files 2026-03-11 20:08:17 +00:00
README.md Add portal source files 2026-03-11 20:08:17 +00:00
server.js Prepare for production: remove hardcoded credentials and fix bugs 2026-03-11 20:17:49 +00:00
styles.css Add portal source files 2026-03-11 20:08:17 +00:00

3M OMG Portal

A web-based portal for managing and editing One2Edit translation jobs with 3M branding.

Table of Contents


Overview

The 3M OMG Portal provides a streamlined interface for:

  • Dual authentication system with Oliver Login and 3M Login
  • External session management using One2Edit API session tokens
  • Dynamic user resolution via user.info API to resolve usernames to user IDs
  • Viewing and managing active translation jobs (STARTED and RUNNING)
  • Opening jobs in the One2Edit editor
  • Exporting jobs as PDFs (blob download with save dialog)
  • Tracking job progress with visual indicators
  • Managing multiple translation projects

System Architecture

┌──────────────────────────────────────────────────────────────────────────┐
│                          BROWSER (User Interface)                        │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   ┌──────────────┐      ┌──────────────┐      ┌──────────────┐         │
│   │ login.html   │─────>│dashboard.html│─────>│ editor.html  │         │
│   │              │      │              │      │              │         │
│   │ - Username   │      │ - Job List   │      │ - One2Edit   │         │
│   │ - Password   │      │ - Progress   │      │   SDK        │         │
│   │ - Oliver /   │      │ - Filters    │      │ - Job Editor │         │
│   │   3M Login   │      │ - PDF Export │      │ - Auto-close │         │
│   └──────┬───────┘      └──────┬───────┘      └──────┬───────┘         │
│          │                     │                     │                  │
│          │ auth.js             │ dashboard.js        │ (inline JS)     │
│          │                     │                     │                  │
└──────────┼─────────────────────┼─────────────────────┼──────────────────┘
           │                     │                     │
           │                     │                     │
           ▼                     ▼                     ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                    NODE.JS PROXY SERVER (server.js)                       │
│                         http://localhost:3000                             │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────────┐  │
│  │  Static File Server                                                │  │
│  │  - Serves HTML, CSS, JS, Images                                    │  │
│  │  - Routes '/' to login.html                                        │  │
│  └────────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────────┐  │
│  │  API Proxy (/api endpoint)                                         │  │
│  │  - Handles GET and POST requests                                   │  │
│  │  - Forwards requests to One2Edit API                               │  │
│  │  - Adds CORS headers to responses                                  │  │
│  │  - Logs requests/responses (passwords masked)                      │  │
│  │  - Converts 302/301 redirects to 401 errors                        │  │
│  └────────────────────────────────────────────────────────────────────┘  │
│                                                                          │
└──────────────────────────────────┬───────────────────────────────────────┘
                                   │
                                   │ HTTPS
                                   ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                        ONE2EDIT API (External)                           │
│                  https://oliver.one2edit.com/v3/Api.php                  │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Available Commands:                                                     │
│  ┌────────────────────────────────────────────────────────────────────┐  │
│  │  * user.info                  → Get user ID from username          │  │
│  │  * user.session.extern.add    → Create external session (login)    │  │
│  │  * user.session.extern.remove → Remove external session (logout)   │  │
│  │  * job.list                   → Get all translation jobs           │  │
│  │  * job.export.pdf             → Export job as PDF                  │  │
│  └────────────────────────────────────────────────────────────────────┘  │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│                         SESSION MANAGEMENT                               │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  sessionStorage (Browser):                                               │
│  ┌────────────────────────────────────────────────────────────────────┐  │
│  │  * isAuthenticated      → 'true' if logged in                      │  │
│  │  * username             → User's username                          │  │
│  │  * userId               → User ID (resolved from user.info API)    │  │
│  │  * externSessionId      → Session ID from extern.add response      │  │
│  │  * authConfig           → API credentials (JSON)                   │  │
│  └────────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  Session Flow:                                                           │
│  1. Login → user.info resolves username to userId                        │
│  2. Login → user.session.extern.add returns externSessionId              │
│  3. Dashboard → user.info again to get userId (credential-based auth)    │
│  4. Dashboard → job.list with credential-based auth + userId             │
│  5. Editor → externSessionId used directly as SDK sessionId              │
│  6. Logout → credential-based auth removes extern session                │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│                     AUTHENTICATION METHODS                               │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  The portal uses TWO authentication methods for API calls:               │
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────────┐  │
│  │  METHOD 1: Credential-Based (used by most API calls)               │  │
│  │  ─────────────────────────────────────────────────                  │  │
│  │  authUsername: 'portal@oliver.agency'                               │  │
│  │  authPassword: 'Sp1d3r26!'                                         │  │
│  │                                                                     │  │
│  │  Used by: user.info, user.session.extern.add,                      │  │
│  │           user.session.extern.remove, job.list                     │  │
│  │                                                                     │  │
│  └────────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────────┐  │
│  │  METHOD 2: Session-Based (used for PDF export and editor)          │  │
│  │  ──────────────────────────────────────────────────                 │  │
│  │  sessionId: [externSessionId from login]                           │  │
│  │                                                                     │  │
│  │  Used by: job.export.pdf, One2Edit SDK                              │  │
│  └────────────────────────────────────────────────────────────────────┘  │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│                          AUTHENTICATION FLOW                             │
└──────────────────────────────────────────────────────────────────────────┘

  User enters credentials
         │
         ▼
  ┌──────────────────────────────────┐
  │  User selects login type:        │
  │  - Oliver Login (black button)   │
  │  - 3M Login (red button)         │
  └────────┬─────────────────────────┘
           │
           ▼
  ┌─────────────────────────────────────┐
  │  Step 1: API Call: user.info       │
  │  - Credential-based auth            │
  │  - Resolves username → userId       │
  │  - clientId: 6                      │
  └────────┬────────────────────────────┘
           │
           ▼
  ┌─────────────────────────────────────┐
  │  Step 2: user.session.extern.add   │
  │  - Credential-based auth            │
  │  - Uses resolved userId             │
  │  - clientId: 7                      │
  └────────┬────────────────────────────┘
           │
           ▼
  ┌──────────────────────────────────┐
  │  API returns externSessionId     │
  │  (e.g., f88637e4d22be36...)      │
  └────────┬─────────────────────────┘
           │
           ▼
  ┌─────────────────────────────────────┐
  │  Store in sessionStorage:           │
  │  - isAuthenticated: 'true'          │
  │  - username (from form)             │
  │  - userId (from user.info)          │
  │  - externSessionId (from API)       │
  │  - authConfig (JSON)                │
  └────────┬────────────────────────────┘
           │
           ▼
  ┌──────────────────────────────────┐
  │  Redirect to dashboard.html      │
  └──────────────────────────────────┘

Project Structure

one2edit_Portal_v1/
├── server.js              # Node.js proxy server (CORS handling, GET + POST)
├── login.html             # Login page with dual authentication form
├── dashboard.html         # Main dashboard showing jobs and progress
├── editor.html            # One2Edit job editor page (SDK integration)
├── auth.js                # Authentication logic (user.info + extern session)
├── dashboard.js           # Dashboard functionality, job management, PDF export
├── login.js               # Login form handler (legacy, uses api.js)
├── api.js                 # API helper functions (legacy One2EditAPI + SessionManager)
├── styles.css             # All styling for the application
├── package.json           # Node.js dependencies and scripts
├── Images/                # Image assets
│   ├── login_logo.png     # 3M logo for login page and dashboard
│   └── 3M_Splash.jpg      # Background splash image for login
├── Example/               # Legacy reference implementation (Bootstrap demo)
├── Example2/              # Alternative reference implementation
├── V1/                    # Version 1 (earlier iteration)
├── V2/                    # Version 2 (earlier iteration)
├── v3/                    # Version 3 (earlier iteration)
├── v4/                    # Version 4 (most recent alternative)
└── README.md              # This file

File Descriptions

server.js (227 lines)

  • Node.js HTTP server running on port 3000
  • Serves static files (HTML, CSS, JS, images) with correct MIME types
  • Proxies both GET and POST API requests to One2Edit API
  • Routes / to login.html
  • Handles CORS preflight (OPTIONS) requests
  • Logs all API requests and responses (with password masking)
  • Converts 302/301 redirects to 401 errors

login.html (141 lines)

  • Login form with username and password fields
  • Two login buttons:
    • Oliver Login (black button) - Uses Oliver authentication
    • 3M Login (red button) - Uses 3M authentication
  • Terms of Use & Privacy Policy modal
  • Spinner feedback during authentication
  • Links to auth.js for form handling

auth.js (163 lines)

  • Two authentication configurations (both use portal@oliver.agency credentials):
  • Login flow (two-step):
    1. Calls user.info to resolve username → userId (clientId: 6)
    2. Calls user.session.extern.add with the resolved userId
  • Extracts externSessionId from API response
  • Stores session data in sessionStorage (isAuthenticated, username, userId, externSessionId, authConfig)
  • Redirects to dashboard on success

dashboard.html (87 lines)

  • Header bar with 3M logo, "Jobs" title, Refresh and Sign Out buttons
  • Overall progress section with aggregated progress bar
  • Category filter dropdown (All, Catalogue, Brochure, Flyer, Poster)
  • Jobs grid container
  • Loading, error, and empty state displays

dashboard.js (553 lines)

  • Checks authentication on page load
  • Calls user.info API with credential-based auth to resolve username → userId
  • Calls job.list with credential-based auth (authUsername/authPassword, NOT sessionId)
  • Filters for STARTED and RUNNING job statuses only
  • Parses XML responses into job objects
  • Creates job cards with:
    • Document preview thumbnails (base64)
    • Per-job progress bars (Done, Finished, Remaining)
    • Edit and PDF buttons
    • Lock indicators for opened jobs
  • PDF export via job.export.pdf using GET (session-based auth, id = jobId)
    • Popup shown immediately on click for instant user feedback
    • PDF button disabled with "Downloading..." text during request
    • Fetches blob, triggers browser download, re-enables button
  • Sign out via user.session.extern.remove (credential-based auth with externSessionId parameter)

editor.html (126 lines)

  • Loads One2Edit SDK from CDN (CSS + JS)
  • Gets jobId from URL parameters
  • Uses externSessionId directly as SDK sessionId (no separate user.auth call)
  • Initializes One2Edit editor with one2edit.create()
  • Removes toolbar buttons: search/replace, download PDF, rotate
  • Hides main menu bar
  • Returns to dashboard on editor close
  • Debug mode enabled

login.js (62 lines)

  • Legacy login form handler (uses api.js One2EditAPI and SessionManager)
  • Checks if user already authenticated, redirects to dashboard
  • Form submit handler with loading state

api.js (207 lines)

  • Legacy API helper module with:
    • One2EditAPI.authenticate() - Login via extern session
    • One2EditAPI.getEditorSession() - Get editor session
    • One2EditAPI.getJobList() - Fetch jobs
    • One2EditAPI.exportJobPDF() - Export PDF
    • SessionManager - Save/get/clear session data
  • Not actively used by the current login flow (auth.js handles login directly)

Setup and Installation

Prerequisites

  • Node.js (v12 or higher)
  • Modern web browser (Chrome, Firefox, Safari, Edge)
  • Internet connection (to access One2Edit API)

Installation Steps

  1. Navigate to the project directory:

    cd one2edit_Portal_v1
    
  2. Install dependencies:

    npm install
    
  3. Start the proxy server:

    npm start
    

    Or directly:

    node server.js
    

    You should see:

    🚀 One2Edit Portal Server Running!
    
    📱 Open your browser to: http://localhost:3000
    
    ✅ API proxy is handling CORS at: http://localhost:3000/api
    
    Press Ctrl+C to stop the server
    
  4. Open your browser and navigate to:

    http://localhost:3000
    

    IMPORTANT: Do NOT open the HTML files directly (file:// protocol). Always access through http://localhost:3000 to ensure the proxy server handles API calls.

Configuration

Authentication credentials are configured in auth.js with two separate configs:

const OLIVER_AUTH_CONFIG = {
    apiBaseUrl: '/api',
    authDomain: 'local',
    authUsername: 'portal@oliver.agency',
    authPassword: 'Sp1d3r26!',
    clientId: '7',
    userId: '9'
};

const TMM_AUTH_CONFIG = {
    apiBaseUrl: '/api',
    authDomain: 'local',
    authUsername: 'portal@oliver.agency',
    authPassword: 'Sp1d3r26!',
    clientId: '7',
    userId: '9'
};
  • Oliver Login button uses OLIVER_AUTH_CONFIG
  • 3M Login button uses TMM_AUTH_CONFIG
  • Both configs currently use the same portal@oliver.agency credentials
  • The userId: '9' in configs is overridden at runtime by the value resolved from user.info

To change credentials, edit auth.js:1-18.


How It Works

Complete User Flow

┌─────────────────────────────────────────────────────────────────┐
│  1. USER OPENS BROWSER                                          │
│     http://localhost:3000                                       │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. LOGIN PAGE (login.html + auth.js)                           │
│     - User enters username and password                         │
│     - Clicks "Oliver Login" OR "3M Login"                       │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│  3. USER RESOLUTION (auth.js)                                   │
│     - Call: user.info (credential-based, clientId: 6)           │
│     - Resolve entered username → numeric userId                 │
│     - Fail with error if user not found                         │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│  4. SESSION CREATION (auth.js)                                  │
│     - Call: user.session.extern.add (credential-based)          │
│     - Pass resolved userId from step 3                          │
│     - Extract externSessionId from XML response                 │
│     - Store session in sessionStorage                           │
│     - Redirect to dashboard.html                                │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────────┐
│  5. DASHBOARD (dashboard.html + dashboard.js)                   │
│     - Check authentication                                      │
│     - Call: user.info (credential-based, resolve userId again)  │
│     - Call: job.list (credential-based + userId)                │
│     - Parse XML response                                        │
│     - Display job cards with progress                           │
│     - Show overall progress bar                                 │
└────────────────────┬────────────────────────────────────────────┘
                     │
                     ├────────────────────────────────────┐
                     │                                    │
                     ▼                                    ▼
┌──────────────────────────────────┐    ┌──────────────────────────────────┐
│  6A. EDIT JOB (editor.html)      │    │  6B. EXPORT PDF (dashboard.js)   │
│     - Get jobId from URL          │    │     - Show popup immediately      │
│     - Use externSessionId as      │    │     - Disable PDF btn + show      │
│       SDK sessionId directly      │    │       "Downloading..." text       │
│     - Load One2Edit editor        │    │     - GET: job.export.pdf         │
│     - Remove toolbar buttons      │    │     - Session-based auth          │
│     - On close → Return to        │    │     - id = jobId (from job.list)  │
│       dashboard                   │    │     - Fetch blob → download       │
└───────────────────────────────────┘    │     - Re-enable PDF button        │
                                         └──────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│  7. SIGN OUT (dashboard.js)                                     │
│     - Call: user.session.extern.remove (credential-based auth)  │
│     - Pass externSessionId as parameter                         │
│     - Clear sessionStorage                                      │
│     - Redirect to login.html                                    │
└─────────────────────────────────────────────────────────────────┘

Data Flow Details

1. Login Process

User Input (username/password)
         │
         ▼
User Clicks "Oliver Login" or "3M Login"
         │
         ▼
auth.js selects OLIVER_AUTH_CONFIG or TMM_AUTH_CONFIG
         │
         ▼
Step 1: user.info (credential-based, clientId: 6)
         │
         ▼
API Returns userId (e.g., 9)
         │
         ▼
Step 2: user.session.extern.add (credential-based, clientId: 7)
         │
         ▼
API Returns externSessionId (e.g., f88637e4d22be361...)
         │
         ▼
sessionStorage Populated:
  - isAuthenticated: 'true'
  - username: [from form]
  - userId: [from user.info]
  - externSessionId: [from API]
  - authConfig: [selected config as JSON]
         │
         ▼
Dashboard Redirect

2. Dashboard Loading

Page Load → Check sessionStorage
     ↓
isAuthenticated = 'true'?
     │
     ├─ No → Redirect to Login
     │
     └─ Yes → Get username + authConfig from sessionStorage
              │
              ▼
         user.info API (credential-based, clientId: 6)
              │
              ▼
         Extract userId from XML response
              │
              ▼
         job.list API (credential-based, clientId: 7)
         ┌─ authUsername: portal@oliver.agency
         ├─ authPassword: Sp1d3r26!
         ├─ userId: [from user.info]
         ├─ status[0]: STARTED
         └─ status[1]: RUNNING
              │
              ▼
         Parse XML Response → Create Job Cards
              │
              ▼
         Update Overall Progress Bar

3. Job Editing

Click Edit → Navigate to editor.html?jobId=XXX
                    │
                    ▼
            Get externSessionId from sessionStorage
                    │
                    ▼
            one2edit.create({
                sessionId: externSessionId,
                clientId: authConfig.clientId,
                jobId: jobId
            })
                    │
                    ▼
            INITIALIZE event → Hide loading, hide menu
                    │
                    ▼
            EDITOR_INITIALIZE → Remove toolbar buttons
                    │
                    ▼
            User Makes Edits
                    │
                    ▼
            EDITOR_CLOSE → Return to Dashboard

4. PDF Export

Click PDF Button
         │
         ├─► Disable PDF button → Show "Downloading..."
         ├─► Show "PDF download requested" popup immediately
         │
         ▼
Find job in allJobs array → Get jobId
         │
         ▼
Build GET params:
┌─ command: job.export.pdf
├─ authDomain: local
├─ sessionId: externSessionId
├─ clientId: 7
├─ id: jobId (from job.list)
└─ result: file
         │
         ▼
GET /api?params → Receive blob response
         │
         ▼
Create blob URL → Trigger download
(filename: title_jobId.pdf)
         │
         ▼
Re-enable PDF button → Restore "PDF" text

API Documentation

All API calls are made to the One2Edit API v3:

https://oliver.one2edit.com/v3/Api.php

Proxied through:

http://localhost:3000/api

Authentication Methods

The portal uses two different authentication methods depending on the API call:

Method 1: Credential-Based Authentication (most API calls)

authDomain: 'local'
authUsername: 'portal@oliver.agency'
authPassword: 'Sp1d3r26!'

Used by: user.info, user.session.extern.add, user.session.extern.remove, job.list

Method 2: Session-Based Authentication (PDF export and SDK)

authDomain: 'local'
sessionId: '[externSessionId from login response]'

Used by: job.export.pdf, One2Edit SDK editor


1. user.info (Resolve Username to User ID)

Purpose: Resolve a username to a numeric userId

When Called:

  • During login (auth.js) before creating extern session
  • On dashboard load (dashboard.js) before loading jobs

Location: auth.js:42-50, dashboard.js:237-245

Parameters:

{
  command: 'user.info',
  authDomain: 'local',
  authUsername: 'portal@oliver.agency',
  authPassword: 'Sp1d3r26!',
  clientId: '6',
  username: '[entered username]',
  domain: 'local'
}

Authentication: Credential-based (authUsername/authPassword)

Response XML:

<user>
  <id>9</id>
  <name>Paul Johns</name>
  ...
</user>

Usage:

  • Extracts <id> from the response
  • Used as userId parameter in user.session.extern.add and job.list

2. user.session.extern.add (Login)

Purpose: Create an external session for portal authentication

When Called: User clicks "Oliver Login" or "3M Login" button (after user.info succeeds)

Location: auth.js:83-91

Parameters:

{
  command: 'user.session.extern.add',
  authDomain: 'local',
  authUsername: 'portal@oliver.agency',
  authPassword: 'Sp1d3r26!',
  clientId: '7',
  username: '[user input]',
  userId: '[resolved from user.info]'
}

Authentication: Credential-based (authUsername/authPassword)

Response XML:

<success>
  <session>
    <externSessionId>f88637e4d22be361821633a14125a2aa</externSessionId>
    <user>
      <id>9</id>
      <name>Paul Johns</name>
      <contact>pauljohns@oliver.agency</contact>
      <role>admin</role>
    </user>
  </session>
</success>

Success Action:

  • Extracts externSessionId from XML response
  • Stores session data in sessionStorage
  • Redirects to dashboard.html

Important: The externSessionId is returned by the API, not generated by the client.


3. job.list (Get Jobs)

Purpose: Retrieve active translation jobs for the user

When Called:

  • Dashboard page loads (after user.info)
  • User clicks "Refresh" button

Location: dashboard.js:278-294

Parameters:

{
  command: 'job.list',
  authDomain: 'local',
  authUsername: 'portal@oliver.agency',
  authPassword: 'Sp1d3r26!',
  clientId: '7',
  userId: '[resolved from user.info]',
  includeDocumentInfos: '1',
  includeDocumentPreviews: '1',
  includeDocumentWorkflows: '1',
  includeDocumentMetadata: '1',
  'status[0]': 'STARTED',
  'status[1]': 'RUNNING'
}

Authentication: Credential-based (authUsername/authPassword), NOT session-based

Response XML Structure:

<response>
  <job>
    <id>325</id>
    <name>Job Name</name>
    <status>STARTED|RUNNING</status>
    <isOpened>false</isOpened>
    <totalItems>789</totalItems>
    <allItems>789</allItems>
    <doneItems>200</doneItems>
    <finishItems>100</finishItems>
    <lastedit>2026-01-15 15:58:03</lastedit>
    <lastuser>
      <name>User Name</name>
    </lastuser>
    <document>
      <id>200</id>
      <version>Version Name</version>
      <name>Document Name</name>
      <pages>24</pages>
      <preview>[base64 encoded JPEG]</preview>
    </document>
    <translationLanguage>
      <name>Spanish</name>
    </translationLanguage>
  </job>
  <!-- More job elements... -->
</response>

Parsed Job Object:

{
  id: '325',
  documentId: '200',
  title: 'Version Name',
  documentName: 'Version Name',
  language: 'Spanish',
  pages: '24',
  status: 'STARTED',
  isOpened: false,
  totalItems: 789,
  doneItems: 200,
  finishItems: 100,
  lastUserName: 'User Name',
  lastEdit: '2026-01-15 15:58:03',
  thumbnail: 'data:image/jpeg;base64,...'
}

4. job.export.pdf (Export PDF)

Purpose: Export a job as PDF

When Called: User clicks "PDF" button on a job card

Location: dashboard.js:38-53

Method: GET

Parameters:

{
  command: 'job.export.pdf',
  authDomain: 'local',
  sessionId: '[externSessionId from login]',
  clientId: '7',
  id: '[jobId from job.list]',
  result: 'file'
}

Authentication: Session-based (sessionId = externSessionId)

UX Behaviour:

  1. "PDF download requested" popup shown immediately on click
  2. PDF button disabled and text changes to "Downloading..." during request
  3. GET request sent to /api with query parameters
  4. PDF received as blob response
  5. Browser download triggered via temporary link element
  6. Filename format: {title}_{jobId}.pdf
  7. PDF button re-enabled and text restored to "PDF"
  8. On error: button re-enabled, alert shown

5. One2Edit SDK (Open Editor)

Purpose: Load and configure the One2Edit editor

When Called: User clicks "Edit" button on a job card

Location: editor.html:80-89

Configuration:

one2edit.create({
  flashvars: {
    server: 'https://oliver.one2edit.com/',
    sessionId: externSessionId,    // Uses externSessionId directly
    clientId: parseInt(authConfig.clientId),
    jobEditor: {
      jobId: parseInt(jobId)
    }
  }
});

Important: The editor uses the externSessionId directly as the SDK sessionId. No separate user.auth call is needed.

Events:

  • INITIALIZE: Hides loading message, hides menu bar via one2edit.editor.showMenu(false)
  • EDITOR_INITIALIZE: Removes toolbar buttons (search/replace, download PDF, rotate)
  • EDITOR_CLOSE: Returns to dashboard via window.location.href
  • EDITOR_ERROR: Displays error message

Customizations:

  • Removes toolbar buttons: toolbar_tool_search_replace, toolbar_download_pdf, toolbar_rotate
  • Hides main menu bar
  • Debug mode enabled (one2edit.debug = true)

6. user.session.extern.remove (Logout)

Purpose: Remove external session on sign out

When Called: User clicks "Sign Out" button

Location: dashboard.js:170-177

Parameters:

{
  command: 'user.session.extern.remove',
  authDomain: 'local',
  authUsername: 'portal@oliver.agency',
  authPassword: 'Sp1d3r26!',
  clientId: '7',
  externSessionId: '[externSessionId from sessionStorage]'
}

Authentication: Credential-based (authUsername/authPassword)

Note: The parameter name is externSessionId here (not sessionId), unlike job.export.pdf which uses sessionId.

Action:

  • Attempts to remove session via API
  • Clears sessionStorage (regardless of API response)
  • Redirects to login.html
  • Sign out always succeeds locally even if API fails

Session Management Details

Understanding externSessionId

IMPORTANT: This is a critical concept for understanding how authentication works in the portal.

┌──────────────────────────────────────────────────────────────────┐
│  1. API Response Field: externSessionId                          │
│     (Returned by user.session.extern.add)                        │
└────────────────────┬─────────────────────────────────────────────┘
                     │
                     ▼
┌──────────────────────────────────────────────────────────────────┐
│  2. sessionStorage Key: externSessionId                          │
│     (How we store it in the browser)                             │
└────────────────────┬─────────────────────────────────────────────┘
                     │
                     ▼
┌──────────────────────────────────────────────────────────────────┐
│  3. Used differently depending on the API call:                  │
│                                                                  │
│     job.export.pdf       → sessionId: [externSessionId]          │
│     One2Edit SDK         → sessionId: [externSessionId]          │
│     extern.remove        → externSessionId: [externSessionId]    │
│     job.list             → NOT USED (credential-based auth)      │
│     user.info            → NOT USED (credential-based auth)      │
└──────────────────────────────────────────────────────────────────┘

Key Points:

  • Most API calls use credential-based auth (authUsername/authPassword) and don't need the externSessionId
  • The PDF export (job.export.pdf) and SDK editor use the externSessionId as sessionId
  • The logout call passes it as externSessionId (its original name)

Features

Dashboard Features

Overall Progress Bar

  • Aggregates progress from all jobs
  • Shows Done, Finished, and Remaining items
  • Displays percentage completion and element counts

Job Cards

  • Document preview thumbnail (base64 from API)
  • Document version/name
  • Document ID
  • Translation language
  • Page count
  • Last user who edited
  • Last edit timestamp
  • Per-job progress bar with legend
  • Edit and PDF buttons

Lock Status

  • Jobs opened by other users show lock icon
  • Edit and PDF buttons disabled for locked jobs
  • Visual indication with disabled styling

Category Filter

  • Filter jobs by category (All, Catalogue, Brochure, Flyer, Poster)

Refresh Button

  • Reload jobs without page refresh
  • Maintains session

Sign Out Button

  • End session securely
  • Clear all session data
  • Return to login

Progress Bar Colors

  • Light Green (#81c784): Done items (translated but not approved)
  • Dark Green (#2e7d32): Finished items (approved/completed)
  • Yellow (#ffd54f): Remaining items (not yet translated)

PDF Export

  • Instant feedback: "PDF download requested" popup shown immediately on button click
  • Button state management: PDF button disabled and shows "Downloading..." during request
  • GET-based download: Fetches PDF as blob via job.export.pdf with session-based auth
  • Job-based export: Uses id: jobId from job.list (not document ID)
  • Browser save dialog: Triggers download with filename {title}_{jobId}.pdf
  • Auto re-enable: PDF button restored after download completes or on error

Security Features

  • Credential-based API authentication using portal service account
  • Dual authentication system (Oliver and 3M login options)
  • External session IDs generated by API (format: 32-character hex string)
  • Dynamic user resolution via user.info at login and dashboard load
  • Automatic session cleanup on sign out
  • Session validation on page load
  • Passwords masked in server logs

Troubleshooting

Common Issues

1. CORS Errors

Symptom:

Access to fetch at 'file:///api...' has been blocked by CORS policy

Cause: Opening HTML files directly (file:// protocol)

Solution:

  1. Start the server: node server.js
  2. Access via: http://localhost:3000
  3. Never open HTML files directly from file system

2. Login Fails

Symptoms:

  • Error message on login page
  • "User not found" error
  • Redirect or authentication failed error

Debugging Steps:

  1. Check server.js is running: lsof -ti:3000
  2. Check server.js console logs for API response
  3. Verify credentials in auth.js (lines 2-18)
  4. Check that user.info call succeeds (returns a valid user)
  5. Ensure API returns 200 status (not 302 redirect)
  6. Check network tab in browser DevTools

Common Causes:

  • Server not running
  • Incorrect credentials
  • Username not found in One2Edit system
  • API returns redirect (converted to 401 by proxy)

3. Jobs Not Loading

Symptoms:

  • Empty dashboard
  • Error message: "Failed to load jobs"
  • Loading message never disappears

Debugging Steps:

  1. Open browser console (F12)
  2. Check for JavaScript errors
  3. Check user.info API call succeeds (returns a valid userId)
  4. Check server.js logs for job.list response
  5. Verify session data in sessionStorage (DevTools → Application → Session Storage)

Common Causes:

  • Session expired
  • user.info call failed (incorrect username or credentials)
  • userId not found for the given username
  • API error
  • No jobs available for user

4. Editor Not Opening

Symptoms:

  • "Loading editor..." message never disappears
  • Error loading editor message
  • Blank page

Debugging Steps:

  1. Check browser console for One2Edit SDK errors
  2. Verify externSessionId exists in sessionStorage
  3. Verify jobId is passed in URL
  4. Check that the One2Edit SDK scripts loaded from CDN
  5. Check server.js logs for any proxy errors

Common Causes:

  • Session expired (externSessionId invalid)
  • Invalid jobId
  • One2Edit SDK failed to load from CDN
  • Network connectivity issues

5. PDF Export Fails

Symptoms:

  • PDF button shows "Downloading..." but download never starts
  • Error in console: "Failed to export PDF"
  • Alert message after clicking PDF

Debugging Steps:

  1. Open browser console and check for errors
  2. Verify the job ID is correct (check allJobs array in console)
  3. Check server.js logs for the GET request/response
  4. Ensure externSessionId is still valid
  5. Check network tab for the GET request to /api?command=job.export.pdf...

Common Causes:

  • externSessionId expired
  • Invalid job ID
  • API error (check server terminal for response body)
  • Network timeout for large documents

6. Server Won't Start

Symptoms:

Error: listen EADDRINUSE :::3000

Cause: Port 3000 already in use

Solution:

# Find process using port 3000
lsof -ti:3000

# Kill the process
kill -9 $(lsof -ti:3000)

# Or change port in server.js
const PORT = 3001;  // Use different port

Debug Mode

To enable detailed logging:

  1. Server Logs: Already enabled in server.js

    • All API requests logged (passwords masked)
    • Response status, length, and body (first 500 chars) logged
  2. Browser Console:

    • Open DevTools (F12)
    • Check Console tab for errors
    • Check Network tab for API calls
  3. One2Edit SDK Debug:

    • Already enabled in editor.html
    • one2edit.debug = true;

Browser Compatibility

Tested and Working:

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

Requirements:

  • JavaScript enabled
  • Modern ES6 support (async/await, URLSearchParams, fetch)
  • sessionStorage enabled
  • Blob API support (for PDF downloads)

API Version

This portal uses One2Edit API v3:

https://oliver.one2edit.com/v3/Api.php

Note: v4 API was tested but caused redirect issues (302 responses). v3 is stable and working correctly with this portal.


Support and Maintenance

Logs Location

  • Server Logs: Console output where node server.js is running
  • Browser Logs: Browser DevTools Console (F12)
  • API Responses: Logged in server console (first 500 chars)

Session Data

View session data in browser:

  1. Open DevTools (F12)
  2. Go to Application tab
  3. Select Session Storage → http://localhost:3000
  4. View stored keys:
    • isAuthenticated
    • username
    • userId
    • externSessionId
    • authConfig

Stopping the Server

Press Ctrl+C in the terminal where server.js is running


Summary

The 3M OMG Portal is a three-tier web application:

  1. Frontend (HTML/CSS/JS) - User interface with dual login system
  2. Proxy Server (Node.js) - CORS handling and API forwarding (GET + POST)
  3. Backend (One2Edit API v3) - Data and authentication

Key Points:

  • Always access via http://localhost:3000
  • Server must be running for API calls to work
  • Dual login system: Oliver Login (black) or 3M Login (red)
  • Two-step login: user.info (resolve userId) → user.session.extern.add (create session)
  • Credential-based auth for most API calls (portal@oliver.agency)
  • Session-based auth for PDF export and SDK editor (externSessionId as sessionId)
  • Session data stored in browser's sessionStorage
  • Job filtering: Only STARTED and RUNNING jobs displayed
  • PDF export: job.export.pdf via GET, instant popup feedback, button disabled during download
  • Editor: Uses externSessionId directly (no separate user.auth call)
  • CORS issues handled by proxy server

Workflow: Select Login Type → Enter Credentials → View Active Jobs → Edit/Export → Sign Out

Authentication Summary:

┌────────────────────────────────────────────────────────────────┐
│  API Call                        │ Auth Method                  │
├──────────────────────────────────┼──────────────────────────────┤
│  user.info                       │ Credential (clientId: 6)     │
│  user.session.extern.add         │ Credential (clientId: 7)     │
│  user.session.extern.remove      │ Credential (clientId: 7)     │
│  job.list                        │ Credential (clientId: 7)     │
│  job.export.pdf                  │ Session (externSessionId)    │
│  One2Edit SDK                    │ Session (externSessionId)    │
└────────────────────────────────────────────────────────────────┘