API Integration Guide

This guide covers how to integrate our fingerprinting and open-end tracking features, and how to call our API using both JavaScript and React.

JavaScript Implementation

Step 1: Set Up Fingerprinting

Integrate our fingerprinting script using one of these methods:

  1. HTML link (preferred):

    <script src="https://roundtable.ai/js/fingerprinting.js"></script>
    
  2. Dynamic JavaScript insertion:

    const script = document.createElement('script');
    script.src = 'https://roundtable.ai/js/fingerprinting.js';
    document.head.appendChild(script);
    

The script adds fingerprint_id to session storage. Retrieve it with:

const fingerprintId = sessionStorage.getItem('fingerprint_id');

Step 2: Set Up Open-end Tracking

Use our JavaScript tracker to record changes in open-ended textboxes:

const textbox_id = 'exampleTextarea'

// Max number of characters to store in history 
const max_characters_for_history = 25000;

document.addEventListener('DOMContentLoaded', function () {

    // Text changes are stored here
    let question_history = [];
    let old_text = document.getElementById(textbox_id).value;
    let text_over_length = false;
    let start_time;

    // Handle input changes
    document.getElementById(textbox_id).addEventListener('input', function (e) {
        if (text_over_length) return;
        if (!start_time) {
            start_time = Date.now();
            t = 0;
        } else {
            t = Date.now() - start_time;
        }
        const state = e.target.value;
        const new_history = {
            s: state,
            t,
        };
        const length_of_history = JSON.stringify([...question_history, new_history]).length;
        if (length_of_history > max_characters_for_history) {
            text_over_length = true;
            return;
        }
        question_history.push(new_history);
        old_text = state;
    });

    // Handle copy changes
    document.getElementById(textbox_id).addEventListener('copy', function (e) {
        if (text_over_length) return;
        if (!start_time) {
            start_time = Date.now();
            t = 0;
        } else {
            t = Date.now() - start_time;
        }
        const new_history = {
            s: e.target.value,
            t,
            o: 'c',
            ct: window.getSelection().toString(),
        };
        const length_of_history = JSON.stringify([...question_history, new_history]).length;
        if (length_of_history > max_characters_for_history) {
            text_over_length = true;
            return;
        }
        question_history.push(new_history);
    });
});

Step 3: Call the API

Use the collected data to make an API call:

const apiData = {
  participant_id: "participant_123",
  question_histories: {
    Q1: question_history
  },
  questions: {
    Q1: "How do you feel our software has impacted your daily workflow?"
  },
  responses: {
    Q1: document.getElementById(textbox_id).value
  },
  survey_id: "survey_456",
  fingerprint_id: fingerprintId
};

fetch('https://api.roundtable.ai/alias/latest', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'api_key': '<api_key>'
  },
  body: JSON.stringify(apiData)
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

React Implementation

Step 1: Set Up Fingerprinting

Create a custom hook to load the fingerprinting script and retrieve the fingerprint_id:

import { useState, useEffect } from 'react';

const useFingerprint = () => {
    const [fingerprintId, setFingerprintId] = useState(null);

    useEffect(() => {
        const script = document.createElement('script');
        script.src = 'https://roundtable.ai/js/fingerprinting.js';
        script.async = true;
        script.onload = () => {
            setTimeout(() => {
                const id = sessionStorage.getItem('fingerprint_id');
                setFingerprintId(id);
            }, 100);
        };
        document.head.appendChild(script);

        return () => {
            document.head.removeChild(script);
        };
    }, []);

    return fingerprintId;
};

Step 2: Set Up Open-end Tracking

Create a custom hook to track changes in open-ended textboxes:

import { useState, useRef, useCallback } from 'react';

const useQuestionHistory = (maxCharactersForHistory = 25000) => {
    const [history, setHistory] = useState([]);
    const startTimeRef = useRef(null);
    const textOverLengthRef = useRef(false);

    const getTimeElapsed = useCallback(() => {
        if (!startTimeRef.current) {
            startTimeRef.current = Date.now();
            return 0;
        }
        return Date.now() - startTimeRef.current;
    }, []);

    const addHistoryEntry = useCallback((newEntry) => {
        if (textOverLengthRef.current) return;

        setHistory(prevHistory => {
            const updatedHistory = [...prevHistory, newEntry];
            const lengthOfHistory = JSON.stringify(updatedHistory).length;

            if (lengthOfHistory > maxCharactersForHistory) {
                textOverLengthRef.current = true;
                return prevHistory;
            }

            return updatedHistory;
        });
    }, [maxCharactersForHistory]);

    const handleInput = useCallback((event) => {
        const timeElapsed = getTimeElapsed();
        addHistoryEntry({
            s: event.target.value,
            t: timeElapsed
        });
    }, [getTimeElapsed, addHistoryEntry]);

    const handleCopy = useCallback((event) => {
        const timeElapsed = getTimeElapsed();
        addHistoryEntry({
            s: event.target.value,
            t: timeElapsed,
            o: 'c',
            ct: window.getSelection().toString()
        });
    }, [getTimeElapsed, addHistoryEntry]);

    return { history, handleInput, handleCopy };
};

Step 3: Create Open-end Component

import React from 'react';
import useQuestionHistory from './useQuestionHistory';

const OpenEndQuestion = ({ id, question }) => {
    const { history, handleInput, handleCopy } = useQuestionHistory();

    return (
        <div>
            <label htmlFor={id}>{question}</label>
            <textarea
                id={id}
                onChange={handleInput}
                onCopy={handleCopy}
            />
        </div>
    );
};

export default OpenEndQuestion;

Step 4: Set Up API Call

Create a custom function for making API calls:

const callApi = async (questionHistories, fingerprintId, responses) => {
    if (!fingerprintId || Object.keys(questionHistories).length === 0) {
        throw new Error('Missing required data');
    }

    const apiData = {
        participant_id: "participant_123", // Replace with actual participant ID
        question_histories: questionHistories,
        questions: {
            // Map of question IDs to question text
            // e.g., Q1: "How do you feel our software has impacted your daily workflow?"
        },
        responses: responses,
        survey_id: "survey_456", // Replace with actual survey ID
        fingerprint_id: fingerprintId
    };

    const response = await fetch('https://api.roundtable.ai/alias/latest', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'api_key': '<api_key>' // Replace with your actual API key
        },
        body: JSON.stringify(apiData)
    });

    if (!response.ok) {
        throw new Error('API call failed');
    }

    return await response.json();
};

Step 5: Implement in a Survey Component

import React, { useState } from 'react';
import OpenEndQuestion from './OpenEndQuestion';
import useFingerprint from './useFingerprint';

const Survey = () => {
    const [questionHistories, setQuestionHistories] = useState({});
    const [responses, setResponses] = useState({});
    const [isSubmitting, setIsSubmitting] = useState(false);
    const [error, setError] = useState(null);
    const [isCompleted, setIsCompleted] = useState(false);
    const fingerprintId = useFingerprint();

    const handleQuestionComplete = (id, history, response) => {
        setQuestionHistories(prev => ({ ...prev, [id]: history }));
        setResponses(prev => ({ ...prev, [id]: response }));
    };

    const handleSubmit = async () => {
        setIsSubmitting(true);
        setError(null);

        try {
            await callApi(questionHistories, fingerprintId, responses);
            setIsCompleted(true);
        } catch (err) {
            setError(err.message);
        } finally {
            setIsSubmitting(false);
        }
    };

    const questions = [
        { id: 'Q1', text: "How do you feel our software has impacted your daily workflow?" },
        { id: 'Q2', text: "What features would you like to see in future updates?" }
    ];

    return (
        <div>
            {questions.map(q => (
                <OpenEndQuestion
                    key={q.id}
                    id={q.id}
                    question={q.text}
                    onComplete={(history, response) => handleQuestionComplete(q.id, history, response)}
                />
            ))}
            <button onClick={handleSubmit} disabled={isSubmitting}>
                Submit Survey
            </button>
            {isSubmitting && <p>Submitting responses...</p>}
            {error && <p>Error: {error}</p>}
            {isCompleted && <p>Survey submitted successfully!</p>}
        </div>
    );
};

export default Survey;

This implementation:

  1. Sets up fingerprinting using a custom hook.
  2. Tracks open-end question responses and histories.
  3. Makes an API call when the submit button is clicked.

Remember to replace placeholder values (e.g. API key, participant ID, survey ID) with actual values in your implementation.