channelling

package
v0.0.0-...-12fe0a3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 8, 2019 License: MIT Imports: 31 Imported by: 0

Documentation

Index

Constants

View Source
const (
	RoomTypeConference = "Conference"
	RoomTypeRoom       = "Room"
)
View Source
const (
	BusManagerStartup    = "startup"
	BusManagerOffer      = "offer"
	BusManagerAnswer     = "answer"
	BusManagerBye        = "bye"
	BusManagerConnect    = "connect"
	BusManagerDisconnect = "disconnect"
	BusManagerSession    = "session"
)
View Source
const (
	PipelineNamespaceCall = "call"
)

Variables

This section is empty.

Functions

func BusSubjectTrigger

func BusSubjectTrigger(prefix, suffix string) string

BusSubjectTrigger returns the bus subject for trigger payloads.

func NewDataError

func NewDataError(code, message string) error

Types

type Broadcaster

type Broadcaster interface {
	Broadcast(sessionID, roomID string, outgoing *DataOutgoing)
}

type BusManager

type BusManager interface {
	ChannellingAPIConsumer
	Start()
	Publish(subject string, v interface{}) error
	Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error
	Trigger(name, from, payload string, data interface{}, pipeline *Pipeline) error
	Subscribe(subject string, cb nats.Handler) (*nats.Subscription, error)
	BindRecvChan(subject string, channel interface{}) (*nats.Subscription, error)
	BindSendChan(subject string, channel interface{}) error
	PrefixSubject(string) string
	CreateSink(string) Sink
}

A BusManager provides the API to interact with a bus.

func NewBusManager

func NewBusManager(apiConsumer ChannellingAPIConsumer, id string, useNats bool, subjectPrefix string) BusManager

NewBusManager creates and initializes a new BusMager with the provided flags for NATS support. It is intended to connect the backend bus with a easy to use API to send and receive bus data.

type BusTrigger

type BusTrigger struct {
	Id       string
	Name     string
	From     string
	Payload  string      `json:",omitempty"`
	Data     interface{} `json:",omitempty"`
	Pipeline string      `json:",omitempty"`
}

A BusTrigger is a container to serialize trigger events for the bus backend.

type ByPrioAndStamp

type ByPrioAndStamp []*DataSession

func (ByPrioAndStamp) Len

func (a ByPrioAndStamp) Len() int

func (ByPrioAndStamp) Less

func (a ByPrioAndStamp) Less(i, j int) bool

func (ByPrioAndStamp) Swap

func (a ByPrioAndStamp) Swap(i, j int)

type ChannellingAPI

type ChannellingAPI interface {
	OnConnect(*Client, *Session) (interface{}, error)
	OnDisconnect(*Client, *Session)
	OnIncoming(Sender, *Session, *DataIncoming) (interface{}, error)
	OnIncomingProcessed(Sender, *Session, *DataIncoming, interface{}, error)
}

type ChannellingAPIConsumer

type ChannellingAPIConsumer interface {
	SetChannellingAPI(ChannellingAPI)
	GetChannellingAPI() ChannellingAPI
}

func NewChannellingAPIConsumer

func NewChannellingAPIConsumer() ChannellingAPIConsumer

type Client

type Client struct {
	Connection
	Codec
	ChannellingAPI ChannellingAPI
	// contains filtered or unexported fields
}

func NewClient

func NewClient(codec Codec, api ChannellingAPI, session *Session) *Client

func (*Client) OnConnect

func (client *Client) OnConnect(conn Connection)

func (*Client) OnDisconnect

func (client *Client) OnDisconnect()

func (*Client) OnText

func (client *Client) OnText(b buffercache.Buffer)

func (*Client) ReplaceAndClose

func (client *Client) ReplaceAndClose(oldClient *Client)

func (*Client) Session

func (client *Client) Session() *Session

type ClientStats

type ClientStats interface {
	ClientInfo(details bool) (int, map[string]*DataSession, map[string]string)
}

type Codec

type Codec interface {
	NewBuffer() buffercache.Buffer
	IncomingDecoder
	OutgoingEncoder
}

func NewCodec

func NewCodec(incomingLimit int) Codec

type Config

