sdk

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 1 Imported by: 0

Documentation

Overview

Package sdk is what an add-on imports to reach LinkCtrl.

It is generated from the host's own definition of the ABI and it is the only thing an add-on needs: it depends on nothing but the Go standard library, and on no LinkCtrl package at all. That is deliberate and it is asserted by a test — an add-on lives in its own repository, on its own release cycle, and a dependency on this product's internals would make every add-on a fork of it.

Building an add-on

A module is a Go program compiled for wasip1 as a *reactor*, which is what -buildmode=c-shared produces: package initialization runs when the host instantiates it and the module then stays alive to be called into, rather than running main and exiting.

GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o myaddon.wasm .

Beside it goes an addon.json naming the module, its sha256 and the ABI generation it was built against — ABIGeneration, which this SDK carries so that the number in the manifest and the number the code was compiled against come from one place. docs/configuration.md in the LinkCtrl repository documents every manifest field; docs/addon-abi.md documents this ABI and the deprecation policy that governs it.

Publishing the two files as one

An operator can upload the pair, or point their instance at a URL. The URL names a **bundle**: a tar, a gzipped tar or a zip holding addon.json and the module it names, and nothing else — no directory entry, no symlink, no path, no duplicate name, no third file. Ship whichever your release pipeline already emits; the host reads the container out of the leading bytes rather than out of the URL, so the file's name is yours to choose.

tar -czf myaddon-1.0.0.tar.gz addon.json myaddon.wasm
sha256sum myaddon-1.0.0.tar.gz

Publish that second number **somewhere other than the page the URL is on**. The operator types it beside the URL and the host refuses to write anything unless the bundle hashes to it, so it is the whole of what makes a URL install safe — and a digest an operator reads off the same page as the address they are pasting proves nothing about either.

A compressed container is bounded twice by the host — on the wire, and again after it is decompressed — and refused if it expands by more than any module plausibly does. Nothing a build tool produces comes near that, so what it means in practice is that a bundle assembled by hand out of something other than your two files may be turned away for the shape rather than for its size.

What the host grants

Only what is in this package. A module is instantiated with no filesystem, no environment, no arguments and its output discarded.

**The clock and the random source are this machine's**, which is worth stating because the runtime this host is built on defaults to fakes for both and this paragraph said so until ABI 0.1.1. time.Now inside a module is the host's wall clock and crypto/rand reads the operating system's entropy, so the standard library does what you expect and code you wrote against it needs no change. TimeNow and RandomBytes are the same two sources with a documented shape — RFC 3339 in UTC, and a count you name — and they exist so a nonce, a `state` parameter or a PKCE verifier has an answer in the published contract rather than only in a runtime detail. Neither costs a permission.

If you are reading this because you found the old sentence: a module built against an earlier SDK is unaffected and does not need rebuilding. The fix is underneath crypto/rand and time.Now, not in the two functions.

A module also gets a bounded amount of memory — 8 MiB of linear memory, with a fresh instance per request — and growing past it traps, which the host answers as a 502 for that one request. It is room for a request's work rather than for a cache: what an add-on wants to keep goes in the schema its storage grant gives it, which is also the only thing that outlives the instance.

**On the redirect path the instance is reused, and it makes no difference to what you may keep.** Building one per redirect cost the visitor 11 ms on a path whose target is 20 ms, so the host keeps instances and hands them on — after restoring your module's memory to exactly what your package initialization left. A package-level variable you write during one redirect is empty on the next, the same as if the instance had been destroyed, and the schema is still the only thing that survives. What *is* different is that your `init` runs once per instance rather than once per invocation, so anything it does outside memory — a log line, a storage write — happens once for many redirects rather than once each. A module whose memory section *demands* more than the bound as its minimum is refused at load, with the add-on named. A toolchain that pins a larger maximum instead costs nothing and changes nothing: the runtime substitutes its own limit for that declaration, and the instance gets 8 MiB either way.

Cookies are bounded in a way worth knowing before you design a flow. You name them and read them back by name, but the host carries the whole set inside one cookie of its own, so an add-on's share of a browser's cookie store is fixed rather than chosen — about 3 KiB, past which the oldest values are dropped. A key to your own storage fits; a flow's state does not belong there.

