@kwirthmagnify/kwirth-common-back 0.5.54 → 0.5.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Consumer.d.ts +8 -0
- package/dist/Consumer.js +53 -0
- package/dist/IExtension.d.ts +30 -0
- package/dist/IFederation.d.ts +3 -3
- package/dist/IFederation.js +5 -5
- package/dist/IIdpConnector.d.ts +1 -1
- package/dist/IIdpConnector.js +6 -6
- package/dist/ILogin.d.ts +2 -2
- package/dist/IPluvider.d.ts +23 -23
- package/dist/IProvider.d.ts +104 -70
- package/dist/ISender.d.ts +3 -3
- package/dist/IWebhook.d.ts +3 -3
- package/dist/KubernetesTools.d.ts +3 -3
- package/dist/KubernetesTools.js +3 -3
- package/dist/github.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/oauth2.js +3 -3
- package/dist/oidc.js +10 -9
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The property the core stamps a consumer instance with. A plain string key, so any producer can read it. */
|
|
2
|
+
export declare const KWIRTH_CONSUMER_ID = "__kwirthConsumerId";
|
|
3
|
+
/** Extension types whose consumer id carries a prefix. Channels do not: their id is the bare channel id. */
|
|
4
|
+
export type TConsumerType = 'provider' | 'sender' | 'webhook' | 'homepage' | 'login' | 'idp' | 'theme' | 'docs' | 'pack';
|
|
5
|
+
export declare const consumerIdFor: (type: TConsumerType, id: string) => string;
|
|
6
|
+
/** Stamps an instance with its qualified consumer id. The core's job; an extension has no reason to call it. */
|
|
7
|
+
export declare const stampConsumerId: (instance: object, consumerId: string) => void;
|
|
8
|
+
export declare const consumerIdOf: (consumer: unknown) => string | undefined;
|
package/dist/Consumer.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Who is consuming a provider.
|
|
4
|
+
|
|
5
|
+
Anything that subscribes to a producer has to be able to NAME itself, or the core's registry of
|
|
6
|
+
who-consumes-what — the one the status graph is drawn from — ends up with anonymous edges, and a
|
|
7
|
+
producer that keeps one credential per consumer (the cloud accounts, say) cannot tell who is asking.
|
|
8
|
+
|
|
9
|
+
🔴 The identity is written by the CORE, never by the extension author: when the core wires an extension
|
|
10
|
+
up to the providers it consumes, it STAMPS the instance with its qualified id — `sender:ses`,
|
|
11
|
+
`webhook:gitlab`, `homepage:status`, `provider:aws` — and hands out a provider access already bound
|
|
12
|
+
to it. A consumer therefore cannot claim to be somebody else and be handed that somebody's secret.
|
|
13
|
+
|
|
14
|
+
Channels are the exception, for history: they name themselves through getChannelData() and their
|
|
15
|
+
consumer id is the bare channel id. A pluvider is a channel that also produces, so it is filed as a
|
|
16
|
+
channel — and the `plugin:` prefix is its id as a PRODUCER, a different thing.
|
|
17
|
+
|
|
18
|
+
This lives in the published contract so that a PRODUCER can read the identity of whoever subscribes
|
|
19
|
+
to it with the very same rule the core applies, instead of each one inventing its own.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.consumerIdOf = exports.stampConsumerId = exports.consumerIdFor = exports.KWIRTH_CONSUMER_ID = void 0;
|
|
23
|
+
/** The property the core stamps a consumer instance with. A plain string key, so any producer can read it. */
|
|
24
|
+
exports.KWIRTH_CONSUMER_ID = '__kwirthConsumerId';
|
|
25
|
+
const consumerIdFor = (type, id) => `${type}:${id}`;
|
|
26
|
+
exports.consumerIdFor = consumerIdFor;
|
|
27
|
+
/** Stamps an instance with its qualified consumer id. The core's job; an extension has no reason to call it. */
|
|
28
|
+
const stampConsumerId = (instance, consumerId) => {
|
|
29
|
+
Object.defineProperty(instance, exports.KWIRTH_CONSUMER_ID, { value: consumerId, enumerable: false, configurable: true, writable: false });
|
|
30
|
+
};
|
|
31
|
+
exports.stampConsumerId = stampConsumerId;
|
|
32
|
+
/*
|
|
33
|
+
The qualified id of whoever subscribed. The stamp wins; without it, the two legacy shapes: a channel
|
|
34
|
+
names itself through getChannelData() (checked FIRST, because a pluvider also carries an `id`), and a
|
|
35
|
+
bare `id` is taken for a provider wired by a core that did not stamp yet.
|
|
36
|
+
|
|
37
|
+
Returns undefined when the consumer cannot name itself at all. The caller decides: a producer hands an
|
|
38
|
+
anonymous consumer only what is meant for everyone, and the core warns and carries on.
|
|
39
|
+
*/
|
|
40
|
+
const consumerIdOf = (consumer) => {
|
|
41
|
+
if (!consumer || typeof consumer !== 'object')
|
|
42
|
+
return undefined;
|
|
43
|
+
const stamped = consumer[exports.KWIRTH_CONSUMER_ID];
|
|
44
|
+
if (typeof stamped === 'string' && stamped.length > 0)
|
|
45
|
+
return stamped;
|
|
46
|
+
const c = consumer;
|
|
47
|
+
if (typeof c.getChannelData === 'function') {
|
|
48
|
+
const id = c.getChannelData()?.id;
|
|
49
|
+
return typeof id === 'string' && id.length > 0 ? id : undefined;
|
|
50
|
+
}
|
|
51
|
+
return typeof c.id === 'string' && c.id.length > 0 ? (0, exports.consumerIdFor)('provider', c.id) : undefined;
|
|
52
|
+
};
|
|
53
|
+
exports.consumerIdOf = consumerIdOf;
|
package/dist/IExtension.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IExtensionExportOptions, IExtensionImportResult } from '@kwirthmagnify/kwirth-common';
|
|
2
|
+
import type { IProviderAccess } from './IProvider';
|
|
2
3
|
/**
|
|
3
4
|
* What an extension writes its log with. The core builds it knowing who the extension is, so the
|
|
4
5
|
* line comes out identified — '[prov] [ERRO] [longhorn] ...' — and the extension only writes the
|
|
@@ -17,7 +18,36 @@ export interface IExtensionLogger {
|
|
|
17
18
|
warning(message: unknown): void;
|
|
18
19
|
error(message: unknown): void;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* What an extension needs from the rest of the core. The same shape for every family: the `providers`
|
|
23
|
+
* list a channel or a provider already declares, now available to all eleven.
|
|
24
|
+
*/
|
|
25
|
+
export interface IExtensionRequirements {
|
|
26
|
+
/** Ids of the providers this extension consumes (never a pluvider 'plugin:<name>' id). */
|
|
27
|
+
providers?: string[];
|
|
28
|
+
}
|
|
20
29
|
export interface IExtension {
|
|
30
|
+
/**
|
|
31
|
+
* The providers this extension CONSUMES. The core instantiates them even if no channel asks for
|
|
32
|
+
* them, the same way it does for a channel's or a provider's requirements. The dependency stays
|
|
33
|
+
* SOFT: one that is not installed is a warning, and the consumer must survive its absence.
|
|
34
|
+
*
|
|
35
|
+
* OPTIONAL: an extension that consumes nothing leaves it out, and an older core ignores it.
|
|
36
|
+
*/
|
|
37
|
+
requirements?: IExtensionRequirements;
|
|
38
|
+
/**
|
|
39
|
+
* Called by the core once EVERY provider is registered and started, with an access already bound
|
|
40
|
+
* to this extension's identity. This is where an extension subscribes to the providers it consumes —
|
|
41
|
+
* never from its own start hook, because whether a producer exists at that point depends on the
|
|
42
|
+
* startup order, which the author cannot see.
|
|
43
|
+
*
|
|
44
|
+
* Any family may implement it: a sender that emails through SES reaching the cloud accounts, a
|
|
45
|
+
* homepage showing the state of an account, an IdP reading Cognito or B2C. Whoever subscribes MUST
|
|
46
|
+
* unsubscribe in its own stop hook, or the producer goes on handing events to a dead instance.
|
|
47
|
+
*
|
|
48
|
+
* OPTIONAL: an extension that consumes nothing leaves it out, and an older core never calls it.
|
|
49
|
+
*/
|
|
50
|
+
onProvidersReady?(access: IProviderAccess): void | Promise<void>;
|
|
21
51
|
exportConfig?(options: IExtensionExportOptions): Promise<unknown>;
|
|
22
52
|
importConfig?(config: unknown): Promise<IExtensionImportResult>;
|
|
23
53
|
}
|
package/dist/IFederation.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ export interface IClusterEndpoint {
|
|
|
6
6
|
id?: string;
|
|
7
7
|
}
|
|
8
8
|
export declare enum ERemoteConnState {
|
|
9
|
-
CONNECTED = "connected",// socket
|
|
10
|
-
HANDSHAKING = "handshaking",// socket
|
|
11
|
-
RECONNECTING = "reconnecting",//
|
|
9
|
+
CONNECTED = "connected",// socket open AND a valid instance captured → operational
|
|
10
|
+
HANDSHAKING = "handshaking",// socket open but with NO instance yet (remote channel starting / re-handshake)
|
|
11
|
+
RECONNECTING = "reconnecting",// no socket, retrying with backoff
|
|
12
12
|
DOWN = "down"
|
|
13
13
|
}
|
|
14
14
|
export interface IRemoteChannelHandlers {
|
package/dist/IFederation.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ERemoteConnState = void 0;
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// State of a remote connection managed by the core. Never string literals.
|
|
5
|
+
// Progression as it comes up: DOWN → RECONNECTING (looking for a socket) → HANDSHAKING (socket open,
|
|
6
|
+
// asking the remote channel for an instance) → CONNECTED (socket + instance = operational). CONNECTED is
|
|
7
|
+
// the ONLY state in which a command actually reaches the remote channel.
|
|
8
8
|
var ERemoteConnState;
|
|
9
9
|
(function (ERemoteConnState) {
|
|
10
10
|
ERemoteConnState["CONNECTED"] = "connected";
|
|
11
11
|
ERemoteConnState["HANDSHAKING"] = "handshaking";
|
|
12
12
|
ERemoteConnState["RECONNECTING"] = "reconnecting";
|
|
13
|
-
ERemoteConnState["DOWN"] = "down"; //
|
|
13
|
+
ERemoteConnState["DOWN"] = "down"; // connection closed / never established (terminal or no credentials)
|
|
14
14
|
})(ERemoteConnState || (exports.ERemoteConnState = ERemoteConnState = {}));
|
package/dist/IIdpConnector.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare enum EIdpConnectorKind {
|
|
|
4
4
|
OIDC = "oidc",
|
|
5
5
|
OAUTH2 = "oauth2"
|
|
6
6
|
}
|
|
7
|
-
/** @deprecated
|
|
7
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
8
8
|
export type IdpFieldType = TConfigFieldType;
|
|
9
9
|
export type IIdpConfigFieldDef = IConfigFieldDef;
|
|
10
10
|
export interface IIdpIdentity {
|
package/dist/IIdpConnector.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
|
|
3
|
+
The Identity Provider (IdP) connector interface for Kwirth.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
A connector is PURE LOGIC (with no routes of its own): it builds the IdP's authorization URL and
|
|
6
|
+
processes the callback, returning the verified identity. The pre-login HTTP flow and the issuing of
|
|
7
|
+
the AccessKey live in Kwirth's core, never in the connector.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
It lives in common-back so that connectors packaged separately (idps/<id>/) can implement it by
|
|
10
|
+
importing '@kwirthmagnify/kwirth-common-back', just like ISender/IProvider.
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.EIdpConnectorKind = void 0;
|
package/dist/ILogin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TConfigFieldType, IConfigFieldDef } from '@kwirthmagnify/kwirth-common';
|
|
2
|
-
/** @deprecated
|
|
2
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
3
3
|
export type LoginFieldType = TConfigFieldType;
|
|
4
|
-
/**
|
|
4
|
+
/** A login's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
|
|
5
5
|
export type ILoginFieldDef = IConfigFieldDef;
|
package/dist/IPluvider.d.ts
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
import { IProviderSubscriber, IProviderSubscriptionHelp } from './IProvider';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* What the extension manager and provider-debug show about a pluvider. The consumer of a pluvider is
|
|
4
|
+
* ANOTHER TEAM, so there has to be something to show without reading the code.
|
|
5
5
|
*/
|
|
6
6
|
export interface IPluviderData {
|
|
7
|
-
/**
|
|
7
|
+
/** What it produces, in one line. */
|
|
8
8
|
description: string;
|
|
9
|
-
/**
|
|
9
|
+
/** Name of the type of event it emits (e.g. 'IAgoraAlert'), to orient the consumer. */
|
|
10
10
|
eventTypeName?: string;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* plugins
|
|
15
|
-
*
|
|
13
|
+
* A plugin that ALSO produces: it exposes in-process the information it already generates, so other
|
|
14
|
+
* plugins can subscribe to it. It is not a second extension packaged inside the plugin: it is
|
|
15
|
+
* implemented by the channel's SAME class, on the same instance and the same data.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* 'apiKeyApi'
|
|
20
|
-
* 'requiresApiKeyApi'
|
|
17
|
+
* It does NOT extend IProvider, on purpose. A pluvider does not go through the provider machinery — it
|
|
18
|
+
* is not put into 'clusterInfo.providers', which is what the loops that mount routers, write
|
|
19
|
+
* 'apiKeyApi' or set 'started' walk over — so it has no 'id', 'router', 'routerAlias',
|
|
20
|
+
* 'providesRouter', 'requiresApiKeyApi' or 'apiKeyApi'.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* Nor does the author write the id: the core composes it as '<PLUVIDER_ID_PREFIX><channelId>', so
|
|
23
|
+
* nobody gets the prefix wrong.
|
|
24
24
|
*
|
|
25
|
-
* 'TSub'
|
|
26
|
-
*
|
|
25
|
+
* 'TSub' is the shape of the subscription filter. As with providers, each pluvider decides whether it
|
|
26
|
+
* filters and with what shape; if it does not filter, the generic is left unspecified.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
28
|
+
* Example:
|
|
29
29
|
*
|
|
30
30
|
* class AgoraChannel implements IChannel, IPluvider<IAgoraAlertSubscription> { … }
|
|
31
31
|
*/
|
|
32
32
|
export interface IPluvider<TSub = unknown> {
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* The pluvider's metadata. Its PRESENCE is the declaration: a channel implementing this method
|
|
35
|
+
* offers itself as a producer, and the core registers it. No flags, no duck-typing detection.
|
|
36
36
|
*/
|
|
37
37
|
getPluviderData(): IPluviderData;
|
|
38
38
|
addSubscriber(c: IProviderSubscriber, data: TSub): Promise<void>;
|
|
39
39
|
removeSubscriber(c: IProviderSubscriber): Promise<void>;
|
|
40
40
|
updateSubscription?(c: IProviderSubscriber, data: TSub): Promise<void>;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Starts production. The core calls it in the pluviders phase, that is BEFORE 'startChannel()':
|
|
43
|
+
* the background work lives on this side and the front end hooks in afterwards.
|
|
44
44
|
*/
|
|
45
45
|
startProvider(): Promise<void>;
|
|
46
46
|
stopProvider(): Promise<void>;
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Mandatory, unlike its namesake on IProvider, which is optional. A provider is usually consumed
|
|
49
|
+
* by whoever wrote it; a pluvider is consumed by outsiders, and without this they have no way of
|
|
50
|
+
* knowing what to write in the subscription or what they are going to receive.
|
|
51
51
|
*/
|
|
52
52
|
getSubscriptionHelp(): IProviderSubscriptionHelp;
|
|
53
53
|
}
|
package/dist/IProvider.d.ts
CHANGED
|
@@ -45,9 +45,9 @@ export interface IProviderHandle {
|
|
|
45
45
|
unsubscribe(subscriber: IProviderSubscriber): unknown;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Persistence the core injects into the provider (the same mechanism channels receive).
|
|
49
|
+
* The 'secret' boolean decides the destination: true -> Kubernetes Secret, false -> ConfigMap.
|
|
50
|
+
* The 'Common' variants write to the store shared between extensions.
|
|
51
51
|
*/
|
|
52
52
|
export interface IProviderStorage {
|
|
53
53
|
writeStorage(id: string, secret: boolean, data: any): Promise<void>;
|
|
@@ -56,9 +56,9 @@ export interface IProviderStorage {
|
|
|
56
56
|
readStorageCommon(id: string, secret: boolean): Promise<any>;
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* A field of the subscription payload, described so a consumer can draw a form instead of demanding
|
|
60
|
+
* hand-written JSON. Declaring them only makes sense when the payload is flat; when it is nested
|
|
61
|
+
* (otel, for instance, with 'spaces'), 'usage' and 'example' are enough.
|
|
62
62
|
*/
|
|
63
63
|
export interface IProviderSubscriptionField {
|
|
64
64
|
name: string;
|
|
@@ -67,60 +67,81 @@ export interface IProviderSubscriptionField {
|
|
|
67
67
|
description: string;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
* addSubscriber.
|
|
72
|
-
*
|
|
70
|
+
* Help a provider publishes about HOW TO SUBSCRIBE to it, that is, about the 'data' argument of
|
|
71
|
+
* addSubscriber. Not to be confused with the 'schema' a provider exports from its back.js, which
|
|
72
|
+
* describes the provider's own configuration (configure/configRouter).
|
|
73
73
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* provider-debug consumes it to explain to the user what to write, but any channel that offers a
|
|
75
|
+
* choice of provider can use it.
|
|
76
76
|
*/
|
|
77
77
|
export interface IProviderSubscriptionHelp {
|
|
78
|
-
/**
|
|
78
|
+
/** How it is used, in prose: what it delivers, what it takes to receive anything, gotchas. */
|
|
79
79
|
usage: string;
|
|
80
|
-
/**
|
|
80
|
+
/** Example payload, ready to pass to addSubscriber as it is. */
|
|
81
81
|
example: Record<string, unknown>;
|
|
82
|
-
/**
|
|
82
|
+
/** Field-by-field description. Optional: only for flat payloads. */
|
|
83
83
|
fields?: IProviderSubscriptionField[];
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
87
|
-
* IConfigFieldDef,
|
|
86
|
+
* A field of the provider's OWN configuration (not of the subscription). It is the common contract
|
|
87
|
+
* IConfigFieldDef, the same one senders, webhooks, idps and logins use.
|
|
88
88
|
*/
|
|
89
89
|
export type IProviderFieldDef = IConfigFieldDef;
|
|
90
90
|
/**
|
|
91
|
-
*
|
|
91
|
+
* What a provider needs from the rest of the core. Same shape as the 'providers' list of a channel's
|
|
92
|
+
* requirements, so both read alike; see IProvider.requirements.
|
|
93
|
+
*/
|
|
94
|
+
export interface IProviderRequirements {
|
|
95
|
+
/** Ids of the providers this one consumes (never a pluvider 'plugin:<name>' id). */
|
|
96
|
+
providers: string[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* What the core hands ANY extension that consumes providers, in its onProvidersReady(). It is already
|
|
100
|
+
* bound to the consumer's identity — the core stamps the instance and builds this for it — so the
|
|
101
|
+
* extension names nobody: it asks for a producer by id and gets a handle, or undefined if it is not
|
|
102
|
+
* installed (a SOFT dependency, to be survived).
|
|
103
|
+
*
|
|
104
|
+
* It is the same door for every family: a sender that emails through SES, a webhook, a homepage that
|
|
105
|
+
* shows the state of a cloud account, an IdP that reads Cognito or B2C — all of them reach a provider
|
|
106
|
+
* through this and nothing else, so the core's registry of who consumes what stays true.
|
|
107
|
+
*/
|
|
108
|
+
export interface IProviderAccess {
|
|
109
|
+
getProvider(providerId: string): IProviderHandle | undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* What a provider can tell about itself.
|
|
92
113
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
114
|
+
* It exists so kwirth can say whether something is being consumed or emitting to nobody, which is one
|
|
115
|
+
* of the few questions NOBODY can answer from the outside: each provider keeps its subscribers in its
|
|
116
|
+
* own structure, and until now there was no way to ask it.
|
|
96
117
|
*
|
|
97
|
-
* ⚠️
|
|
98
|
-
*
|
|
99
|
-
*
|
|
118
|
+
* ⚠️ Only the NUMBER, not who they are: 'IProviderSubscriber' is a single-method interface and carries
|
|
119
|
+
* no identity, so a provider has nothing to identify them with. Drawing the graph of who consumes whom
|
|
120
|
+
* will require widening that contract, and that is a separate decision.
|
|
100
121
|
*/
|
|
101
122
|
export interface IProviderStats {
|
|
102
|
-
/**
|
|
123
|
+
/** How many subscribers it has RIGHT NOW. Zero means it is emitting to nobody. */
|
|
103
124
|
subscribers: number;
|
|
104
125
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* "no informa", igual que el resto.
|
|
126
|
+
* DELIVERIES made since the provider started: one per call to processProviderEvent, not one per
|
|
127
|
+
* event produced. OPTIONAL: whoever does not keep it is shown as "not reported", like the rest.
|
|
108
128
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
129
|
+
* Deliveries are counted instead of events on purpose. A provider that produces a thousand events
|
|
130
|
+
* and filters them all out is moving nothing, and the number that is useful to whoever operates is
|
|
131
|
+
* the work that ACTUALLY HAPPENS. Besides, the place to increment is unambiguous — right where the
|
|
132
|
+
* subscriber is already called — and that means wiring it in sixteen providers does not depend on
|
|
133
|
+
* interpreting each one's code.
|
|
113
134
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
135
|
+
* It is a RUNNING TOTAL, not a rate: whoever reads it subtracts two readings and divides by time.
|
|
136
|
+
* The provider must know nothing about windows or averages — that would force keeping history in
|
|
137
|
+
* the hot path, which is exactly what must not happen.
|
|
117
138
|
*
|
|
118
|
-
* ⚠️
|
|
119
|
-
*
|
|
120
|
-
*
|
|
139
|
+
* ⚠️ The increment goes RIGHT NEXT to the subscriber call, and it is an integer. No per-event
|
|
140
|
+
* timestamps, no growing arrays, no new objects: what hurts in Node is not the counter, it is the
|
|
141
|
+
* garbage it generates.
|
|
121
142
|
*/
|
|
122
143
|
events?: number;
|
|
123
|
-
/**
|
|
144
|
+
/** Delivery errors, under the same criterion: a running total, and cheap. */
|
|
124
145
|
errors?: number;
|
|
125
146
|
}
|
|
126
147
|
/**
|
|
@@ -135,41 +156,40 @@ export interface IProvider extends IExtension {
|
|
|
135
156
|
removeSubscriber(c: IProviderSubscriber): Promise<void>;
|
|
136
157
|
updateSubscription?(c: IProviderSubscriber, data: any): Promise<void>;
|
|
137
158
|
/**
|
|
138
|
-
* @deprecated
|
|
139
|
-
*
|
|
140
|
-
*
|
|
159
|
+
* @deprecated The core no longer feeds this method: a provider owns its own configuration and
|
|
160
|
+
* serves it through 'configRouter'. It is kept for compatibility with third-party providers that
|
|
161
|
+
* still use the core-managed config.
|
|
141
162
|
*/
|
|
142
163
|
configure?(config: Record<string, unknown>): void;
|
|
143
164
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* payload escribir.
|
|
165
|
+
* Subscription help. OPTIONAL: whoever writes a provider adds it if they want to. Without it the
|
|
166
|
+
* consumer keeps working, it simply has nothing to show the user about which payload to write.
|
|
147
167
|
*/
|
|
148
168
|
getSubscriptionHelp?(): IProviderSubscriptionHelp;
|
|
149
169
|
/**
|
|
150
|
-
*
|
|
151
|
-
* ISender.getConfigNames).
|
|
152
|
-
*
|
|
153
|
-
*
|
|
170
|
+
* Names of the configurations the provider has defined (the equivalent of
|
|
171
|
+
* ISender.getConfigNames). OPTIONAL: it only makes sense on a provider that owns its
|
|
172
|
+
* configuration. The extension manager uses it to show how many there are on the card, just as it
|
|
173
|
+
* does with senders. It exposes no values, only names.
|
|
154
174
|
*/
|
|
155
175
|
getConfigNames?(): string[];
|
|
156
176
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
177
|
+
* Configuration schema of the provider itself, which kwirth uses to draw a generic form.
|
|
178
|
+
* This is the STANDARD way to declare it, the same as ISender.getConfigSchema and IWebhook.
|
|
159
179
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
180
|
+
* A provider nobody subscribes to and that exposes no router is NEVER instantiated, so in that
|
|
181
|
+
* case there is nobody to ask: for those, also export a 'schema' constant with the same array from
|
|
182
|
+
* the back.js, which the core reads at install time without instantiating anything.
|
|
163
183
|
*/
|
|
164
184
|
getConfigSchema?(): IProviderFieldDef[];
|
|
165
185
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
186
|
+
* What the provider knows about itself right now. OPTIONAL, like the rest of this block: whoever
|
|
187
|
+
* does not implement it is shown as "not reported", which is different from zero — a zero would be
|
|
188
|
+
* a claim nobody can back up.
|
|
169
189
|
*
|
|
170
|
-
* ⚠️
|
|
171
|
-
*
|
|
172
|
-
*
|
|
190
|
+
* ⚠️ It has to be CHEAP: return what you already have, do not compute it. It is called when
|
|
191
|
+
* somebody opens a status screen, but a provider does not know how often, and walking structures
|
|
192
|
+
* here turns a query into work for everyone.
|
|
173
193
|
*/
|
|
174
194
|
getStats?(): IProviderStats;
|
|
175
195
|
/**
|
|
@@ -202,27 +222,41 @@ export interface IProvider extends IExtension {
|
|
|
202
222
|
* and an older core that does not know about it simply never calls anyone.
|
|
203
223
|
*/
|
|
204
224
|
onProvidersReady?(): void | Promise<void>;
|
|
225
|
+
/**
|
|
226
|
+
* The providers this provider CONSUMES. The core instantiates them even if no channel asks for
|
|
227
|
+
* them, the same way it instantiates the ones a channel lists in its own requirements.
|
|
228
|
+
*
|
|
229
|
+
* Without it, a producer nobody else asks for and that exposes no router is never instantiated,
|
|
230
|
+
* and getProvider() hands the consumer 'undefined' in onProvidersReady(). The dependency stays
|
|
231
|
+
* SOFT: one that is not installed is a warning, and the consumer must survive its absence.
|
|
232
|
+
*
|
|
233
|
+
* Pluvider ids ('plugin:<name>') are not listed here: a pluvider exists when its plugin is
|
|
234
|
+
* installed, the core cannot create it.
|
|
235
|
+
*
|
|
236
|
+
* OPTIONAL: a provider that consumes nothing leaves it out, and an older core ignores it.
|
|
237
|
+
*/
|
|
238
|
+
requirements?: IProviderRequirements;
|
|
205
239
|
startProvider(): Promise<void>;
|
|
206
240
|
stopProvider(): Promise<void>;
|
|
207
241
|
router: any;
|
|
208
242
|
routerAlias: string | undefined;
|
|
209
243
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
244
|
+
* The provider wants the body of the requests to its public router RAW (a Buffer), untouched by
|
|
245
|
+
* the core's global bodyParser.
|
|
212
246
|
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* webhooks
|
|
247
|
+
* Needed for anything that is not plain JSON: ndjson, msgpack, protobuf, or verifying a signature
|
|
248
|
+
* over the exact bytes that arrived. Without this, an extension that INGESTS receives the body
|
|
249
|
+
* already parsed — and with the global parser's limit — which is exactly what the core solved for
|
|
250
|
+
* webhooks by mounting them in front.
|
|
217
251
|
*
|
|
218
|
-
*
|
|
252
|
+
* It defaults to false: providers that read 'req.body' as an object today are unaffected.
|
|
219
253
|
*/
|
|
220
254
|
readonly rawBody?: boolean;
|
|
221
255
|
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* '/core/providerconfig/<providerId>'.
|
|
225
|
-
*
|
|
256
|
+
* The provider's management router (its own configuration). The core ALWAYS mounts it behind
|
|
257
|
+
* accessKey validation, just as it does with a channel's endpoints, at the route
|
|
258
|
+
* '/core/providerconfig/<providerId>'. It is a separate path from 'router', which is public and may
|
|
259
|
+
* receive external traffic (OTLP, third-party POSTs) and therefore cannot demand an accessKey.
|
|
226
260
|
*/
|
|
227
261
|
configRouter?: any;
|
|
228
262
|
apiKeyApi: any | undefined;
|
package/dist/ISender.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ISenderMessage, ISenderConfig, ISenderAccess, ISenderStoredConfig, ISenderResult, TConfigFieldType, IConfigFieldDef, IExtensionNodeMeta } from '@kwirthmagnify/kwirth-common';
|
|
2
2
|
import { IExtension, IExtensionLogger } from './IExtension';
|
|
3
3
|
export { ISenderMessage, ISenderConfig, ISenderAccess, ISenderStoredConfig, ISenderResult };
|
|
4
|
-
/** @deprecated
|
|
4
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
5
5
|
export type SenderFieldType = TConfigFieldType;
|
|
6
|
-
/**
|
|
6
|
+
/** A sender's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
|
|
7
7
|
export type ISenderFieldDef = IConfigFieldDef;
|
|
8
|
-
/** @deprecated
|
|
8
|
+
/** @deprecated use IExtensionNodeMeta, common to every extension. */
|
|
9
9
|
export type ISenderNodeMeta = IExtensionNodeMeta;
|
|
10
10
|
export interface ISender extends IExtension {
|
|
11
11
|
readonly id: string;
|
package/dist/IWebhook.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { IWebhookEvent, IWebhookConfig, IWebhookAccess, IWebhookConsumer, IWebhookStoredConfig, TConfigFieldType, IConfigFieldDef, IExtensionNodeMeta } from '@kwirthmagnify/kwirth-common';
|
|
2
2
|
import { IExtension } from './IExtension';
|
|
3
3
|
export { IWebhookEvent, IWebhookConfig, IWebhookAccess, IWebhookConsumer, IWebhookStoredConfig };
|
|
4
|
-
/** @deprecated
|
|
4
|
+
/** @deprecated use TConfigFieldType, common to every extension. */
|
|
5
5
|
export type WebhookFieldType = TConfigFieldType;
|
|
6
|
-
/**
|
|
6
|
+
/** A webhook's configuration field. It is the common contract IConfigFieldDef, with nothing of its own. */
|
|
7
7
|
export type IWebhookFieldDef = IConfigFieldDef;
|
|
8
|
-
/** @deprecated
|
|
8
|
+
/** @deprecated use IExtensionNodeMeta, common to every extension. */
|
|
9
9
|
export type IWebhookNodeMeta = IExtensionNodeMeta;
|
|
10
10
|
export interface IWebhook extends IExtension {
|
|
11
11
|
readonly id: string;
|
|
@@ -7,9 +7,9 @@ export interface ICrdInformerHandlers {
|
|
|
7
7
|
onError?: (err: any) => void;
|
|
8
8
|
}
|
|
9
9
|
export declare function createCrdInformer(clusterInfo: any, apiGroup: string, apiVersion: string, plural: string, handlers: ICrdInformerHandlers): any;
|
|
10
|
-
/**
|
|
11
|
-
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses')
|
|
12
|
-
*
|
|
10
|
+
/** Generic informer for ANY resource (core or group): the caller supplies the `watchPath` (e.g.
|
|
11
|
+
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') and the `listFn` that returns {items}.
|
|
12
|
+
* Reused by createCrdInformer and by consumers of core resources (exposure, for instance). */
|
|
13
13
|
export declare function createInformer(clusterInfo: any, watchPath: string, listFn: () => Promise<{
|
|
14
14
|
items: any[];
|
|
15
15
|
}>, handlers: ICrdInformerHandlers): any;
|
package/dist/KubernetesTools.js
CHANGED
|
@@ -127,9 +127,9 @@ function createCrdInformer(clusterInfo, apiGroup, apiVersion, plural, handlers)
|
|
|
127
127
|
.then((res) => res);
|
|
128
128
|
return createInformer(clusterInfo, path, listFunction, handlers);
|
|
129
129
|
}
|
|
130
|
-
/**
|
|
131
|
-
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses')
|
|
132
|
-
*
|
|
130
|
+
/** Generic informer for ANY resource (core or group): the caller supplies the `watchPath` (e.g.
|
|
131
|
+
* '/api/v1/services', '/apis/networking.k8s.io/v1/ingresses') and the `listFn` that returns {items}.
|
|
132
|
+
* Reused by createCrdInformer and by consumers of core resources (exposure, for instance). */
|
|
133
133
|
function createInformer(clusterInfo, watchPath, listFn, handlers) {
|
|
134
134
|
const informer = k8s.makeInformer(clusterInfo.kubeConfig, watchPath, listFn);
|
|
135
135
|
if (handlers.onAdd)
|
package/dist/github.js
CHANGED
|
@@ -16,9 +16,9 @@ async function ghGet(base, resource, accessToken) {
|
|
|
16
16
|
async function githubIdentityFromToken(apiBaseUrl, accessToken) {
|
|
17
17
|
const base = apiBaseUrl.replace(/\/+$/, '');
|
|
18
18
|
const user = await ghGet(base, '/user', accessToken);
|
|
19
|
-
// /user/emails
|
|
19
|
+
// /user/emails can fail when the user:email scope is missing; in that case we fall back to the public email
|
|
20
20
|
const emails = await ghGet(base, '/user/emails', accessToken).catch(() => []);
|
|
21
|
-
//
|
|
21
|
+
// we prefer the primary email; failing that, the first verified one; failing that, the first there is
|
|
22
22
|
const chosen = emails.find(e => e.primary) ?? emails.find(e => e.verified) ?? emails[0];
|
|
23
23
|
return {
|
|
24
24
|
email: chosen?.email ?? user.email ?? '',
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __exportStar(require("./ISender"), exports);
|
|
|
24
24
|
__exportStar(require("./IWebhook"), exports);
|
|
25
25
|
__exportStar(require("./IIdpConnector"), exports);
|
|
26
26
|
__exportStar(require("./IExtension"), exports);
|
|
27
|
+
__exportStar(require("./Consumer"), exports);
|
|
27
28
|
__exportStar(require("./oidc"), exports);
|
|
28
29
|
__exportStar(require("./oauth2"), exports);
|
|
29
30
|
__exportStar(require("./github"), exports);
|
package/dist/oauth2.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.oauth2ConfigSchema = oauth2ConfigSchema;
|
|
4
4
|
exports.oauth2BuildAuthorizationUrl = oauth2BuildAuthorizationUrl;
|
|
5
5
|
exports.oauth2HandleCallback = oauth2HandleCallback;
|
|
6
|
-
//
|
|
6
|
+
// base schema of an OAuth2 IdP (the connector adds its URLs where applicable; clientSecret 'password' → masked)
|
|
7
7
|
function oauth2ConfigSchema() {
|
|
8
8
|
return [
|
|
9
9
|
{ name: 'clientId', label: 'Client ID', type: 'text', required: true },
|
|
@@ -26,7 +26,7 @@ function oauth2BuildAuthorizationUrl(config, ctx, ep) {
|
|
|
26
26
|
}
|
|
27
27
|
return url.toString();
|
|
28
28
|
}
|
|
29
|
-
//
|
|
29
|
+
// exchanges the 'code' for an access_token (back-channel) and delegates userinfo to fetchIdentity(accessToken).
|
|
30
30
|
async function oauth2HandleCallback(config, ctx, ep, fetchIdentity) {
|
|
31
31
|
const body = new URLSearchParams();
|
|
32
32
|
body.set('grant_type', 'authorization_code');
|
|
@@ -36,7 +36,7 @@ async function oauth2HandleCallback(config, ctx, ep, fetchIdentity) {
|
|
|
36
36
|
body.set('redirect_uri', ctx.redirectUri);
|
|
37
37
|
if (ep.usePkce)
|
|
38
38
|
body.set('code_verifier', ctx.codeVerifier);
|
|
39
|
-
// Accept: application/json →
|
|
39
|
+
// Accept: application/json → some IdPs (GitHub) return form-urlencoded without this header
|
|
40
40
|
const res = await fetch(ep.tokenEndpoint, {
|
|
41
41
|
method: 'POST',
|
|
42
42
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },
|
package/dist/oidc.js
CHANGED
|
@@ -40,7 +40,7 @@ exports.oidcBuildAuthorizationUrl = oidcBuildAuthorizationUrl;
|
|
|
40
40
|
exports.oidcHandleCallback = oidcHandleCallback;
|
|
41
41
|
const openid_client_1 = require("openid-client");
|
|
42
42
|
const jose = __importStar(require("jose"));
|
|
43
|
-
//
|
|
43
|
+
// standard config schema of an OIDC IdP (clientSecret is 'password' → masked in the UI)
|
|
44
44
|
function oidcConfigSchema() {
|
|
45
45
|
return [
|
|
46
46
|
{ name: 'clientId', label: 'Client ID', type: 'text', required: true },
|
|
@@ -62,7 +62,7 @@ async function makeClient(config, redirectUri, defaultIssuer) {
|
|
|
62
62
|
});
|
|
63
63
|
return { issuer, client };
|
|
64
64
|
}
|
|
65
|
-
//
|
|
65
|
+
// maps the id_token claims to Kwirth's identity (email fallback + assumed verified)
|
|
66
66
|
function mapOidcIdentity(claims, opts) {
|
|
67
67
|
const emailClaims = opts?.emailClaims ?? ['email'];
|
|
68
68
|
let email = '';
|
|
@@ -80,7 +80,7 @@ function mapOidcIdentity(claims, opts) {
|
|
|
80
80
|
sub: claims.sub !== undefined ? String(claims.sub) : undefined
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
//
|
|
83
|
+
// is the tenant (tid) allowed? (an empty allowlist means any tenant)
|
|
84
84
|
function tenantAllowed(tid, allowed) {
|
|
85
85
|
if (!allowed || allowed.length === 0)
|
|
86
86
|
return true;
|
|
@@ -103,9 +103,10 @@ async function oidcBuildAuthorizationUrl(config, ctx, defaultIssuer) {
|
|
|
103
103
|
async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
|
|
104
104
|
const { issuer, client } = await makeClient(config, ctx.redirectUri, defaultIssuer);
|
|
105
105
|
if (opts?.multiTenant) {
|
|
106
|
-
// openid-client
|
|
107
|
-
//
|
|
108
|
-
//
|
|
106
|
+
// openid-client validates iss LITERALLY, and in multi-tenant the discovered issuer carries the
|
|
107
|
+
// {tenantid} placeholder; so we do the raw exchange (grant, without validating the id_token) and
|
|
108
|
+
// validate the id_token by hand with jose: signature (the issuer's JWKS) + iss against the
|
|
109
|
+
// token's concrete tid.
|
|
109
110
|
const tokenSet = await client.grant({
|
|
110
111
|
grant_type: 'authorization_code',
|
|
111
112
|
code: ctx.code,
|
|
@@ -114,7 +115,7 @@ async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
|
|
|
114
115
|
});
|
|
115
116
|
if (!tokenSet.id_token)
|
|
116
117
|
throw new Error('OIDC multi-tenant: token response has no id_token');
|
|
117
|
-
const tid = tokenSet.claims().tid; //
|
|
118
|
+
const tid = tokenSet.claims().tid; // unverified; only to learn the tenant
|
|
118
119
|
if (!tid)
|
|
119
120
|
throw new Error('OIDC multi-tenant: id_token has no tid claim');
|
|
120
121
|
if (!tenantAllowed(tid, opts.allowedTenants))
|
|
@@ -130,8 +131,8 @@ async function oidcHandleCallback(config, ctx, defaultIssuer, opts) {
|
|
|
130
131
|
});
|
|
131
132
|
return mapOidcIdentity(payload, opts);
|
|
132
133
|
}
|
|
133
|
-
// single-tenant:
|
|
134
|
-
//
|
|
134
|
+
// single-tenant: we pass the callback's raw params (they include iss for RFC 9207, and state); the
|
|
135
|
+
// core already validates state, but openid-client demands checks.state when the param is present.
|
|
135
136
|
const params = ctx.params ?? { code: ctx.code };
|
|
136
137
|
const checks = { code_verifier: ctx.codeVerifier };
|
|
137
138
|
if (params.state !== undefined)
|