type Config struct {
	Title                           string                    // Title
	Ver                             string                    `json:"-"` // Version (not exported to Javascript)
	S                               string                    // Static URL prefix with version
	B                               string                    // Base URL
	Token                           string                    // Server token
	Renegotiation                   bool                      // Renegotiation flag
	StunURIs                        []string                  // STUN server URIs
	TurnURIs                        []string                  // TURN server URIs
	Tokens                          bool                      // True when we got a tokens file
	Version                         string                    // Server version number
	UsersEnabled                    bool                      // Flag if users are enabled
	UsersAllowRegistration          bool                      // Flag if users can register
	UsersMode                       string                    // Users mode string
	DefaultRoomEnabled              bool                      // Flag if default room ("") is enabled
	Plugin                          string                    // Plugin to load
	AuthorizeRoomCreation           bool                      // Whether a user account is required to create rooms
	AuthorizeRoomJoin               bool                      // Whether a user account is required to join rooms
	Modules                         []string                  // List of enabled modules
	ModulesTable                    map[string]bool           `json:"-"` // Map of enabled modules
	GlobalRoomID                    string                    `json:"-"` // Id of the global room (not exported to Javascript)
	ContentSecurityPolicy           string                    `json:"-"` // HTML content security policy
	ContentSecurityPolicyReportOnly string                    `json:"-"` // HTML content security policy in report only mode
	RoomTypeDefault                 string                    `json:"-"` // New rooms default to this type
	RoomTypes                       map[*regexp.Regexp]string `json:"-"` // Map of regular expression -> room type
}

func (*Config) Get

func (config *Config) Get(request *http.Request) (int, interface{}, http.Header)

func (*Config) WithModule

func (config *Config) WithModule(m string) bool

type Connection

type Connection interface {
	Index() uint64
	Send(buffercache.Buffer)
	Close()
	ReadPump()
	WritePump()
}

func NewConnection

func NewConnection(index uint64, ws *websocket.Conn, handler ConnectionHandler) Connection

type ConnectionCounter

type ConnectionCounter interface {
	CountConnection() uint64
}

type ConnectionHandler

type ConnectionHandler interface {
	NewBuffer() buffercache.Buffer
	OnConnect(Connection)
	OnDisconnect()
	OnText(buffercache.Buffer)
}

type Contact

type Contact struct {
	A string
	B string
}

type ContactManager

type ContactManager interface {
	ContactrequestHandler(*Session, string, *DataContactRequest) error
	GetContactID(*Session, string) (string, error)
}

type Context

type Context struct {
	App        string // Main client script
	Cfg        *Config
	Host       string
	Ssl        bool
	Csp        bool
	Languages  []string
	Room       string        `json:"-"`
	Scheme     string        `json:"-"`
	Origin     string        `json:",omitempty"`
	S          string        `json:",omitempty"`
	ExtraDHead template.HTML `json:"-"`
	ExtraDBody template.HTML `json:"-"`
}

type DataAlive

type DataAlive struct {
	Type  string
	Alive uint64
}

type DataAnswer

type DataAnswer struct {
	Type   string
	To     string
	Answer map[string]interface{}
}

type DataAuthentication

type DataAuthentication struct {
	Type           string
	Authentication *SessionToken
}

type DataAutoCall

type DataAutoCall struct {
	Id   string
	Type string
}

type DataBye

type DataBye struct {
	Type string
	To   string
	Bye  interface{}
}

type DataCandidate

type DataCandidate struct {
	Type      string
	To        string
	Candidate interface{}
}

type DataChat

type DataChat struct {
	To   string
	Type string
	Chat *DataChatMessage
}

type DataChatMessage

type DataChatMessage struct {
	Message string
	Time    string
	NoEcho  bool   `json:",omitempty"`
	Mid     string `json:",omitempty"`
	Status  *DataChatStatus
}

type DataChatStatus

type DataChatStatus struct {
	Typing         string              `json:",omitempty"`
	State          string              `json:",omitempty"`
	Mid            string              `json:",omitempty"`
	SeenMids       []string            `json:",omitempty"`
	FileInfo       *DataFileInfo       `json:",omitempty"`
	Geolocation    *DataGeolocation    `json:",omitempty"`
	ContactRequest *DataContactRequest `json:",omitempty"`
	AutoCall       *DataAutoCall       `json:",omitempty"`
}