**Your routes are rate limited, and the budget is the instance's sign-in budget.** Every request that reaches a route your add-on serves is charged against LINKCTRL_LOGIN_RATE_PER_MIN — the operator's number, and tens of requests a minute per client address rather than thousands — which is the same allowance the login form spends. It applies to every add-on and not only one that can mint a session, and there is no per-add-on budget to raise instead. So a provider's server-to-server callback that retries hard, and a page of yours a browser polls, are both spending an allowance somebody else's sign-in also needs; a refusal is a 429 the host answers, and your module is not entered. A path under /addons/ naming no installed add-on is a 404 refused on shape and is charged to nobody. docs/addon-abi.md states it in full.

Every function returns an error from the closed set in this package. A function this ABI declares and this host has not implemented yet answers ErrNotAvailable, which is a fact a module may branch on: the ABI is complete as a contract one release before it is complete as behaviour, and probing for a capability is how a single module works on two hosts.

Off wasm

Every function in this package compiles for any GOOS, so an add-on's own tests build and run natively. Off wasip1 each one returns an error saying so, which is the honest answer: the host is not there.

Index

Constants

View Source
const (
	LevelDebug = "debug"
	LevelInfo  = "info"
	LevelWarn  = "warn"
	LevelError = "error"
)

The levels Log accepts. Anything else is ErrInvalid.

View Source
const ABIGeneration = 1

ABIGeneration is the integer a manifest's abi_version field must carry for a module built against this SDK.

View Source
const ABIVersion = "0.1.5"

ABIVersion is the ABI version this SDK was generated from. An add-on's manifest declares ABIGeneration in its abi_version field; the host refuses a module built against a generation it does not implement, before instantiating it.

Variables

View Source
var (
	// The host failed at something that is not the add-on's fault; it has
	// logged the detail.
	ErrInternal = errors.New("linkctrl: the host failed at something that is not the add-on's fault; it has logged the detail")
	// This ABI declares the function and this host does not implement it yet.
	ErrNotAvailable = errors.New("linkctrl: this ABI declares the function and this host does not implement it yet")
	// The add-on did not declare this capability, or declared it and may not
	// have it.
	ErrDenied = errors.New("linkctrl: the add-on did not declare this capability, or declared it and may not have it")
	// A well-formed request for something that is not there.
	ErrNotFound = errors.New("linkctrl: a well-formed request for something that is not there")
	// The arguments were the add-on's fault: a length outside its memory, text
	// that is not UTF-8, or a value outside the vocabulary.
	ErrInvalid = errors.New("linkctrl: the arguments were the add-on's fault: a length outside its memory, text that is not UTF-8, or a value outside the vocabulary")
)

The errors a host function can answer with. Every one is comparable with errors.Is, and a status this SDK does not know arrives as an unwrapped error naming the number — which is what a module built against an older SDK sees when a newer host refuses it for a reason that did not exist yet.

Functions

func ConfigGet

func ConfigGet(key string) (string, error)

ConfigGet reads one of this add-on's own settings. The key must be one the add-on's manifest declares; anything else is ErrDenied, which is what scopes the function to the add-on rather than to the instance — there is no way to ask for another add-on's setting or for one of this product's own configuration values. A declared setting with no value yet answers with the default the manifest gave it. ErrNotFound means the setting is not declared, or is declared with no default and has no value — the manifest format cannot tell an empty default from an absent one, so the two are one case here rather than two. An operator sets a value in the Add-on manager, which stores it host-side, or with LINKCTRL_ADDON_<NAME>_<SETTING>; either outranks the manifest's default, and the environment outranks the stored value. A value saved in the manager is what this function answers on the add-on's next invocation, and one already inside a call reads what it read.

key is the name of a setting this add-on's manifest declares.

ABI: linkctrl.config_get, since 0.1.0; implemented since this version.

Requires the config.read permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func HTTPRequestRead

func HTTPRequestRead() ([]byte, error)

HTTPRequestRead reads the request that reached one of this add-on's routes. It answers ErrNotFound outside a request, which is what a module calling it from package initialization gets — an instance is made per request and its initialization runs before the request is attached, so this is the ordinary answer during init rather than an edge case. Read twice in one request it answers the same record twice: the host holds it, the guest does not consume it.

