Skip to main content

Architecture Overview

The node-fullykiosk library provides TypeScript-friendly React hooks that wrap the Fully Kiosk Browser JavaScript API. The architecture consists of three main layers:
  1. Detection Layer - Checks if the app is running inside Fully Kiosk Browser
  2. Hook Layer - React hooks that manage state and event subscriptions
  3. Event Binding Layer - Connects Fully Kiosk events to React state updates
All hooks are designed to fail gracefully when not running in Fully Kiosk Browser, returning undefined values instead of throwing errors.

The window.fully Object

Fully Kiosk Browser exposes a global fully object on the window that provides access to device functionality:
// Available globally when running in Fully Kiosk Browser
window.fully.getBatteryLevel(); // Returns battery percentage
window.fully.getDeviceName();   // Returns device name
window.fully.turnScreenOff();   // Turns screen off
The library wraps these functions in React hooks to provide:
  • Type safety via TypeScript definitions
  • Reactive updates when device state changes
  • Automatic cleanup when components unmount

Detection Mechanism

The library detects if Fully Kiosk is available using a simple check (src/utils/is_fully.ts:2):
export const is_fully = () => !!window['fully' as any];
This function is called at the start of every hook to determine if the Fully API is available.

Hook Interface Pattern

Each hook follows a consistent pattern for interfacing with the Fully Kiosk API:

Read-Only Hooks

Simple hooks that return device information:
// src/hooks/useOrientation.ts
export const useOrientation = () => {
    if (!is_fully()) return;
    
    return fully.getScreenOrientation();
};
import { useOrientation } from 'fullykiosk';

function DeviceInfo() {
    const orientation = useOrientation();
    
    return <div>Orientation: {orientation}°</div>;
}

Stateful Hooks with Events

Hooks that maintain state and listen to device events:
// src/hooks/useBatteryLevel.ts (simplified)
export const useBatteryLevel = () => {
    if (!is_fully()) return { batteryLevel: undefined };
    
    const [batteryLevel, setBatteryLevel] = useState<number>(
        fully.getBatteryLevel()
    );
    
    const vId = useMemo(() => Math.random().toString(36).slice(2, 11), []);
    
    useEffect(() => {
        // Store callback in global registry
        window._fullykiosk[vId] = () => {
            setBatteryLevel(fully.getBatteryLevel());
        };
        
        // Bind to Fully Kiosk event
        fully.bind('onBatteryLevelChanged', `_fullykiosk['${vId}']();`);
        
        // Cleanup on unmount
        return () => {
            window._fullykiosk[vId] = undefined;
        };
    }, []);
    
    return { batteryLevel };
};
The event binding mechanism requires generating unique IDs for each hook instance to prevent conflicts between multiple components using the same hook.

Control Hooks

Hooks that provide methods to control device features:
// src/hooks/useScreenBrightness.ts (simplified)
export const useScreenBrightness = () => {
    if (!is_fully()) {
        return {
            brightness: undefined,
            setBrightness: () => {}
        };
    }
    
    const [brightness, setLocalBrightness] = useState<number>(
        fully.getScreenBrightness()
    );
    
    const setBrightness = useCallback((brightness: number) => {
        fully.setScreenBrightness(brightness);
        setLocalBrightness(fully.getScreenBrightness());
    }, []);
    
    return { brightness, setBrightness };
};

Event Binding Mechanism

Fully Kiosk Browser uses a string-based event binding system. The library bridges this to React’s state management using the window._fullykiosk registry.

How Event Binding Works

  1. Create unique identifier for each hook instance:
    const vId = useMemo(() => Math.random().toString(36).slice(2, 11), []);
    
  2. Register callback in the global _fullykiosk object:
    window._fullykiosk[vId] = () => {
        setBatteryLevel(fully.getBatteryLevel());
    };
    
  3. Bind to Fully event using string code:
    fully.bind('onBatteryLevelChanged', `_fullykiosk['${vId}']();`);
    
  4. Cleanup on unmount:
    return () => {
        window._fullykiosk[vId] = undefined;
    };
    
// src/hooks/useKeyboard.ts
window._fullykiosk[vId] = (state: 'show' | 'hide') => {
    setKeyboardVisible(state === 'show');
};