type DataConference

type DataConference struct {
	Id         string
	Type       string
	Conference []string
}

type DataContactRequest

type DataContactRequest struct {
	Id      string
	Success bool
	Userid  string `json:",omitempty"`
	Token   string `json:",omitempty"`
}

type DataError

type DataError struct {
	Type    string
	Code    string
	Message string
}

func (*DataError) Error

func (err *DataError) Error() string

type DataFileInfo

type DataFileInfo struct {
	Id     string `json:"id"`
	Chunks uint64 `json:"chunks"`
	Name   string `json:"name"`
	Size   uint64 `json:"size"`
	Type   string `json:"type"`
}

type DataGeolocation

type DataGeolocation struct {
	Accuracy         float64 `json:"accuracy,omitempty"`
	Latitude         float64 `json:"latitude,omitempty"`
	Longitude        float64 `json:"longitude,omitempty"`
	Altitude         float64 `json:"altitude,omitempty"`
	AltitudeAccuracy float64 `json:"altitudeAccuracy,omitempty"`
}

type DataHello

type DataHello struct {
	Version     string
	Ua          string
	Id          string // Compatibility with old clients.
	Name        string // Room name.
	Type        string // Room type.
	Credentials *DataRoomCredentials
}

type DataIncoming

type DataIncoming struct {
	Type           string
	Hello          *DataHello          `json:",omitempty"`
	Offer          *DataOffer          `json:",omitempty"`
	Candidate      *DataCandidate      `json:",omitempty"`
	Answer         *DataAnswer         `json:",omitempty"`
	Bye            *DataBye            `json:",omitempty"`
	Status         *DataStatus         `json:",omitempty"`
	Chat           *DataChat           `json:",omitempty"`
	Conference     *DataConference     `json:",omitempty"`
	Alive          *DataAlive          `json:",omitempty"`
	Authentication *DataAuthentication `json:",omitempty"`
	Sessions       *DataSessions       `json:",omitempty"`
	Room           *DataRoom           `json:",omitempty"`
	Iid            string              `json:",omitempty"`
}

type DataOffer

type DataOffer struct {
	Type  string
	To    string
	Offer map[string]interface{}
}

type DataOutgoing

type DataOutgoing struct {
	Data interface{} `json:",omitempty"`
	From string      `json:",omitempty"`
	To   string      `json:",omitempty"`
	Iid  string      `json:",omitempty"`
	A    string      `json:",omitempty"`
}

type DataRoom

type DataRoom struct {
	Type        string // Room type.
	Name        string // Room name.
	Credentials *DataRoomCredentials
}

type DataRoomCredentials

type DataRoomCredentials struct {
	PIN string
}

type DataSelf

type DataSelf struct {
	Type       string
	Id         string
	Sid        string
	Userid     string
	Suserid    string
	Token      string
	Version    string  // Server version.
	ApiVersion float64 // Server channelling API version.
	Turn       *DataTurn
	Stun       []string
}

type DataSession

type DataSession struct {
	Type    string
	Id      string
	Userid  string      `json:",omitempty"`
	Ua      string      `json:",omitempty"`
	Token   string      `json:",omitempty"`
	Version string      `json:",omitempty"`
	Rev     uint64      `json:",omitempty"`
	Prio    int         `json:",omitempty"`
	Status  interface{} `json:",omitempty"`
	// contains filtered or unexported fields
}

type DataSessions

type DataSessions struct {
	Type     string
	Sessions *DataSessionsRequest `json:",omitempty"`
	Users    []*DataSession
}

type DataSessionsRequest

type DataSessionsRequest struct {
	Token string
	Type  string
}

type DataSink

type DataSink struct {
	SubjectOut string `json:subject_out"`
	SubjectIn  string `json:subject_in"`
}

type DataSinkOutgoing

type DataSinkOutgoing struct {
	Outgoing   *DataOutgoing
	ToUserid   string
	FromUserid string
	Pipe       string `json:",omitempty"`
}

type DataStatus

type DataStatus struct {
	Type   string
	Status interface{}
}

type DataTurn

type DataTurn struct {
	Username string   `json:"username"`
	Password string   `json:"password"`
	Ttl      int      `json:"ttl"`
	Urls     []string `json:"urls"`
}