ABI: linkctrl.http_request_read, since 0.1.0; implemented since this version.

Requires the routes.own_prefix permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func HTTPResponseWrite

func HTTPResponseWrite(response []byte) error

HTTPResponseWrite answers the request that reached one of this add-on's routes. Called twice for one request it is ErrInvalid: a response is one record, not a stream, because a module that can hold a connection open is a module that can hold every connection open. What the record may carry is bounded by the host and not by the module: `content_type` is a closed vocabulary that does not include text/html, because the host wraps a page and an add-on that could choose the type could choose markup; `location` is answered 302 and never a permanent redirect; and `set_cookie` is bounded by the prefixes the manifest declares and by a `max_age` of at most 400 days, with the host's own Secure, HttpOnly and SameSite attributes applied. Each of those is ErrInvalid rather than a silently corrected response. The cookies themselves are carried in one cookie of the host's rather than written individually, so what an add-on occupies in a browser does not grow with what it sets or with how often it is visited — a set too large to pack into one is ErrInvalid at this call.

response is the response, as an HTTPResponse record.

ABI: linkctrl.http_response_write, since 0.1.0; implemented since this version.

Requires the routes.own_prefix permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func HostABIVersion

func HostABIVersion() (string, error)

HostABIVersion is the ABI version of the host this module is running in. A module's manifest declares the generation it was built against and the host refuses a mismatch before instantiation, so this is not how a module checks compatibility — it is how one logs what it is talking to, and how it decides whether a function added in a later patch is worth probing for.

ABI: linkctrl.abi_version, since 0.1.0; implemented since this version.

func IdentityLink(claim []byte) error

IdentityLink connects an external identity to the account of the person who is **already signed in** on this request, and it is the only way anything an add-on does writes that mapping. It is session_mint's mirror and its precondition: a subject nobody has linked mints nothing, and a subject can only be linked while its owner is in front of the browser. So the two functions have opposite requirements — this one is ErrDenied when nobody is signed in, and session_mint is ErrDenied when somebody is — which is what stops either being used to do the other's job. Linking the same subject to the same account twice succeeds and changes nothing; linking one another account already holds is ErrDenied and never moves it, because a link is a credential and re-pointing one is the takeover this table exists to prevent. An API key is not a person and cannot be the signed-in party. **Your callback still needs its own CSRF defence.** The host's guarantee is that a link is only ever made for whoever is signed in, in their own browser, at that moment; whether that browser meant to be there is what OAuth's `state` parameter is for, and it is yours.

claim is who was authenticated, as a SessionClaim record.

ABI: linkctrl.identity_link, since 0.1.1; implemented since this version.

Requires the session.mint permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func Log

func Log(level string, message string) error

Log writes one line to the host's logger, attributed to this add-on. It is the only way out: a module's stdout and stderr are discarded, because routing them into an operator's log is a capability and the host grants none it was not asked for. The host adds the add-on's name; a message that repeats it is noise. An unknown level is ErrInvalid rather than a silent default, so a typo does not become a line nobody greps for. The message is neutralized before it is written and bounded at 4 KiB, and the rule is stated as what survives rather than as what is caught: a graphic character reaches the line as itself, in any script, and everything else becomes its escape — a newline, a control character, an ANSI escape, every format and bidirectional control, every unassigned or private-use code point, and the 268 graphic code points this host treats as invisible: the 267 graphic members of Unicode's derived Default_Ignorable_Code_Point, which the host computes rather than reads because Go ships only the residue property the derivation subtracts from, plus U+2800 BRAILLE PATTERN BLANK, the one blank that is not whitespace. One class is deleted rather than escaped, and it is the only one: every variation selector is removed from the message. So a heart written as U+2764 U+FE0F arrives as U+2764 and is still a heart, an emoji that carries no selector is untouched, and a selector hung off a letter, a space, an ideograph or a block element takes nothing with it when it goes. There is no exemption and no base list: a selector after a character the reader's renderer does not vary is invisible, and no property tells the host which those are. That set is a published property and not the set of characters that render as nothing, because Unicode publishes no such property: eight combining marks it annotates as not visibly rendered — U+2D7F, U+17D2, U+10A3F, U+1107F, U+11A47, U+11A99, U+11F42 and U+16FE4 — reach the line as themselves, as do seventeen space characters and the prepended concatenation marks named below. What bounds that residue is that this log is write-only to you: Log declares no out-parameter, no function in this ABI hands log content back, your module gets no preopened file and its stdout and stderr are discarded, and your storage is a schema this log does not live in. So a character that survives is one an operator can still see; it is not a channel you can read back. A code point Unicode adds after the host was built is escaped rather than let through. One graphic character does not reach the line as itself: a backslash is doubled, so that the two characters \ and n cannot be mistaken for an escaped newline, and a module cannot spell the host's own truncation mark. The named exceptions run the other way: Unicode's prepended concatenation marks — the Arabic, Syriac and Kaithi signs that scope the digits after them — are left alone, read from Unicode's property rather than from a list, so a host built against a newer revision carries the marks it added. Nothing is refused for any of it, and a message that needed none arrives as it was written, backslashes aside.

