Apps schrijven (plugins)

Apps voegen functionaliteit toe aan Antimatter van binnenuit: een servergedeelte in Go dat zich aan de server koopt, en een webappgedeelte in React dat menu’s, panelen, berichttypes en pagina’s toevoegt. Deze pagina toont hoe een app is opgebouwd, met de meegeleverde Peilingen-app als voorbeeld, en wat er verandert in de Fusion-interface van Antimatter.

Hoe een app werkt

  • Het servergedeelte is een Go-programma. De server start dit als een apart proces en communiceert ermee via RPC: het roept de hooks van de app aan (een bericht staat op het punt te worden gepost, een opdracht is uitgevoerd…), en de app roept de plugin-API van de server aan (een bericht aanmaken, een gebruiker lezen, gegevens opslaan…). Het kan ook HTTP-routes aanbieden onder /plugins/<app id>/.
  • Het webappgedeelte is een JavaScript-bundel die in de browser wordt geladen samen met de webapp. Het registreert React-componenten en callbacks bij het register: een knop in een menu, een paneel, een manier om een berichttype weer te geven.
  • Elk van beide onderdelen is optioneel. Een app wordt geïnstalleerd als een .tar.gz-bundel, en systeembeheerders schakelen deze in, uit en configureren deze.
Het servergedeelte wordt uitgevoerd met volledige toegang tot de gegevens van de server. Behandel een app als code die je op de server uitrolt.

Antimatter voert apps uit die voor Mattermost zijn geschreven, en omgekeerd, zolang een app alleen hooks gebruikt die beide hebben. Zie Alleen door Antimatter beschikbare hooks.

De bundelstructuur

Er is geen apart starttemplate: begin bij een kleine meegeleverde app zoals antimatter-plugin-polls of antimatter-plugin-todo, en wijzig de ID en de code. De Peilingen-app is als volgt opgebouwd:

antimatter-plugin-polls/
├── plugin.json            the manifest
├── Makefile               build, test, bundle, deploy
├── build/                 the manifest and pluginctl helper tools (Go), setup.mk
├── assets/icon.svg        files shipped with the bundle
├── go.mod
├── server/
│   ├── main.go            plugin.ClientMain(&Plugin{})
│   ├── plugin.go          the Plugin type, OnActivate, ServeHTTP
│   ├── manifest.go        generated from plugin.json
│   ├── api.go, command.go, hooks.go, store.go …
│   └── dist/              compiled binaries, one per platform
└── webapp/
    ├── package.json, webpack.config.js, tsconfig.json
    ├── i18n/              en.json and one file per language
    ├── src/index.tsx      registers the app with the web app
    ├── src/manifest.ts    generated from plugin.json
    ├── src/translations.ts
    └── dist/main.js       the bundle

Optioneel: een public/-map, die ongewijzigd wordt aangeboden op /plugins/<app id>/public/.

plugin.json

{
  "id": "com.example.weather",
  "name": "Weather",
  "description": "Forecasts in the chat.",
  "homepage_url": "https://github.com/example/weather",
  "support_url": "https://github.com/example/weather/issues",
  "icon_path": "assets/icon.svg",
  "version": "1.0.0",
  "min_server_version": "12.0.0",
  "server": {
    "executables": {
      "linux-amd64": "server/dist/plugin-linux-amd64",
      "linux-arm64": "server/dist/plugin-linux-arm64",
      "darwin-amd64": "server/dist/plugin-darwin-amd64",
      "darwin-arm64": "server/dist/plugin-darwin-arm64",
      "windows-amd64": "server/dist/plugin-windows-amd64.exe"
    }
  },
  "webapp": {"bundle_path": "webapp/dist/main.js"},
  "settings_schema": {"settings": []}
}
  • id: uniek en permanent; een omgekeerd domeinnaam is gebruikelijk. Het noemt de routes, de opgeslagen instellingen en de WebSocket-gebeurtenissen.
  • version: semantische versie. De meegeleverde apps laten dit weg uit plugin.json; hun bouwwerkzeug haalt het uit de nieuwste v*-git-tag.
  • min_server_version: Antimatter meldt 12.0.0.
  • settings_schema: de instellingenpagina van de app in de Systeemconsole (zie Instellingen).