type DataUser

type DataUser struct {
	Id       string
	Sessions int
}

type DataWelcome

type DataWelcome struct {
	Type  string
	Room  *DataRoom
	Users []*DataSession
}

type Hub

func NewHub

func NewHub(config *Config, sessionSecret, encryptionSecret, turnSecret []byte, encoder OutgoingEncoder) Hub

type HubStat

type HubStat struct {
	Rooms                 int                     `json:"rooms"`
	Connections           int                     `json:"connections"`
	Sessions              int                     `json:"sessions"`
	Users                 int                     `json:"users"`
	Count                 uint64                  `json:"count"`
	BroadcastChatMessages uint64                  `json:"broadcastchatmessages"`
	UnicastChatMessages   uint64                  `json:"unicastchatmessages"`
	IdsInRoom             map[string][]string     `json:"idsinroom,omitempty"`
	SessionsById          map[string]*DataSession `json:"sessionsbyid,omitempty"`
	UsersById             map[string]*DataUser    `json:"usersbyid,omitempty"`
	ConnectionsByIdx      map[string]string       `json:"connectionsbyidx,omitempty"`
}

type Image

type Image struct {
	// contains filtered or unexported fields
}

func (*Image) LastChange

func (img *Image) LastChange() time.Time

func (*Image) LastChangeID

func (img *Image) LastChangeID() string

func (*Image) MimeType

func (img *Image) MimeType() string

func (*Image) Reader

func (img *Image) Reader() *bytes.Reader

type ImageCache

type ImageCache interface {
	Update(sessionId string, image string) string
	Get(imageId string) *Image
	Delete(sessionId string)
}

func NewImageCache

func NewImageCache() ImageCache

type IncomingDecoder

type IncomingDecoder interface {
	DecodeIncoming(buffercache.Buffer) (*DataIncoming, error)
}

type OutgoingEncoder

type OutgoingEncoder interface {
	EncodeOutgoing(*DataOutgoing) (buffercache.Buffer, error)
}

type Pipeline

type Pipeline struct {
	PipelineManager PipelineManager
	// contains filtered or unexported fields
}

func NewPipeline

func NewPipeline(manager PipelineManager,
	namespace string,
	id string,
	from *Session,
	duration time.Duration) *Pipeline

func (*Pipeline) Add

func (pipeline *Pipeline) Add(msg *DataSinkOutgoing) *Pipeline

func (*Pipeline) Attach

func (pipeline *Pipeline) Attach(sink Sink) error

func (*Pipeline) Close

func (pipeline *Pipeline) Close()

func (*Pipeline) Expired

func (pipeline *Pipeline) Expired() bool

func (*Pipeline) FlushOutgoing

func (pipeline *Pipeline) FlushOutgoing(hub Hub, client *Client, to string, outgoing *DataOutgoing) bool

func (*Pipeline) FromSession

func (pipeline *Pipeline) FromSession() *Session

func (*Pipeline) GetID

func (pipeline *Pipeline) GetID() string

func (*Pipeline) Index

func (pipeline *Pipeline) Index() uint64

func (*Pipeline) JSONFeed

func (pipeline *Pipeline) JSONFeed(since, limit int) ([]byte, error)

func (*Pipeline) Refresh

func (pipeline *Pipeline) Refresh(duration time.Duration)

func (*Pipeline) Send

func (pipeline *Pipeline) Send(b buffercache.Buffer)

func (*Pipeline) ToSession

func (pipeline *Pipeline) ToSession() *Session

type PipelineFeedLine

type PipelineFeedLine struct {
	Seq int
	Msg *DataOutgoing
}

type PipelineManager

type PipelineManager interface {
	BusManager
	SessionStore
	UserStore
	SessionCreator
	GetPipelineByID(id string) (pipeline *Pipeline, ok bool)
	GetPipeline(namespace string, sender Sender, session *Session, to string) *Pipeline
	FindSinkAndSession(to string) (Sink, *Session)
}

func NewPipelineManager

func NewPipelineManager(busManager BusManager, sessionStore SessionStore, userStore UserStore, sessionCreator SessionCreator) PipelineManager

type RoomManager

type RoomManager interface {
	RoomStatusManager
	Broadcaster
	RoomStats
	SetBusManager(bus BusManager) error
}