fully.bind('hideKeyboard', `_fullykiosk['${vId}']('hide');`);
fully.bind('showKeyboard', `_fullykiosk['${vId}']('show');`);

The _fullykiosk Registry Pattern

The window._fullykiosk object serves as a callback registry initialized in each hook:
window._fullykiosk = window._fullykiosk || {};
This pattern allows:
  • Multiple hook instances to coexist without conflicts
  • Safe cleanup when components unmount
  • Bridge between Fully’s string-based events and React callbacks
The random ID generation ensures each hook instance has a unique namespace in the global registry, preventing event handler collisions.

TypeScript Type Safety

The library provides comprehensive TypeScript definitions for the entire Fully Kiosk API in src/global.d.ts.

Global Type Declarations

declare global {
    interface Window {
        _fullykiosk: {
            [key: string]: any;
        };
    }
    
    let fully: {
        getBatteryLevel(): number;
        getScreenBrightness(): number;
        isWifiEnabled(): boolean;
        // ... 100+ more methods
    };
}
These type definitions provide:
  • Autocomplete for all Fully Kiosk methods
  • Type checking for parameters and return values
  • Version comments indicating API availability (e.g., “ver. 1.44+”)
  • Parameter documentation for complex methods

Example with Full Type Safety

import { useScreenBrightness } from 'fullykiosk';

function BrightnessControl() {
    const { brightness, setBrightness } = useScreenBrightness();
    //    ^? number | undefined
    //                  ^? (brightness: number) => void
    
    // TypeScript knows brightness is a number between 0-255
    return (
        <div>
            <span>Current: {brightness}</span>
            <button onClick={() => setBrightness(128)}>50%</button>
        </div>
    );
}

Error Handling

Graceful Degradation

When not running in Fully Kiosk Browser, hooks return safe default values:
// Returns undefined instead of throwing
const { batteryLevel } = useBatteryLevel();
// batteryLevel will be undefined if not in Fully Kiosk

// Control functions become no-ops
const { brightness, setBrightness } = useScreenBrightness();
// setBrightness() does nothing if not in Fully Kiosk
This allows you to develop and test your React application in a normal browser without errors, with full functionality available when deployed to Fully Kiosk Browser.

Graceful Degradation

Hooks return safe defaults when Fully Kiosk is not available:
// src/hooks/useScreenSleep.ts
export const useScreenSleep = (config) => {
    if (!is_fully())
        return {
            isScreenOn: false,
            turnOff: () => {},
            turnOn: () => {},
            forceSleep: () => {},
            // ... other no-op functions
        };
    
    // Normal implementation when Fully is available
};
This pattern allows your React app to work in development mode without throwing errors.

Function Wrapping Pattern

The library includes a utility for wrapping Fully functions with automatic detection:
// src/utils/basic_hook.ts
export const wrapBasicFunction = <T, K>(fn: (...v: K[]) => T) => 
    (...v: K[]) => {
        if (!is_fully()) {
            return;
        }
        return fn(...v) as T;
    };
This utility ensures functions only execute when Fully is available.

Component Lifecycle

Understanding how hooks interact with React’s lifecycle:
function MyComponent() {
    // 1. Hook initializes on mount
    const { batteryLevel } = useBatteryLevel();
    //    - Checks is_fully()
    //    - Gets initial value
    //    - Registers event listener
    
    // 2. Component receives updates
    //    - Fully fires onBatteryLevelChanged
    //    - Callback updates React state
    //    - Component re-renders
    
    // 3. Cleanup on unmount
    //    - useEffect cleanup runs
    //    - Removes callback from _fullykiosk registry
    
    return <div>Battery: {batteryLevel}%</div>;
}
import { useKeyboard } from 'fullykiosk';

function KeyboardManager() {
    const { keyboardVisible, showKeyboard, hideKeyboard } = useKeyboard();
    
    useEffect(() => {
        console.log('Keyboard visibility changed:', keyboardVisible);
    }, [keyboardVisible]);
    
    return (
        <div>
            <p>Keyboard is {keyboardVisible ? 'visible' : 'hidden'}</p>
            <button onClick={showKeyboard}>Show</button>
            <button onClick={hideKeyboard}>Hide</button>
        </div>
    );
}

Build docs developers (and LLMs) love