make apply kopieert het manifest naar server/manifest.go en webapp/src/manifest.ts; make manifest-check valideert het.

Het servergedeelte

Het servergedeelte importeert dezelfde Go-module als Mattermost-apps, github.com/mattermost/mattermost/server/public, via de Go-module-proxy. De main start alleen de app:

package main

import "github.com/mattermost/mattermost/server/public/plugin"

func main() {
	plugin.ClientMain(&Plugin{})
}

Het type Plugin bevat plugin.MattermostPlugin, wat p.API biedt, en implementeert de benodigde hooks:

type Plugin struct {
	plugin.MattermostPlugin
	router *mux.Router
}

func (p *Plugin) OnActivate() error {
	p.router = p.newRouter()
	return p.API.RegisterCommand(&model.Command{
		Trigger:          "weather",
		AutoComplete:     true,
		AutoCompleteDesc: "Show the forecast",
		AutoCompleteHint: "[city]",
	})
}

func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
	city := strings.TrimSpace(strings.TrimPrefix(args.Command, "/weather"))
	return &model.CommandResponse{
		ResponseType: model.CommandResponseTypeEphemeral,
		Text:         "Forecast for " + city + ": sunny",
	}, nil
}

func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
	p.router.ServeHTTP(w, r)
}

Veelvoorkomende hooks

HookWanneer het wordt uitgevoerd
OnActivate, OnDeactivateWanneer de app wordt ingeschakeld of uitgeschakeld, en wanneer de server start en stopt. Registreer slash-opdrachten en bots in OnActivate.
OnConfigurationChangeWanneer de instellingen veranderen; laad ze in met p.API.LoadPluginConfiguration(&cfg).
ServeHTTPVerzoeken naar /plugins/<app id>/…. De server stelt de Mattermost-User-Id-header in wanneer het verzoek van een ingelogde gebruiker komt; weiger verzoeken zonder deze header, tenzij ze publiek bedoeld zijn.
ExecuteCommandEen door de app geregistreerde slash-opdracht is uitgevoerd.
MessageWillBePosted, MessageWillBeUpdatedVoordat een bericht wordt opgeslagen: geef een gewijzigd bericht terug, of een reden om het te weigeren.
MessageHasBeenPosted, MessageHasBeenUpdated, MessageHasBeenDeletedNaderhand, voor neveneffecten.
ReactionHasBeenAdded, ReactionHasBeenRemovedReacties.
UserHasJoinedChannel, UserHasLeftChannel, UserHasJoinedTeam, UserHasLeftTeam, ChannelHasBeenCreatedLidmaatschap en wijzigingen in kanalen. Een team is een community.
FileWillBeUploadedVoordat een bestand wordt opgeslagen: wijzig het of weiger het.
OnPluginClusterEventEen bericht van dezelfde app op een andere server van een cluster.

De volledige lijst, met de exacte signaturen, staat in server/public/plugin/hooks.go; de API staat in server/public/plugin/api.go. Het pluginapi-pakket (server/public/pluginapi) verpakt de API in vriendelijkere helpers.

Handige API-aanroepen

  • CreatePost, UpdatePost, SendEphemeralPost: post als de bot van de app of als een gebruiker.
  • EnsureBotUser(&model.Bot{Username, DisplayName}): maak de bot van de app een keer aan en verkrijg de ID.
  • PublishWebSocketEvent("poll_updated", payload, &model.WebsocketBroadcast{ChannelId: id}): stuur een evenement naar de webapp. Het komt aan als custom_<app id>_poll_updated.
  • OpenInteractiveDialog: open een dialoog.
  • LogInfo, LogError en dergelijke: schrijf naar de serverlog, gemarkeerd met de ID van de app.
  • GetUser, GetChannel, HasPermissionToChannel: controleer wie wat mag doen voordat u handelt namens een gebruiker.

Een app wordt op elke server van een cluster uitgevoerd. Bewaar de status in de sleutel-waarde-opslag of de database, niet alleen in het geheugen.

De AI-service

De server van Antimatter roept AI-modellen aan voor apps via de modelverbindingen die een beheerder heeft ingesteld (zie AI in de beheerdersgids). Een app bewaart nooit een sleutel van een provider: de app geeft aan voor welke persoon en op welke locatie hij handelt, en de server controleert of die persoon de verbinding daar mag gebruiken, past de limieten en budgetten toe, rekent de tokens af op de persoon en de app en controleert de aanroep zonder de inhoud.

MethodeWat het doet
AIChat(actingUserID, channelID, teamID, *ai.ChatRequest)Een chatvoltooiing: berichten, tools, gestructureerde output (JSONSchema).
AIChatStream(…)Hetzelfde, gestreamd: lees de Events van de stream totdat het kanaal wordt gesloten, of sluit het met Close om de aanroep te stoppen.
AIEmbed(…, *ai.EmbedRequest)Embeddings van teksten.
AITranscribe(…, *ai.TranscribeRequest)Spraak naar tekst.
AIDecide(…, *ai.DecideRequest)Gestuurde vragen aan een beslissingsmodel (ja of nee, een keuze, een score) over een tekst.
AIConnectionsForUser(userID, channelID, teamID)De verbindingen die een persoon daar mag gebruiken, met hun modellen en mogelijkheden, nooit hun sleutels.

De ConnectionID van elke aanvraag kiest de verbinding, en Model kiest een van de modellen (leeg voor het standaardmodel van de verbinding). Operation is een korte label voor de gebruiks- en controlelogboeken, nooit de inhoud. De minimale serverversie is 11.11.

Neem actingUserID alleen uit een bron die de server heeft geverifieerd: de Mattermost-User-Id-header van een aanvraag naar uw ServeHTTP, of de gebruiker in de context van een hook (de auteur van een bericht, de gebruiker die een opdracht heeft uitgevoerd). Neem het nooit uit een bericht, een webhook-payload, een URL of een aanvraag van een andere app: uw app zou dan de toegang van iemand anders gebruiken.

Deze methoden en het ai-pakket (server/public/ai) bestaan alleen in de kopie van het module van Antimatter, niet in de versie die door Mattermost is gepubliceerd. Om ze te gebruiken, compileer tegen server/public van Antimatter met een replace-regel in uw go.mod, bijvoorbeeld replace github.com/mattermost/mattermost/server/public => ../antimatter/server/public. Een dergelijke app werkt alleen op Antimatter.

Data opslaan

Elke app heeft zijn eigen sleutel-waarde-opslag in de database van de server:

  • KVSet(key, value), KVGet(key), KVDelete(key): waarden zijn bytes, meestal JSON. Sleutels zijn maximaal 150 tekens lang.
  • KVSetWithExpiry(key, value, seconds): de waarde verdwijnt na die tijd.
  • KVCompareAndSet(key, old, new) en KVSetWithOptions met Atomic: veilige updates wanneer meerdere verzoeken of servers dezelfde sleutel schrijven. Peilingen slaat elke stem op deze manier op en probeert opnieuw bij een conflict.
  • KVList(page, perPage): de sleutels, pagina voor pagina.

Het webapp-deel

Het bundle registreert een klasse met window.registerPlugin. De initialize-methode ontvangt het register en de Redux store:

import manifest from './manifest';

class Plugin {
    initialize(registry, store) {
        registry.registerTranslations((locale) => translationsFor(locale));
        registry.registerPostTypeComponent('custom_poll', PollPost);
        registry.registerWebSocketEventHandler(`custom_${manifest.id}_poll_updated`, (msg) => {
            store.dispatch(pollUpdated(JSON.parse(msg.data.poll)));
        });
        registry.registerReconnectHandler(() => store.dispatch(reloadPolls()));
    }
}

window.registerPlugin(manifest.id, new Plugin());
  • Gedeelde bibliotheken. De webapp biedt React, ReactDOM, Redux, React Redux en React Intl: vermeld ze als webpack externals (React, ReactDOM, Redux, ReactRedux, ReactIntl) in plaats van uw eigen kopieën te bundelen. Zie webpack.config.js van Polls.
  • Uw server-deel aanroepen. Haal ${window.basename}/plugins/<app id>/… op met de opties uit Client4.getOptions() (uit mattermost-redux/client): ze bevatten de headers die de server nodig heeft om een verzoek van de browser te accepteren.
  • Uw eigen status. registerReducer voegt een reducer toe, opgeslagen onder state['plugins-<app id>'].
  • Types. De extra registermethoden van Antimatter staan niet in de gepubliceerde types van Mattermost: Polls declareert het register dat het gebruikt in webapp/src/types/host.ts.

De methoden van het register en hun parameters zijn gedocumenteerd in de code, in webapp/fusion/src/plugins/registry.ts (Fusion) en webapp/channels/src/plugins/registry.ts (klassiek).

Ondersteuning voor de Fusion-interface

Antimatter heeft twee webinterfaces: Fusion, de standaard, en de klassieke. Beide laden dezelfde app-bundles en hebben hetzelfde register, maar Fusion tekent veel dingen zelf en plaatst plugincomponenten op zijn eigen manier. Test uw app in beide.

De interface detecteren

Vóórdat een app wordt geladen, stellen beide interfaces window.antimatterWebUI in op 'fusion' of 'classic', en dezelfde waarde op het wortelelement van de pagina als data-am-web-ui. Op Mattermost is er geen van beide ingesteld.

export function isFusionUI(): boolean {
    return window.antimatterWebUI === 'fusion' || document.documentElement.dataset.amWebUi === 'fusion';
}

De ingebouwde apps gebruiken dit om anders te worden weergegeven. Peilingen toont zijn peilingskaarten en zijn aanmaakscherm in de stijl van Fusion en opent een eigen dialoogvenster voor /poll in Fusion. Gesprekken en spraakkanaal laten de zijbalk en toast-componenten weg die Fusion vervangt door zijn eigen gespreksinterface. De Agents-app plaatst zijn AI-acties in het berichtmenu, als een groep onder Meer acties, in plaats van in de hover-balk. De eigen CSS-classes van Fusion (met het voorvoegsel am-) en de dialooglaag (het am-layer-element) zijn wat deze apps gebruiken om de interface te matchen; het is geen gedocumenteerde, stabiele API.

Waar Fusion plugin-componenten toont

RegistratiemethodeWaar het in Fusion verschijnt
registerAppBarComponent, registerChannelHeaderButtonActionDe app-rail, de kolom met pictogrammen aan de rechterkant, tussen je avatar en de knop Over Antimatter. Knoppen in de kanaalkop gaan daar ook naartoe, niet in de kanaalkop zelf. Verborgen wanneer een systeembeheerder Apps-balk uitschakelen inschakelt (ExperimentalSettings.DisableAppBar, standaard uit).
registerRightHandSidebarComponentHet paneel aan de rechterkant. Gebruikers kunnen het verkleinen door de rand te verslepen en het in een nieuw venster te openen. Als de app ook een product is, biedt het paneel Openen als volledige pagina. Zie Het paneel in een nieuw venster.
registerChannelHeaderIconPictogrammen in de kanaalkop, na de gespreksknoppen; op telefoons in het menu Meer van de kop.
registerCallButtonActionDe gespreksknop in de kanaalkop; meerdere providers delen een menu Andere manieren om te bellen.
registerPostDropdownMenuActionHet menu Acties voor berichten (⋯) van het bericht, in het submenu Meer acties; ook in het menu Acties voor draadje van het draadpaneel, onder Meer acties. Fusion toont het pictogram van de app vóór elke actie, in grijs: het eigen pictogram voor de ingebouwde apps, anders het pictogram dat de app toont in de app-rail, anders een neutraal pictogram. Een element zonder tekst aan het begin van de actietekst (een pictogram dat de app zelf tekent) wordt verborgen.
registerPostDropdownSubMenuActionEen groep met een kop in hetzelfde submenu Meer acties, na de individuele acties. Geen app krijgt een eigen item in het berichtmenu.
registerPostActionComponentDe hover-balk van het bericht, vóór de ⋯-knop.
registerPostTypeComponent, registerPostMessageAttachmentComponent, registerPostHeaderComponent, registerPostWillRenderEmbedComponentHet bericht zelf: de inhoud, onder de tekst, na de tijd, en ter vervanging van linkvoorbeelden.
registerChannelHeaderMenuActionHet menu Kanaalopties van het kanaal (⋯ op een kanaal in de zijbalk, of rechtermuisklik), als items met een stekkerpictogram.
registerMainMenuActionHet gemeenschapsmenu (de naam van de gemeenschap bovenaan de kanaallijst), onder Meer acties, gegroepeerd per app.
registerSidebarBrowseOrAddChannelMenuActionHet gemeenschapsmenu, na Kanaal aanmaken en Categorie aanmaken.
registerFileUploadMethodHet +-menu van de invoerbalk (Toevoegen aan bericht), na Bestand uploaden.
registerPostEditorActionComponentDe opmaakbalk van de invoerbalk, wanneer de opmaak wordt weergegeven.
registerAIActionMenuItemComponentHet AI-menu van de invoerbalk.
registerFileDropdownMenuAction, registerFilePreviewComponentHet optiemenu van een bestand en de voorvertoning van het bestand.
registerUserSettingsProfiel & instellingen, met één tab per app onder de kop Plugins.
registerChannelSettingsTabHet dialoogvenster voor kanaalinstellingen, onder de kop Apps.
registerPopoverUserAttributesComponent, registerPopoverUserActionsComponentDe profielkaart die wordt weergegeven wanneer u op een naam of een avatar klikt.
registerLeftSidebarHeaderComponent, registerSidebarChannelLinkLabelComponent, registerChannelIconOverride, registerBottomTeamSidebarComponentDe bovenkant van de kanaallijst van een community, naast de naam van een kanaal, het pictogram van een kanaal en de onderkant van de community-zijbalk aan de linkerkant.
registerChannelIntro, registerChannelIntroButtonAction, registerChannelToastComponent, registerNewMessagesSeparatorActionComponent, registerChannelComposerBannerComponent, registerComposerPlaceholderHet begin van de geschiedenis van een kanaal, de bovenkant van de berichtenlijst, de onlezenlijn, boven de invoerbalk en de placeholder van de invoerbalk.
registerChannelTypeOptionDe kanaaltypen in het dialoogvenster Kanaal aanmaken.
registerSearchComponentsHet zoekveld in de kanaalkop: appbereiken verschijnen wanneer het leeg is.
registerUserGuideDropdownMenuActionOver Antimatter (onderaan de app-zijbalk), onder Help.
registerProductVolledige pagina’s op het product-URL, vermeld onder Producten in Over Antimatter. Productpagina’s behouden het klassieke paginaframe, waarvan het productmenu alleen wordt weergegeven wanneer een app een product registreert of een item toevoegt. De ingebouwde Kanban- en Playbooks-apps registreren geen producten meer: hun pagina’s openen in hun zijpanelen.
registerRootComponent, registerGlobalComponent, hooks zoals registerMessageWillBePostedHook, registerSlashCommandWillBePostedHook, registerMessageWillFormatHook, registerFilesWillUploadHook, registerDesktopNotificationHook, registerWebSocketEventHandler, registerReducer, registerTranslationsWerk zoals in de klassieke interface.
registerAdminConsolePlugin, registerAdminConsoleCustomSettingDe Systeemconsole, die in beide interfaces de klassieke is.

Wat Fusion niet weergeeft of uitvoert

  • registerPostDropdownMenuComponent (een component in het berichtmenu), registerPostCardTypeComponent (een kaart van een bericht in het paneel aan de rechterkant) en registerProductSwitcherMenuItem worden niet weergegeven in de hoofdweergaven van Fusion. Gebruik in plaats van de eerste registerPostDropdownMenuAction.
  • registerMessageWillBeUpdatedHook wordt niet uitgevoerd wanneer een bericht in Fusion wordt bewerkt. Controleer bewerkingen op de server met de MessageWillBeUpdated-hook.
  • registerActionAfterChannelCreation wordt alleen uitgevoerd vanuit het klassieke dialoogvenster voor kanaalaanmaak, dat Fusion opent via Meer opties… in het dialoogvenster Kanaal aanmaken.
  • Componenten in de appbalk worden weergegeven ongeacht hun supportedProductIds.
  • Routes van registerNeedsTeamRoute en registerCustomRoute behouden het klassieke paginakraam, zoals producten.

Hooks die alleen Antimatter heeft

Deze registratiemethoden bestaan niet in Mattermost. Controleer of een methode bestaat voordat je hem aanroept, zodat de app daar ook werkt:

if (typeof registry.registerComposerMenuItem === 'function') {
    registry.registerComposerMenuItem({
        text: 'Create a poll',
        icon: <i className='icon icon-chart-bar'/>,
        fusionIcon: 'poll',
        action: ({channelId, rootId}) => openPollCreation(channelId, rootId),
    });
} else {
    registry.registerChannelHeaderButtonAction(icon, (channel) => openPollCreation(channel.id), 'Create a poll', 'Create a poll');
}
MethodeWat het toevoegt
registerComposerMenuItem({text, icon, fusionIcon, action, shouldRender})Een item in het +-menu van het berichtvak, in beide interfaces. action ontvangt {channelId, rootId}.
registerEmojiPickerTab({id, label, icon, fusionIcon, order, component, shouldRender, replacesGifPicker})Een tab van de emoji-kiezer in het berichtvak, zoals GIFs of stickers. In Fusion komen tabs met een order onder de 10 vóór de Emoji-tab. De component ontvangt channelId, rootId, filter, onFilterChange, onSelectDone, insertText, isFusion en, in Fusion, pickerKit (zie hieronder).
registerChannelViewPanel({matcher, component})Een paneel tussen de kanaalkop en de berichten, voor de kanalen die matcher(state, channel) selecteert. Het kan de berichten en de invoerbalk verbergen (setMessagesVisible), zoals spraakkanaal dat doet.
registerSidebarChannelFooterComponent(component)Een regel onder een kanaal in de zijbalk, zoals de personen in het gesprek. Het ontvangt het channel.
registerIntegrationsOption({title, description, fusionIcon, action, shouldRender})Alleen Fusion: een item op de pagina's Integraties, naast aangepaste emoji's. De GIFs-app voegt daar zijn aangepaste GIFs en stickers toe.

Weergaven toevoegen en beheren in de emoji-kiezer

In Fusion krijgt een emoji-kiezer tab een pickerKit-prop: de componenten die de Emoji-tab gebruikt voor zijn eigen toevoegen- en beheren-weergaven, zodat de weergaven van een tab er hetzelfde uitzien en binnen de kiezer blijven. De GIFs- en Memes-apps gebruiken dit om GIFs, stickers en memetemplates toe te voegen en te beheren. Het bevat version (1), de hooks usePickerView en useOnEscape, en de componenten PickerViewButtons (de +- en beheren-knoppen naast het zoekvak), PickerViewFrame (een weergave met een terugpijl), PickerNotice, PickerField, PickerSearch, PickerDropZone en PickerManageList. Escape gaat terug van een weergave naar het raster van de tab. Controleer of de prop aanwezig is: andere hosts laten hem weg, en de tab gebruikt dan zijn eigen dialoogvensters. De types staan in webapp/fusion/src/types/store/plugins.ts (EmojiPickerTabProps) en de componenten in webapp/fusion/src/fusion/popovers/picker_views.tsx.

Het paneel in een nieuw venster

Fusion geeft het component van registerRightHandSidebarComponent twee extra props mee, zodat Openen in nieuw venster het paneel opent op de plek waar het zich bevindt, in plaats van op het startscherm van de app:

  • setPanelState(state): roep dit aan wanneer het paneel van plaats verandert, met een korte tekenreeks die uw app definieert, zoals 'note/<id>' of 'board/<id>/card/<id>', of null op het startscherm. Deze waarde komt in het adres van het venster, dus plaats er nooit een geheim in. Een staat van meer dan 512 tekens, of met controletekens, wordt genegeerd.
  • initialPanelState: in het nieuwe venster is dit de staat die het paneel had, of undefined. Behandel dit als onbetrouwbare invoer: haal het item opnieuw op als de aangemelde gebruiker, en toon het startscherm van de app of een 'niet gevonden'-weergave als ze het niet kunnen openen.

De staat wordt vergeten wanneer het paneel wordt gesloten of wanneer het paneel van een andere app de plek inneemt. Om zelf het venster van een paneel te openen, gebruikt u popoutRhsPlugin (ook te vinden in window.WebappUtils.popouts), waarbij de staat als optioneel vijfde argument wordt doorgegeven. Notities, Whiteboard, Te doen, Kanban en Playbooks maken gebruik van deze functionaliteit.

fusionIcon noemt een pictogram uit de pictogramset van Fusion (bijvoorbeeld poll, sticker, calendar of image; zie webapp/fusion/src/fusion/components/icon). Fusion toont dit in plaats van icon, zodat het item overeenkomt met de andere.

Instellingen

settings_schema in plugin.json beschrijft de pagina van de app in Systeemconsole › Plugins:

"settings_schema": {
  "header": "Forecasts come from the weather service below.",
  "settings": [
    {"key": "ServiceURL", "display_name": "Service URL", "type": "text", "default": "https://api.example.com"},
    {"key": "Units", "display_name": "Units", "type": "dropdown", "default": "metric",
     "options": [{"display_name": "Celsius", "value": "metric"}, {"display_name": "Fahrenheit", "value": "imperial"}]},
    {"key": "EnableAlerts", "display_name": "Severe weather alerts", "type": "bool", "default": false,
     "help_text": "Post alerts in the channels that follow a city."}
  ]
}

Instellingstypen: bool, text, longtext, number, dropdown, radio, generated (een willekeurig geheim), username en custom (getekend door een component dat het webapp-deel registreert met registerAdminConsoleCustomSetting). Instellingen kunnen worden gegroepeerd in sections.

Het serverdeel leest ze in naar een structuur waarvan de velden overeenkomen met de sleutels:

type configuration struct {
	ServiceURL   string
	Units        string
	EnableAlerts bool
}

func (p *Plugin) OnConfigurationChange() error {
	var cfg configuration
	if err := p.API.LoadPluginConfiguration(&cfg); err != nil {
		return err
	}
	p.setConfiguration(&cfg)
	return nil
}

Beheerders kunnen ze ook instellen in de configuratie, onder PluginSettings.Plugins.<app id>, met sleutels in kleine letters (zie de beheerdersgids).

Vertalingen

Antimatter is vertaald naar 37 talen, en ook de meegeleverde apps. Het webapp-deel doet dit als volgt:

  1. Schrijf elke tekst met React Intl, met een ID en de Engelse tekst: <FormattedMessage id='polls.create' defaultMessage='Create a poll'/> of defineMessages. Voeg de naam van uw app als prefix toe aan de IDs.
  2. Extraheer de Engelse teksten naar webapp/i18n/en.json (de gebundelde apps gebruiken formatjs extract).
  3. Voeg er één bestand per taal naast toe, met dezelfde IDs: fr.json, de.json, pt-BR.json, zh-CN.json…
  4. Geef ze door aan de webapp, die de taal van de gebruiker vraagt:
    import fr from '../i18n/fr.json';
    import ptBR from '../i18n/pt-BR.json';
    
    const catalogs = {fr, 'pt-BR': ptBR};
    
    export function translationsFor(locale) {
        return catalogs[locale] || catalogs[locale.split('-')[0]] || {};
    }
    
    registry.registerTranslations(translationsFor);

Een ontbrekende tekst valt terug op de Engelse defaultMessage. Voor teksten die het servergedeelte verstuurt (antwoorden op opdrachten, botberichten), laadt het pakket pluginapi/i18n de vertaalbestanden die in de bundle zijn opgenomen (bijvoorbeeld assets/i18n) en geeft een localizer in de taal van elke gebruiker.

Bouwen, installeren en debuggen

U heeft Go en Node.js nodig (de versie in het .nvmrc-bestand van de app). In de map van de app:

OpdrachtWat het doet
make distBouwt het servergedeelte voor elk platform en het webappgedeelte, en schrijft de bundle naar dist/<app id>-<version>.tar.gz.
make testVoert de Go- en JavaScript-tests uit.
make check-styleControleert de code (golangci-lint, ESLint) en de TypeScript-types.
make deployBouwt, installeert en activeert de bundle op een server.
make watch, make deploy-from-watchDe eerste bouwt het webappgedeelte elke keer opnieuw wanneer u opslaat; de tweede bundelt wat is gebouwd en installeert het op de server.
make enable, make disable, make resetActiveert, deactiveert of herstart de app op de server.
make logs, make logs-watchToont de regels van de app in het serverlog.
make attachKoppelt de Delve-debugger aan het draaiende servergedeelte (bouw eerst met AM_DEBUG=1).

make deploy en de andere serveropdrachten gebruiken een lokale modus socket als ze er een vinden (/var/tmp/antimatter_local.socket, of AM_LOCALSOCKETPATH): voer ze uit op de machine van de server met de lokale modus ingeschakeld. Anders loggen ze in op de server die is opgegeven door MM_SERVICESETTINGS_SITEURL met AM_ADMIN_TOKEN, of AM_ADMIN_USERNAME en AM_ADMIN_PASSWORD. De bouwt hulpmiddelen lezen nog geen AM_-naam voor de site-URL.

Een bundle handmatig installeren

  • Een systeembeheerder staat uploads toe met AM_PLUGINSETTINGS_ENABLEUPLOADS=true (standaard uit; er is geen schakelaar in de Systeemconsole).
  • Vervolgens Systeemconsole › Plugins › Plugin Beheer › Plugin Uploaden, of amctl plugin add weather.tar.gz gevolgd door amctl plugin enable com.example.weather.
  • Als de server Plugin-handtekening vereist is ingeschakeld, accepteert het alleen bundels die zijn ondertekend met een sleutel waaraan het vertrouwen hecht. Zie Andere apps installeren.
Voor snelle iteraties op een ontwikkelserver, stel AM_SERVICESETTINGS_ENABLEDEVELOPER=true in op de server: fouten tonen dan hun details. De Makefiles van de gebundelde apps bouwen het servergedeelte alleen voor uw platform als MM_SERVICESETTINGS_ENABLEDEVELOPER is ingesteld in uw shell.