From a5c55a1a3d260ae64e5afa873510e80b4f536227 Mon Sep 17 00:00:00 2001 From: or-else Date: Tue, 3 Dec 2024 20:45:24 +0300 Subject: [PATCH] link preview comments, formatting, documentation --- docs/API.md | 138 +++++++++++++++++++++++------------------- server/linkpreview.go | 15 +++-- server/main.go | 40 ++++++------ server/tinode.conf | 8 ++- 4 files changed, 115 insertions(+), 86 deletions(-) diff --git a/docs/API.md b/docs/API.md index e54aa054..6ed096cd 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,66 +1,67 @@ - + - [Server API](#server-api) - - [How it Works?](#how-it-works) - - [General Considerations](#general-considerations) - - [Connecting to the Server](#connecting-to-the-server) - - [gRPC](#grpc) - - [WebSocket](#websocket) - - [Long Polling](#long-polling) - - [Out of Band Large Files](#out-of-band-large-files) - - [Running Behind a Reverse Proxy](#running-behind-a-reverse-proxy) - - [Users](#users) - - [Authentication](#authentication) - - [Creating an Account](#creating-an-account) - - [Logging in](#logging-in) - - [Changing Authentication Parameters](#changing-authentication-parameters) - - [Resetting a Password, i.e. "Forgot Password"](#resetting-a-password-ie-forgot-password) - - [Suspending a User](#suspending-a-user) - - [Credential Validation](#credential-validation) - - [Access Control](#access-control) - - [Topics](#topics) - - [`me` Topic](#me-topic) - - [`fnd` and Tags: Finding Users and Topics](#fnd-and-tags-finding-users-and-topics) - - [Query Language](#query-language) - - [Incremental Updates to Queries](#incremental-updates-to-queries) - - [Query Rewrite](#query-rewrite) - - [Possible Use Cases](#possible-use-cases) - - [Peer to Peer Topics](#peer-to-peer-topics) - - [Group Topics](#group-topics) - - [`sys` Topic](#sys-topic) - - [Using Server-Issued Message IDs](#using-server-issued-message-ids) - - [User Agent and Presence Notifications](#user-agent-and-presence-notifications) - - [Trusted, Public, and Private Fields](#trusted-public-and-private-fields) - - [Trusted](#trusted) - - [Public](#public) - - [Private](#private) - - [Format of Content](#format-of-content) - - [Out-of-Band Handling of Large Files](#out-of-band-handling-of-large-files) - - [Uploading](#uploading) - - [Downloading](#downloading) - - [Push Notifications](#push-notifications) - - [Tinode Push Gateway](#tinode-push-gateway) - - [Google FCM](#google-fcm) - - [Stdout](#stdout) - - [Video Calls](#video-calls) - - [Messages](#messages) - - [Client to Server Messages](#client-to-server-messages) - - [`{hi}`](#hi) - - [`{acc}`](#acc) - - [`{login}`](#login) - - [`{sub}`](#sub) - - [`{leave}`](#leave) - - [`{pub}`](#pub) - - [`{get}`](#get) - - [`{set}`](#set) - - [`{del}`](#del) - - [`{note}`](#note) - - [Server to Client Messages](#server-to-client-messages) - - [`{data}`](#data) - - [`{ctrl}`](#ctrl) - - [`{meta}`](#meta) - - [`{pres}`](#pres) - - [`{info}`](#info) + - [How it Works?](#how-it-works) + - [General Considerations](#general-considerations) + - [Connecting to the Server](#connecting-to-the-server) + - [gRPC](#grpc) + - [WebSocket](#websocket) + - [Long Polling](#long-polling) + - [Out of Band Large Files](#out-of-band-large-files) + - [Running Behind a Reverse Proxy](#running-behind-a-reverse-proxy) + - [Users](#users) + - [Authentication](#authentication) + - [Creating an Account](#creating-an-account) + - [Logging in](#logging-in) + - [Changing Authentication Parameters](#changing-authentication-parameters) + - [Resetting a Password, i.e. "Forgot Password"](#resetting-a-password-ie-forgot-password) + - [Suspending a User](#suspending-a-user) + - [Credential Validation](#credential-validation) + - [Access Control](#access-control) + - [Topics](#topics) + - [me Topic](#me-topic) + - [fnd and Tags: Finding Users and Topics](#fnd-and-tags-finding-users-and-topics) + - [Query Language](#query-language) + - [Incremental Updates to Queries](#incremental-updates-to-queries) + - [Query Rewrite](#query-rewrite) + - [Possible Use Cases](#possible-use-cases) + - [Peer to Peer Topics](#peer-to-peer-topics) + - [Group Topics](#group-topics) + - [sys Topic](#sys-topic) + - [Using Server-Issued Message IDs](#using-server-issued-message-ids) + - [User Agent and Presence Notifications](#user-agent-and-presence-notifications) + - [Trusted, Public, and Private Fields](#trusted-public-and-private-fields) + - [Trusted](#trusted) + - [Public](#public) + - [Private](#private) + - [Format of Content](#format-of-content) + - [Out-of-Band Handling of Large Files](#out-of-band-handling-of-large-files) + - [Uploading](#uploading) + - [Downloading](#downloading) + - [Push Notifications](#push-notifications) + - [Tinode Push Gateway](#tinode-push-gateway) + - [Google FCM](#google-fcm) + - [Stdout](#stdout) + - [Video Calls](#video-calls) + - [Link Previews](#link-previews) + - [Messages](#messages) + - [Client to Server Messages](#client-to-server-messages) + - [{hi}](#hi) + - [{acc}](#acc) + - [{login}](#login) + - [{sub}](#sub) + - [{leave}](#leave) + - [{pub}](#pub) + - [{get}](#get) + - [{set}](#set) + - [{del}](#del) + - [{note}](#note) + - [Server to Client Messages](#server-to-client-messages) + - [{data}](#data) + - [{ctrl}](#ctrl) + - [{meta}](#meta) + - [{pres}](#pres) + - [{info}](#info) @@ -601,6 +602,21 @@ The `stdout` adapter is mostly useful for debugging and logging. It writes push [See separate document](call-establishment.md). +## Link Previews + +Tinode provides an optional service which helps client applications generate link (URL) previews for inclusion into messages. The enpoint of this service (if enabled) is located at `/v0/urlpreview`. The service takes a single parameter `url`: + +``` +/v0/urlpreview?url=https%3A%2F%2Ftinode.co +``` +The first several kilobytes of the document at the given URL is fetched by issuing an HTTP(S) GET request. If the returned document has content-type `text/html`, the HTML is parsed for page title, description, and image URL. The result is formatted as JSON and returned as + +```json +{"title": "Page title", "description": "This is a page description", "image_url": "https://tinode.co/img/logo64x64.png"} +``` + +The link preview service requires authentication. It's exactly the same as authentication for [Out of Band Large Files](#out-of-band-handling-of-large-files). + ## Messages A message is a logically associated set of data. Messages are passed as JSON-formatted UTF-8 text. diff --git a/server/linkpreview.go b/server/linkpreview.go index 98234bcc..19ad55fd 100644 --- a/server/linkpreview.go +++ b/server/linkpreview.go @@ -3,8 +3,6 @@ package main import ( "encoding/json" "errors" - "golang.org/x/net/html" - "golang.org/x/net/html/atom" "io" "net" "net/http" @@ -12,6 +10,9 @@ import ( "strings" "time" "unicode/utf8" + + "golang.org/x/net/html" + "golang.org/x/net/html/atom" ) type linkPreview struct { @@ -31,26 +32,27 @@ var client = &http.Client{ } // previewLink handles the HTTP request, fetches the URL, and returns the link preview. +// urlpreview?url=https%3A%2F%2Ftinode.co func previewLink(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodHead { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } // check authorization uid, challenge, err := authHttpRequest(r) if err != nil { - http.Error(w, "invalid auth secret", http.StatusBadRequest) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if challenge != nil || uid.IsZero() { - http.Error(w, "user not authenticated", http.StatusUnauthorized) + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } u := r.URL.Query().Get("url") if u == "" { - http.Error(w, "Missing 'url' query parameter", http.StatusBadRequest) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } @@ -92,6 +94,7 @@ func previewLink(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) return } + if strings.HasPrefix(resp.Header.Get("Content-Type"), "text/html") { if err := json.NewEncoder(w).Encode(extractMetadata(body)); err != nil { http.Error(w, "Failed to encode response", http.StatusInternalServerError) diff --git a/server/main.go b/server/main.go index 4b4c9313..157aecc2 100644 --- a/server/main.go +++ b/server/main.go @@ -202,7 +202,9 @@ var globals struct { // URL of the main endpoint. // TODO: implement file-serving API for gRPC and remove this feature. - servingAt string + servingAt string + + // Indicator if link preview generator is enabled. linkPreviewEnabled bool } @@ -292,18 +294,22 @@ type configType struct { // it's impossible to infer it. DefaultCountryCode string `json:"default_country_code"` + // Enable service which generates link previews: in response to a GET request with a URL + // /v0/urlpreview?url=https%3A%2F%2Ftinode.co visit the URL, parse HTML, and return JSON like + // {"title": "Page title", description: "This is a demo page", image_url: "https://tinode.co/img/logo.png"}. + LinkPreviewEnabled bool `json:"link_preview"` + // Configs for subsystems - Cluster json.RawMessage `json:"cluster_config"` - Plugin json.RawMessage `json:"plugins"` - Store json.RawMessage `json:"store_config"` - Push json.RawMessage `json:"push"` - TLS json.RawMessage `json:"tls"` - Auth map[string]json.RawMessage `json:"auth_config"` - Validator map[string]*validatorConfig `json:"acc_validation"` - AccountGC *accountGcConfig `json:"acc_gc_config"` - Media *mediaConfig `json:"media"` - WebRTC json.RawMessage `json:"webrtc"` - LinkPreviewEnabled bool `json:"link_preview_enabled"` + Cluster json.RawMessage `json:"cluster_config"` + Plugin json.RawMessage `json:"plugins"` + Store json.RawMessage `json:"store_config"` + Push json.RawMessage `json:"push"` + TLS json.RawMessage `json:"tls"` + Auth map[string]json.RawMessage `json:"auth_config"` + Validator map[string]*validatorConfig `json:"acc_validation"` + AccountGC *accountGcConfig `json:"acc_gc_config"` + Media *mediaConfig `json:"media"` + WebRTC json.RawMessage `json:"webrtc"` } func main() { @@ -731,16 +737,16 @@ func main() { logs.Info.Println("Large media handling enabled", config.Media.UseHandler) } + if config.LinkPreviewEnabled { + globals.linkPreviewEnabled = true + mux.HandleFunc(config.ApiPath+"v0/urlpreview", previewLink) + } + if staticMountPoint != "/" { // Serve json-formatted 404 for all other URLs mux.HandleFunc("/", serve404) } - globals.linkPreviewEnabled = config.LinkPreviewEnabled - if config.LinkPreviewEnabled { - mux.HandleFunc(config.ApiPath+"v0/preview-link", previewLink) - } - if err = listenAndServe(config.Listen, mux, tlsConfig, signalHandler()); err != nil { logs.Err.Fatal(err) } diff --git a/server/tinode.conf b/server/tinode.conf index e84ac49b..2d9d6a7c 100644 --- a/server/tinode.conf +++ b/server/tinode.conf @@ -68,6 +68,11 @@ // If missing, the server will default to "US". "default_country_code": "", + // Enable service which generates link previews: in response to a GET request with a URL + // /v0/urlpreview?url=https%3A%2F%2Ftinode.co visit the URL, parse HTML, and return JSON like + // {"title": "Page title", description: "This is a page description", image_url: "https://tinode.co/img/logo.png"}. + "link_preview_enabled": false, + // Large media/blob handlers: large files/images included in messages. "media": { // The name of the media handler to use. @@ -678,6 +683,5 @@ // Address of the plugin. "service_addr": "tcp://localhost:40051" } - ], - "link_preview_enabled":false + ] }