level is one of the Level constants; message is the line, without a trailing newline.

ABI: linkctrl.log, since 0.1.0; implemented since this version.

func NetworkFetch

func NetworkFetch(request []byte) ([]byte, error)

NetworkFetch makes one outbound request from the host and hands you what came back. It is the only way out of this sandbox and it is bounded on every axis the host can bound it on. **Where** is the operator's: the URL's origin — scheme, host and port — must be one they named in a setting your manifest declared as carrying origins, and an add-on configured with none reaches nothing at all. Your manifest cannot name a host, so a discovery document pointing at a second origin is a second origin the operator has to authorize before you can follow it; that is the bound, and it is why an issuer whose token endpoint lives on another name needs both written down. **What** is the host's: https only, GET or form-encoded POST, no request headers of your choosing — the host sets Accept, Content-Type and its own User-Agent — and no response header reaches you but the content type, so nothing a third party sets in a browser can be laundered through this call. **How far** is fixed: every address the name resolves to is checked at the moment of dialling, so loopback, link-local, unique-local, the private ranges and this machine's own metadata service are refused however the name got there; a redirect is followed only on the origin it started on; the response is cut off at the host's size cap; and the whole call is bounded by the host's timeout and by whatever is left of the invocation's own. **When** is the class: this is callable from a route handler and from nowhere else, because an inline module holds a visitor's request open against a deadline in milliseconds and an observing one has no caller to spend a budget against. The two redirect classes are refused in **two different places** and you branch on two different things. An **inline** invocation never reaches this function at all: it is outside the redirect-safe subset, so the call is ErrDenied — the same refusal storage_query gets there, and deliberately the same one an undeclared permission gets, so it is uncounted and tells you nothing about what the host implements. An **observing** invocation reaches it and comes back with the `class_refused` outcome, which is a counter label. Nothing here traps in either case: the answer is a FetchResponse whose `outcome` says what happened, from a closed vocabulary you can branch on, and an operator sees the same word as a counter label — or, in the inline case, the ErrDenied every function outside that subset returns. **One call is one request**: a response too large for the buffer you offered is held by the host and handed to your retry rather than fetched again, which is the calling convention's *a function that changes something* rule answered for a change that happens on somebody else's server.

request is what to fetch, as a FetchRequest record.

ABI: linkctrl.network_fetch, since 0.1.4; implemented since this version.

Requires the network.fetch permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func RandomBytes

func RandomBytes(count int32) ([]byte, error)

RandomBytes draws bytes from the operating system's entropy source, through the host. It is what a nonce, a `state` parameter or a PKCE verifier is built from. A count outside 1..4096 is ErrInvalid rather than a clamped answer, because a caller that asked for the wrong number of bytes wanted a different number and not a shorter one. Nothing about this function is a permission: every module already reaches the same source through crypto/rand, which the host wires to the same reader, so gating it would buy an operator nothing and cost every manifest a line.

count is how many bytes to draw, at most 4096.

ABI: linkctrl.random_bytes, since 0.1.1; implemented since this version.