func NewRoomManager

func NewRoomManager(config *Config, encoder OutgoingEncoder) RoomManager

type RoomStats

type RoomStats interface {
	RoomInfo(includeSessions bool) (count int, sessionInfo map[string][]string)
}

type RoomStatusManager

type RoomStatusManager interface {
	RoomUsers(*Session) []*DataSession
	JoinRoom(roomID, roomName, roomType string, credentials *DataRoomCredentials, session *Session, sessionAuthenticated bool, sender Sender) (*DataRoom, error)
	LeaveRoom(roomID, sessionID string)
	UpdateRoom(*Session, *DataRoom) (*DataRoom, error)
	MakeRoomID(roomName, roomType string) string
	Get(roomID string) (room RoomWorker, ok bool)
}

type RoomWorker

type RoomWorker interface {
	Start()
	SessionIDs() []string
	Users() []*roomUser
	Update(*DataRoom) error
	GetUsers() []*DataSession
	Broadcast(sessionID string, buf buffercache.Buffer)
	Join(*DataRoomCredentials, *Session, Sender) (*DataRoom, error)
	Leave(sessionID string)
	GetType() string
}

func NewRoomWorker

func NewRoomWorker(manager *roomManager, roomID, roomName, roomType string, credentials *DataRoomCredentials) RoomWorker

type Sender

type Sender interface {
	Index() uint64
	Send(buffercache.Buffer)
}

type Session

type Session struct {
	SessionManager    SessionManager
	Unicaster         Unicaster
	Broadcaster       Broadcaster
	RoomStatusManager RoomStatusManager

	Id        string
	Sid       string
	Ua        string
	UpdateRev uint64
	Status    interface{}
	Nonce     string
	Prio      int
	Hello     bool
	Roomid    string
	// contains filtered or unexported fields
}

func NewSession

func NewSession(manager SessionManager,
	unicaster Unicaster,
	broadcaster Broadcaster,
	rooms RoomStatusManager,
	buddyImages ImageCache,
	attestations *securecookie.SecureCookie,
	id,
	sid string) *Session

func (*Session) AddSubscriber

func (s *Session) AddSubscriber(session *Session)

func (*Session) Authenticate

func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error

func (*Session) Authorize

func (s *Session) Authorize(realm string, st *SessionToken) (string, error)

func (*Session) Broadcast

func (s *Session) Broadcast(m interface{})

func (*Session) BroadcastStatus

func (s *Session) BroadcastStatus()

func (*Session) Close

func (s *Session) Close()

func (*Session) Data

func (s *Session) Data() *DataSession

func (*Session) DecodeAttestation

func (s *Session) DecodeAttestation(token string) (string, error)

func (*Session) JoinRoom

func (s *Session) JoinRoom(roomName, roomType string, credentials *DataRoomCredentials, sender Sender) (*DataRoom, error)

func (*Session) LeaveRoom

func (s *Session) LeaveRoom()

func (*Session) NewAttestation

func (s *Session) NewAttestation()

func (*Session) RemoveSubscriber

func (s *Session) RemoveSubscriber(id string)

func (*Session) Replace

func (s *Session) Replace(oldSession *Session)

func (*Session) SetUseridFake

func (s *Session) SetUseridFake(userid string)

func (*Session) Subscribe

func (s *Session) Subscribe(session *Session)

func (*Session) Token

func (s *Session) Token() *SessionToken

func (*Session) Unicast

func (s *Session) Unicast(to string, m interface{}, pipeline *Pipeline)

func (*Session) Unsubscribe

func (s *Session) Unsubscribe(id string)

func (*Session) Update

func (s *Session) Update(update *SessionUpdate) uint64

func (*Session) UpdateAttestation

func (s *Session) UpdateAttestation() (string, error)

func (*Session) Userid

func (s *Session) Userid() (userid string)

type SessionAttestation

type SessionAttestation struct {
	// contains filtered or unexported fields
}

func (*SessionAttestation) Decode

func (sa *SessionAttestation) Decode(token string) (string, error)

func (*SessionAttestation) Encode

func (sa *SessionAttestation) Encode() (string, error)

func (*SessionAttestation) Token

func (sa *SessionAttestation) Token() (token string)

