Skip to content

Commit

Permalink
link preview comments, formatting, documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
or-else committed Dec 3, 2024
1 parent c2d61a6 commit a5c55a1
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 86 deletions.
138 changes: 77 additions & 61 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,67 @@
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
<!-- TOC depthfrom:1 depthto:6 withlinks:true updateonsave:true orderedlist:false -->

- [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)

<!-- /TOC -->

Expand Down Expand Up @@ -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.
Expand Down
15 changes: 9 additions & 6 deletions server/linkpreview.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package main
import (
"encoding/json"
"errors"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
"unicode/utf8"

"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)

type linkPreview struct {
Expand All @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
40 changes: 23 additions & 17 deletions server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
}
Expand Down
8 changes: 6 additions & 2 deletions server/tinode.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -678,6 +683,5 @@
// Address of the plugin.
"service_addr": "tcp://localhost:40051"
}
],
"link_preview_enabled":false
]
}

0 comments on commit a5c55a1

Please sign in to comment.