func RedirectAnswerWrite

func RedirectAnswerWrite(answer []byte) error

RedirectAnswerWrite is how an inline module answers, and it is the only channel it has: `linkctrl_redirect_inline` returns a status and not a payload, for the reason the request handler does. Not calling it is the ordinary case and means *allow* — a module that only watches writes nothing, and a module the host had to kill wrote nothing either, so the two agree. A verdict of `veto` refuses the visitor with the same page a gate refuses with; the alias, the destination and the reason are never echoed to them. A `query` alters the destination's query string and costs redirect.rewrite_query on top of redirect.inline — ErrDenied without it — and it is a **replacement** rather than a merge: what you write is the whole query, and an empty string with `rewrite` set removes it. You cannot reach the scheme, the host, the port or the path, because the host substitutes your query into the URL it already decided rather than accepting a URL from you. A query carrying anything outside RFC 3986's query characters is ErrInvalid, and so is a verdict outside the vocabulary. Called twice in one invocation the second is ErrInvalid, for the reason http_response_write is.

answer is your verdict, as a RedirectAnswer record.

ABI: linkctrl.redirect_answer_write, since 0.1.2; implemented since this version.

Requires the redirect.inline permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func RedirectDecisionRead

func RedirectDecisionRead() ([]byte, error)

RedirectDecisionRead reads the redirect this module is being asked about, while the visitor waits. The host calls your `linkctrl_redirect_inline` export after it has decided where the visitor goes and **before it has written anything** — before the gates that spend a link's budget, so a veto costs nobody a click. What crosses is the decision and not the visitor: the link, the alias and the destination, and no field derived from the person in front of the browser. Watching visitors is redirect.observe's job and it happens off this path. Outside an inline invocation this is ErrNotFound.

ABI: linkctrl.redirect_decision_read, since 0.1.2; implemented since this version.

Requires the redirect.inline permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func RedirectEventRead

func RedirectEventRead() ([]byte, error)

RedirectEventRead reads the redirect this add-on is observing. What it carries is at most what click_events may carry — prefix-derived and country-level, and no client address in any form. The grant it costs is redirect.observe, which is out-of-band observation and nothing more: running inside the redirect path itself is redirect.inline, a separate declaration, so a module cannot reach the path by holding this. The host calls your `linkctrl_redirect_observe` export once per recorded redirect, **after the visitor has already been answered and after the click is durable**, so nothing you do here can delay or fail a redirect — and nothing you do here can affect one either. Outside such an invocation it is ErrNotFound, which is what a module calling it from package initialization gets. An instance that could not be given the event within the host's own bound is dropped rather than queued: observation is best-effort by construction, exactly as the click pipeline it is fed from is.

ABI: linkctrl.redirect_event_read, since 0.1.0; implemented since this version.

Requires the redirect.observe permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func SessionContextRead

func SessionContextRead() ([]byte, error)

SessionContextRead asks the host who is signed in on the request this add-on is answering. It is the *read* half of the session boundary and the whole of it: what comes back is an identity and where it is working, never a cookie, a token or a session row, so an add-on can draw a page for the person in front of it and cannot act as them anywhere else. Nobody signed in is not an error — add-on routes are reachable without a session, because a sign-in flow could not otherwise begin — so the record's `signed_in` is false and every other field is empty. Outside a request it is ErrNotFound, which is what a module calling it from package initialization gets. Minting a session is session_mint and is a different grant.

ABI: linkctrl.session_context, since 0.1.0; implemented since this version.

Requires the session.context permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func SessionMint

func SessionMint(claim []byte) ([]byte, error)

SessionMint tells the host that this add-on authenticated somebody, and asks for a session. The add-on does not make a session and never sees a token: it makes an assertion, the host decides whether an account exists for it and what the session may do, and the cookie is written by the host. That split is what keeps the host, and not an add-on, the authority over who is signed in. What comes back is a MintedSession, and it is enumerated for the same reason the claim is: an answer described only as "a JSON object" is an answer the credential assertion over this surface cannot read. Four host rules decide whether anything is minted, and each is a status rather than a page: the claim must name a subject and an issuer (ErrInvalid); that subject must already be linked to an account, through a linking flow the host owns and this function is not (ErrNotFound); the account must be active and not locked out (ErrDenied); and nobody may already be signed in on the request, because a mint is how somebody signs in and not how a browser changes who it is (ErrDenied). Called twice in one request the second is ErrInvalid, for the reason http_response_write is. An account with a second factor enrolled meets it after this call rather than instead of it: the host answers with second_factor_required set, and sends the visitor to its own prompt before your response's location. **What that replaces is your response, and not your cookies**: every set_cookie you made on the request is written to the browser either way, so a callback that clears the `state` cookie it set at the start clears it for an account with a second factor exactly as for one without. You cannot see which kind of account you asserted about, so nothing about your flow's own state may depend on it. **The out buffer is checked before anything is minted**, which is the one place this ABI's retry convention needs saying twice: a buffer too small for the record answers with the size to retry at and mints nothing, so the retry is the first mint rather than a second one and the sentence above about the second call keeps meaning what it says. A buffer of zero, offered to ask for the size, costs nothing for the same reason. The generated SDK starts larger than the record and never sees it.

claim is who authenticated, as a SessionClaim record.

ABI: linkctrl.session_mint, since 0.1.0; implemented since this version.

Requires the session.mint permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func StorageExec

func StorageExec(sql string, args []byte) error

StorageExec runs a write against the Postgres schema this add-on owns. Migrations are not this function: the host runs an add-on's migrations, which is what keeps *DDL is additive within a minor version* a promise somebody can keep — the add-on ships them in its own `migrations/` directory and names each with its digest in the manifest, and the host applies them at load inside the same schema this function writes to. Everything StorageQuery says about the boundary, the single statement and the arguments applies here too; what differs is that the transaction is not read-only.

sql is a statement against this add-on's own schema; args is positional arguments, as a JSON array.

ABI: linkctrl.storage_exec, since 0.1.0; implemented since this version.

Requires the storage.own_schema permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func StorageQuery

func StorageQuery(sql string, args []byte) ([]byte, error)

StorageQuery runs a read against the Postgres schema this add-on owns. The schema boundary is the whole of the permission: an add-on names no database, no connection and no search_path, and a statement that reaches outside its own schema is refused rather than executed — ErrDenied, which is distinguishable from ErrInvalid so that a module can tell confinement from its own mistake. One statement per call: the host parses through the extended protocol, so a payload carrying two is refused. The read is a read at the server, in a READ ONLY transaction, so this function cannot be used to write. Arguments are a JSON array of strings, numbers, booleans and nulls; pass JSON as a string and cast it. Rows come back as a JSON array of objects keyed by column name, and a result with two columns of one name is refused rather than collapsed.

sql is a statement against this add-on's own schema; args is positional arguments, as a JSON array.

ABI: linkctrl.storage_query, since 0.1.0; implemented since this version.

Requires the storage.own_schema permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func TemplateRender

func TemplateRender(name string, data []byte) ([]byte, error)

TemplateRender renders one of this add-on's own templates through the host's renderer, so a page an add-on draws inherits the product's escaping, its theme tokens and its Content-Security-Policy. It is also how an add-on reaches the page without bringing a front-end toolchain: it renders nothing itself. A host that does not implement it yet answers ErrNotAvailable.

name is a template this add-on shipped; data is the template's data, as a JSON object.

ABI: linkctrl.template_render, since 0.1.0; declared, and not implemented by every host: a host without it answers ErrNotAvailable, which a module may branch on.

Requires the routes.own_prefix permission, declared in this add-on's manifest. A module that did not declare it gets ErrDenied, whether or not the host implements the function.

func TimeNow

func TimeNow() (string, error)

TimeNow is the host's wall clock, which is this machine's. It is what an expiry is compared against and what a record's timestamp is stamped from. UTC and RFC 3339, so there is one spelling to parse and no zone to guess. Ungated for the reason random_bytes is: a module already reads the same clock through time.Now, and this is the same value with a documented shape.

ABI: linkctrl.time_now, since 0.1.1; implemented since this version.

Types

This section is empty.

Jump to

Keyboard shortcuts

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