func (*SessionAttestation) Update

func (sa *SessionAttestation) Update() (string, error)

type SessionCreateRequest

type SessionCreateRequest struct {
	Id           string
	Session      *DataSession
	Room         *DataRoom
	SetAsDefault bool
}

type SessionCreator

type SessionCreator interface {
	CreateSession(st *SessionToken, userid string) *Session
}

type SessionEncoder

type SessionEncoder interface {
	EncodeSessionToken(*Session) (string, error)
	EncodeSessionUserID(*Session) string
}

type SessionManager

type SessionManager interface {
	UserStats
	SessionStore
	UserStore
	SessionCreator
	DestroySession(sessionID, userID string)
	Authenticate(*Session, *SessionToken, string) error
	GetUserSessions(session *Session, id string) []*DataSession
	DecodeSessionToken(token string) (st *SessionToken)
}

func NewSessionManager

func NewSessionManager(config *Config, tickets Tickets, unicaster Unicaster, broadcaster Broadcaster, rooms RoomStatusManager, buddyImages ImageCache, sessionSecret []byte) SessionManager

type SessionStore

type SessionStore interface {
	GetSession(id string) (session *Session, ok bool)
}

type SessionToken

type SessionToken struct {
	Id     string // Public session id.
	Sid    string // Secret session id.
	Userid string // Public user id.
	Nonce  string `json:"Nonce,omitempty"` // User autentication nonce.
}

type SessionUpdate

type SessionUpdate struct {
	Types  []string
	Ua     string
	Prio   int
	Status interface{}
}

type SessionValidator

type SessionValidator interface {
	Realm() string
	ValidateSession(string, string) bool
}

type Sink

type Sink interface {
	// Write sends outgoing data on the sink
	Write(*DataSinkOutgoing) error
	Enabled() bool
	Close()
	Export() *DataSink
	BindRecvChan(channel interface{}) (*nats.Subscription, error)
}

Sink connects a Pipeline with end points in both directions by getting attached to a Pipeline.

type StatsCounter

type StatsCounter interface {
	CountBroadcastChat()
	CountUnicastChat()
}

type StatsGenerator

type StatsGenerator interface {
	Stat(details bool) *HubStat
}

type StatsManager

type StatsManager interface {
	ConnectionCounter
	StatsCounter
	StatsGenerator
}

func NewStatsManager

func NewStatsManager(clientStats ClientStats, roomStats RoomStats, userStats UserStats) StatsManager

type Tickets

type Tickets interface {
	SessionValidator
	SessionEncoder
	DecodeSessionToken(token string) (st *SessionToken)
	FakeSessionToken(userid string) *SessionToken
}

func NewTickets

func NewTickets(sessionSecret, encryptionSecret []byte, realm string) Tickets

type TokenFile

type TokenFile struct {
	Path   string
	Info   os.FileInfo
	Reload func()
	Tokens map[string]bool
}

func (*TokenFile) ReloadIfModified

func (tf *TokenFile) ReloadIfModified() error

type TokenProvider

type TokenProvider func(token string) string

func TokenFileProvider

func TokenFileProvider(filename string) TokenProvider

type TurnDataCreator

type TurnDataCreator interface {
	CreateTurnData(*Session) *DataTurn
}

type Unicaster

type Unicaster interface {
	SessionStore
	OnConnect(*Client, *Session)
	OnDisconnect(*Client, *Session)
	Unicast(to string, outgoing *DataOutgoing, pipeline *Pipeline)
}

type User

type User struct {
	Id string
	// contains filtered or unexported fields
}

func NewUser

func NewUser(id string) *User

func (*User) AddSession

func (u *User) AddSession(s *Session) bool

AddSession adds a session to the session table and returns true if s is the first session.

func (*User) Data

func (u *User) Data() *DataUser

func (*User) RemoveSession

func (u *User) RemoveSession(sessionID string) bool

RemoveSession removes a session from the session table abd returns true if no session is left left.

func (*User) SubscribeSessions

func (u *User) SubscribeSessions(from *Session) []*DataSession

type UserStats

type UserStats interface {
	UserInfo(bool) (int, map[string]*DataUser)
}

type UserStore

type UserStore interface {
	GetUser(id string) (user *User, ok bool)
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL