Why Host Your Own Endpoint?

iCanText is designed to be radically decentralized. However, for users to find each other and initiate a connection, a public "rendezvous point" is necessary. This is the role of the signaling server, or "endpoint".

By default, iCanText uses the https://p.sayseeshow.io endpoint. While this service is minimalist and privacy-respecting (it logs nothing), the mere act of using it reveals to our servers that a connection attempt has occurred.

By deploying your own endpoint on a server you control, you achieve a higher level of confidentiality:

  • Absolute Privacy: The editor of iCanText (Say See Show) no longer has any knowledge, even technical, of the existence of your network or its members.
  • Total Sovereignty: Your community becomes a completely isolated and autonomous universe, with no external dependencies for its operation.
  • Increased Resilience: Your network is unaffected by a potential outage of the default signaling service.
  • Unlimited Scalability: You can create "federations" of communities, each with its own entry point, allowing iCanText to scale to millions of users without congestion points.

3-Step Deployment Guide

Step 1: Prerequisites

This guide is for deployment on a standard shared web hosting. You will need:

  • A web hosting provider that supports PHP (version 7.4 or higher).
  • The ability to create files and folders (via FTP or a file manager).
  • The ability to create or modify a .htaccess file (for Apache servers).

Step 2: File Creation

On your server, create the following folder and file structure. For example, at the root of your site https://your-domain.com/:

/ (your site's root)
├── canaux/             (This folder must have write permissions, e.g., 755)
├── canal.php           (The main script)
├── .htaccess           (The Apache configuration file)
├── cors-handler.php    (Handler for CORS requests)
└── cors_config.php     (Configuration for allowed domains)
						

Security Note: The CORS configuration below is secure because it only allows specific domains. You can edit the $allowedOrigins list in cors_config.php to add your own domains if you host a modified version of the iCanText application.

Contents of the cors_config.php file

This file centralizes the domains authorized to connect to your endpoint.

File: cors_config.php

<?php
// File: cors_config.php

$allowedOrigins = [
    'https://sayseeshow.io',
    'https://icansend.com',
    'https://icanphone.com',
    'https://icantext.com'
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';

if (in_array($origin, $allowedOrigins, true)) {
    header("Access-Control-Allow-Origin: $origin");
    header('Access-Control-Allow-Credentials: true');
}

Contents of the cors-handler.php file

This script handles "pre-flight" (OPTIONS) requests sent by browsers.

File: cors-handler.php

<?php
// File: cors-handler.php
// This script is called by .htaccess only for OPTIONS requests.

require_once __DIR__ . '/cors_config.php';

// OPTIONS (pre-flight) requests only need these headers.
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Max-Age: 86400');
header("HTTP/1.1 204 No Content");
exit(0);

Contents of the canal.php file

This PHP code uses the CORS configuration.

File: canal.php

<?php
/*
 * File-based WebRTC signaling endpoint.
 */
 
require_once __DIR__ . '/cors_config.php';

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    require __DIR__ . '/cors-handler.php';
    exit(0);
}

define('MAX_INPUT_SIZE', 15 * 1024);
define('MAX_FILE_SIZE', 150 * 1024);

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');

$fichier = $_GET['fichier'] ?? '';
if (preg_match('/^[a-zA-Z0-9_-]+$/', $fichier) !== 1) {
    header("HTTP/1.1 400 Bad Request");
    exit('Invalid channel name.');
}

$chemin_fichier = __DIR__ . '/canaux/' . basename($fichier);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $contenu = file_get_contents('php://input');

    if (strlen($contenu) > MAX_INPUT_SIZE) {
        header("HTTP/1.1 413 Payload Too Large");
        exit('Payload exceeds limit.');
    }

    if (!$contenu) {
        header("HTTP/1.1 400 Bad Request");
        exit('No content received.');
    }

    $fp = fopen($chemin_fichier, 'a');
    if (!$fp) {
        header("HTTP/1.1 500 Internal Server Error");
        exit('Could not open channel file.');
    }

    if (flock($fp, LOCK_EX)) {
        clearstatcache(true, $chemin_fichier);
        if (file_exists($chemin_fichier) && filesize($chemin_fichier) + strlen($contenu) > MAX_FILE_SIZE) {
            flock($fp, LOCK_UN);
            fclose($fp);
            header("HTTP/1.1 409 Conflict");
            exit('Channel is full.');
        }
        fwrite($fp, $contenu . "\n---\n");
        fflush($fp);
        flock($fp, LOCK_UN);
        fclose($fp);

        header("HTTP/1.1 201 Created");
        exit;
    } else {
        fclose($fp);
        header("HTTP/1.1 503 Service Unavailable");
        exit('Could not acquire lock.');
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    if (!file_exists($chemin_fichier)) {
        header("HTTP/1.1 404 Not Found");
        exit;
    }

    $fp = fopen($chemin_fichier, 'r');
    if (!$fp) {
        header("HTTP/1.1 500 Internal Server Error");
        exit('Could not open file.');
    }

    if (flock($fp, LOCK_EX)) {
        $contenu = stream_get_contents($fp);
        unlink($chemin_fichier);
        flock($fp, LOCK_UN);
        fclose($fp);

        header('Content-Type: text/plain; charset=utf-8');
        echo $contenu;
        exit;
    } else {
        fclose($fp);
        header("HTTP/1.1 503 Service Unavailable");
        exit('Could not acquire lock.');
    }
}

header("HTTP/1.1 405 Method Not Allowed");
exit;

Contents of the .htaccess file

This version of the .htaccess file correctly routes OPTIONS requests to the new handler.

File: .htaccess

RewriteEngine On
Options -Indexes

# 1. Capture the channel name in an environment variable.
RewriteRule ^canaux/([a-zA-Z0-9_-]+)$ - [E=CHANNEL_ID:$1]

# 2. Rule for OPTIONS (pre-flight) requests.
#    These are ALWAYS sent to the dedicated PHP script to handle CORS.
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^canaux/.*$ /cors-handler.php [L]

# 3. Rule for GET requests (only if the data file exists).
RewriteCond %{REQUEST_METHOD} GET
RewriteCond %{DOCUMENT_ROOT}/canaux/%{ENV:CHANNEL_ID} -f
RewriteRule ^canaux/.*$ /canal.php?fichier=%{ENV:CHANNEL_ID} [L,QSA]

# 4. Rule for POST requests (always, to allow creation).
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^canaux/.*$ /canal.php?fichier=%{ENV:CHANNEL_ID} [L,QSA]

Step 3: Using Your Endpoint

Once the files are in place on your server, your endpoint is ready! To use it, simply construct a specific URL to launch iCanText and share it with your community members.

The URL consists of the application address, followed by a # fragment containing the endpoint parameter.

URL Format:

https://icantext.com/#&endpoint=https://your-domain.com

Replace https://your-domain.com with the base URL where you installed the files. All users who access iCanText via this link will use your private signaling server instead of the default one.

Important: To ensure privacy, all members of your workspace must use the same URL with your custom endpoint. We recommend bookmarking this URL or sharing it as the single entry point for your group.

Endpoint Code License

The PHP code and .htaccess configuration provided on this page are free software. We believe that transparency and collaboration are essential for security.

We have chosen to release this code under the MIT License. It is one of the most permissive open-source licenses. It allows you to:

  • Use the code freely for commercial or non-commercial purposes.
  • Modify the code to suit your needs.
  • Distribute your modified versions.

The MIT License requires only one simple obligation: you must include the original copyright notice in any copy or modification of the software you distribute. The notice to retain is as follows:

Copyright (c) 2025 S.A.S. SAY SEE SHOW ( https://sayseeshow.com )

The software is provided "as is", without warranty of any kind.

Pourquoi héberger son propre endpoint ?

iCanText est conçu pour être radicalement décentralisé. Cependant, pour que les utilisateurs puissent se trouver et initier une connexion, un "lieu de rendez-vous" public est nécessaire. C'est le rôle du serveur de signalisation, ou "endpoint".

Par défaut, iCanText utilise l'endpoint https://p.sayseeshow.io. Bien que ce service soit minimaliste et respectueux de la vie privée (il n'enregistre rien), le simple fait de l'utiliser révèle à nos serveurs qu'une tentative de connexion a eu lieu.

En déployant votre propre endpoint sur un serveur que vous contrôlez, vous atteignez un niveau de confidentialité supérieur :

  • Confidentialité Absolue : L'éditeur d'iCanText (Say See Show) n'a plus aucune connaissance, même technique, de l'existence de votre réseau ou de ses membres.
  • Souveraineté Totale : Votre communauté devient un univers complètement isolé et autonome, sans aucune dépendance externe pour son fonctionnement.
  • Résilience Accrue : Votre réseau n'est pas affecté par une éventuelle panne du service de signalisation par défaut.
  • Scalabilité Illimitée : Vous pouvez créer des "fédérations" de communautés, chacune avec son propre point d'entrée, permettant à iCanText de s'adapter à des millions d'utilisateurs sans point de congestion.

Guide de déploiement en 3 étapes

Étape 1 : prérequis

Ce guide s'adresse à un déploiement sur un hébergement web mutualisé standard. Vous aurez besoin de :

  • Un hébergement web supportant PHP (version 7.4 ou supérieure).
  • La possibilité de créer des fichiers et des dossiers (via FTP ou un gestionnaire de fichiers).
  • La possibilité de créer ou modifier un fichier .htaccess (pour les serveurs Apache).

Étape 2 : création des fichiers

Sur votre serveur, créez la structure de dossiers et de fichiers suivante. Par exemple, à la racine de votre site https://votre-domaine.com/ :

/ (racine de votre site)
├── canaux/             (Ce dossier doit avoir les permissions d'écriture, ex: 755)
├── canal.php           (Le script principal)
├── .htaccess           (Le fichier de configuration Apache)
├── cors-handler.php    (Gestionnaire pour les requêtes CORS)
└── cors_config.php     (Configuration des domaines autorisés)
						

Note de sécurité : La configuration CORS ci-dessous est sécurisée car elle n'autorise que des domaines spécifiques. Vous pouvez éditer la liste $allowedOrigins dans cors_config.php pour ajouter vos propres domaines si vous hébergez une version modifiée de l'application iCanText.

Contenu du fichier cors_config.php

Ce fichier centralise les domaines autorisés à se connecter à votre endpoint.

Fichier : cors_config.php

<?php
// Fichier : cors_config.php

$allowedOrigins = [
    'https://sayseeshow.io',
    'https://icansend.com',
    'https://icanphone.com',
    'https://icantext.com'
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';

if (in_array($origin, $allowedOrigins, true)) {
    header("Access-Control-Allow-Origin: $origin");
    header('Access-Control-Allow-Credentials: true');
}

Contenu du fichier cors-handler.php

Ce script gère les requêtes "pre-flight" (OPTIONS) envoyées par les navigateurs.

Fichier : cors-handler.php

<?php
// Fichier : cors-handler.php
// Ce script est appelé par le .htaccess uniquement pour les requêtes OPTIONS.

require_once __DIR__ . '/cors_config.php';

// Les requêtes OPTIONS (pre-flight) n'ont besoin que de ces en-têtes.
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Max-Age: 86400');
header("HTTP/1.1 204 No Content");
exit(0);

Contenu du fichier canal.php

Ce code php utilise la configuration CORS.

Fichier : canal.php

<?php
/*
 * Endpoint de signalisation WebRTC basé sur des fichiers.
 */
 
require_once __DIR__ . '/cors_config.php';

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    require __DIR__ . '/cors-handler.php';
    exit(0);
}

define('MAX_INPUT_SIZE', 15 * 1024);
define('MAX_FILE_SIZE', 150 * 1024);

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: 0');

$fichier = $_GET['fichier'] ?? '';
if (preg_match('/^[a-zA-Z0-9_-]+$/', $fichier) !== 1) {
    header("HTTP/1.1 400 Bad Request");
    exit('Nom de canal invalide.');
}

$chemin_fichier = __DIR__ . '/canaux/' . basename($fichier);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $contenu = file_get_contents('php://input');

    if (strlen($contenu) > MAX_INPUT_SIZE) {
        header("HTTP/1.1 413 Payload Too Large");
        exit('La charge utile dépasse la limite.');
    }

    if (!$contenu) {
        header("HTTP/1.1 400 Bad Request");
        exit('Aucun contenu reçu.');
    }

    $fp = fopen($chemin_fichier, 'a');
    if (!$fp) {
        header("HTTP/1.1 500 Internal Server Error");
        exit('Impossible d\'ouvrir le fichier de canal.');
    }

    if (flock($fp, LOCK_EX)) {
        clearstatcache(true, $chemin_fichier);
        if (file_exists($chemin_fichier) && filesize($chemin_fichier) + strlen($contenu) > MAX_FILE_SIZE) {
            flock($fp, LOCK_UN);
            fclose($fp);
            header("HTTP/1.1 409 Conflict");
            exit('Le canal est plein.');
        }
        fwrite($fp, $contenu . "\n---\n");
        fflush($fp);
        flock($fp, LOCK_UN);
        fclose($fp);

        header("HTTP/1.1 201 Created");
        exit;
    } else {
        fclose($fp);
        header("HTTP/1.1 503 Service Unavailable");
        exit('Impossible d\'acquérir un verrou.');
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    if (!file_exists($chemin_fichier)) {
        header("HTTP/1.1 404 Not Found");
        exit;
    }

    $fp = fopen($chemin_fichier, 'r');
    if (!$fp) {
        header("HTTP/1.1 500 Internal Server Error");
        exit('Impossible d\'ouvrir le fichier.');
    }

    if (flock($fp, LOCK_EX)) {
        $contenu = stream_get_contents($fp);
        unlink($chemin_fichier);
        flock($fp, LOCK_UN);
        fclose($fp);

        header('Content-Type: text/plain; charset=utf-8');
        echo $contenu;
        exit;
    } else {
        fclose($fp);
        header("HTTP/1.1 503 Service Unavailable");
        exit('Impossible d\'acquérir un verrou.');
    }
}

header("HTTP/1.1 405 Method Not Allowed");
exit;

Contenu du fichier .htaccess

Cette version du .htaccess redirige correctement les requêtes OPTIONS vers le nouveau gestionnaire.

Fichier : .htaccess

RewriteEngine On
Options -Indexes

# 1. Capture du nom du canal dans une variable d'environnement.
RewriteRule ^canaux/([a-zA-Z0-9_-]+)$ - [E=CHANNEL_ID:$1]

# 2. Règle pour les requêtes OPTIONS (pre-flight)
#    On les envoie TOUJOURS au script PHP dédié pour gérer le CORS.
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^canaux/.*$ /cors-handler.php [L]

# 3. Règle pour les requêtes GET (uniquement si le fichier de données existe).
RewriteCond %{REQUEST_METHOD} GET
RewriteCond %{DOCUMENT_ROOT}/canaux/%{ENV:CHANNEL_ID} -f
RewriteRule ^canaux/.*$ /canal.php?fichier=%{ENV:CHANNEL_ID} [L,QSA]

# 4. Règle pour les requêtes POST (toujours, pour permettre la création).
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^canaux/.*$ /canal.php?fichier=%{ENV:CHANNEL_ID} [L,QSA]

Étape 3 : utiliser votre endpoint

Une fois les fichiers en place sur votre serveur, votre endpoint est prêt ! Pour l'utiliser, il suffit de construire une URL spécifique pour lancer iCanText et de la partager avec les membres de votre communauté.

L'URL se compose de l'adresse de l'application, suivie d'un fragment # contenant le paramètre endpoint.

Format de l'URL :

https://icantext.com/#&endpoint=https://votre-domaine.com

Remplacez https://votre-domaine.com par l'URL de base où vous avez installé les fichiers. Tous les utilisateurs qui accèdent à iCanText via ce lien utiliseront votre serveur de signalisation privé au lieu de celui par défaut.

Important : Pour garantir la confidentialité, tous les membres de votre espace de travail doivent utiliser la même URL avec votre endpoint personnalisé. Nous vous recommandons de mettre cette URL dans vos favoris ou de la partager comme point d'entrée unique pour votre groupe.

Licence du code de l'Endpoint

Le code PHP et la configuration .htaccess fournis sur cette page sont des logiciels libres. Nous pensons que la transparence et la collaboration sont essentielles à la sécurité.

Nous avons choisi de publier ce code sous la Licence MIT. C'est l'une des licences open source les plus permissives. Elle vous autorise à :

  • Utiliser le code librement à des fins commerciales ou non commerciales.
  • Modifier le code pour l'adapter à vos besoins.
  • Distribuer vos versions modifiées.

La licence MIT vous impose une seule obligation simple : inclure l'avis de droit d'auteur original dans toute copie ou modification du logiciel que vous distribuez. L'avis à conserver est le suivant :

Copyright (c) 2025 S.A.S. SAY SEE SHOW ( https://sayseeshow.fr )

Le logiciel est fourni "en l'état", sans aucune garantie.