@luti/angular 0.1.0
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/README.md +103 -0
- package/esm2022/luti-angular.mjs +5 -0
- package/esm2022/luti-config.mjs +6 -0
- package/esm2022/luti-launcher.component.mjs +261 -0
- package/esm2022/luti.service.mjs +32 -0
- package/esm2022/protocol.mjs +47 -0
- package/esm2022/public-api.mjs +5 -0
- package/fesm2022/luti-angular.mjs +346 -0
- package/fesm2022/luti-angular.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/luti-config.d.ts +22 -0
- package/luti-launcher.component.d.ts +36 -0
- package/luti.service.d.ts +13 -0
- package/package.json +31 -0
- package/protocol.d.ts +31 -0
- package/public-api.d.ts +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# `@luti/angular`
|
|
2
|
+
|
|
3
|
+
Angular bindings for Embedded Luti. The package renders a native Angular header launcher and hosts the clinical interface in Luti's isolated embedded surface.
|
|
4
|
+
|
|
5
|
+
Supports Angular 18, 19, 20, 21, and 22. The published package uses Angular's partial-Ivy format and is compiled with the oldest supported major for forward compatibility.
|
|
6
|
+
|
|
7
|
+
Maintainers can run `pnpm test:compatibility` to pack the library and build it in pinned Angular 18–22 consumer applications. The command provisions Node.js 22.22.3, the version shared by all five test environments.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @luti/angular
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Configure the Angular app
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { provideLuti } from "@luti/angular";
|
|
19
|
+
|
|
20
|
+
export const appConfig = {
|
|
21
|
+
providers: [
|
|
22
|
+
provideLuti({
|
|
23
|
+
embedUrl: "https://app.luti.example/embed",
|
|
24
|
+
integrationId: "integration-id-from-luti",
|
|
25
|
+
async sessionProvider({ bootstrapId, integrationId, origin }) {
|
|
26
|
+
const response = await fetch("/api/luti/session", {
|
|
27
|
+
body: JSON.stringify({ bootstrapId, integrationId, origin }),
|
|
28
|
+
credentials: "include",
|
|
29
|
+
headers: { "Content-Type": "application/json" },
|
|
30
|
+
method: "POST",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!response.ok) throw new Error("Luti could not start");
|
|
34
|
+
return response.json();
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Import `LutiLauncherComponent` into the header component and render:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<luti-launcher
|
|
45
|
+
(authorizationRequired)="onAuthorizationRequired($event)"
|
|
46
|
+
(launchError)="onLutiError($event)"
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The host backend must use its Integration Credential to call Luti's authorization and bootstrap-authorization endpoints. Never put the credential in Angular configuration or browser code.
|
|
51
|
+
|
|
52
|
+
## Implement the host backend
|
|
53
|
+
|
|
54
|
+
Keep the Integration Credential in a server-side secret store. Your `POST /api/luti/session` handler should:
|
|
55
|
+
|
|
56
|
+
1. Authenticate the current external-app user and use a stable, opaque account ID as `externalSubject`.
|
|
57
|
+
2. Confirm that `origin` is one of your own expected browser origins. Do not trust the browser value by itself.
|
|
58
|
+
3. Forward the exact opaque `bootstrapId` to `POST {LUTI_URL}/api/integrations/embed/bootstrap/authorize` with the Integration Credential as a Bearer token. The Luti iframe registered this short-lived bootstrap directly; do not generate or cache one.
|
|
59
|
+
4. Return `{ status: "ready", expiresAt }` when Luti acknowledges the authorized bootstrap. No launch or User token passes through your backend.
|
|
60
|
+
5. If Luti returns `409 link_required`, call the authorization endpoint below and return `{ status: "authorization_required", authorizationUrl }`.
|
|
61
|
+
|
|
62
|
+
Bootstrap authorization request:
|
|
63
|
+
|
|
64
|
+
```http
|
|
65
|
+
POST /api/integrations/embed/bootstrap/authorize
|
|
66
|
+
Authorization: Bearer <server-only Integration Credential>
|
|
67
|
+
Content-Type: application/json
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
"bootstrapId":"78e336d5-d4c1-47b5-946a-bc92959f9836",
|
|
71
|
+
"externalSubject":"external-user-42"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Authorization request:
|
|
76
|
+
|
|
77
|
+
```http
|
|
78
|
+
POST /api/integrations/authorizations
|
|
79
|
+
Authorization: Bearer <server-only Integration Credential>
|
|
80
|
+
Content-Type: application/json
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
"externalSubject":"external-user-42",
|
|
84
|
+
"origin":"https://portal.example.com",
|
|
85
|
+
"callbackUrl":"https://portal.example.com/luti/callback",
|
|
86
|
+
"state":"single-use-CSRF-value-at-least-16-characters"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
At the registered callback, verify `state`, exchange the one-time `code` server-to-server at `POST /api/integrations/authorizations/exchange`, then render this small completion script from your own origin:
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<script>
|
|
94
|
+
window.opener?.postMessage({ type: "luti:host-authorized" }, window.location.origin);
|
|
95
|
+
window.close();
|
|
96
|
+
</script>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The launcher then retries `/api/luti/session`. Your backend authorizes the iframe-created bootstrap and receives only an acknowledgement; the iframe exchanges its bootstrap and private verifier directly with Luti. On external-app logout, call `POST /api/integrations/embed/revoke` from your backend for that external subject. This immediately ends its current Embedded Luti sessions.
|
|
100
|
+
|
|
101
|
+
## Trust boundary
|
|
102
|
+
|
|
103
|
+
The supported protocol never returns clinical content, an Embedded Session bearer, or a one-use exchange token to the external backend. The credential-holding backend is trusted to identify its own signed-in External Subjects and follow this protocol. A malicious credential holder can imitate public browser requests without browser/device attestation; that stronger threat is outside this web integration's boundary and is mitigated operationally with per-environment credentials, audit, rate limits, rotation, and revocation.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated bundle index. Do not edit.
|
|
3
|
+
*/
|
|
4
|
+
export * from './public-api';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibHV0aS1hbmd1bGFyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2x1dGktYW5ndWxhci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHZW5lcmF0ZWQgYnVuZGxlIGluZGV4LiBEbyBub3QgZWRpdC5cbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL3B1YmxpYy1hcGknO1xuIl19
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InjectionToken } from "@angular/core";
|
|
2
|
+
export const LUTI_CONFIG = new InjectionToken("LUTI_CONFIG");
|
|
3
|
+
export function provideLuti(config) {
|
|
4
|
+
return [{ provide: LUTI_CONFIG, useValue: config }];
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibHV0aS1jb25maWcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbHV0aS1jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGNBQWMsRUFBaUIsTUFBTSxlQUFlLENBQUM7QUF5QjlELE1BQU0sQ0FBQyxNQUFNLFdBQVcsR0FBRyxJQUFJLGNBQWMsQ0FBYSxhQUFhLENBQUMsQ0FBQztBQUV6RSxNQUFNLFVBQVUsV0FBVyxDQUFDLE1BQWtCO0lBQzVDLE9BQU8sQ0FBQyxFQUFFLE9BQU8sRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7QUFDdEQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGlvblRva2VuLCB0eXBlIFByb3ZpZGVyIH0gZnJvbSBcIkBhbmd1bGFyL2NvcmVcIjtcblxuZXhwb3J0IHR5cGUgTHV0aVJlYWR5U2Vzc2lvbiA9IHtcbiAgZXhwaXJlc0F0OiBEYXRlIHwgc3RyaW5nO1xuICBzdGF0dXM6IFwicmVhZHlcIjtcbn07XG5cbmV4cG9ydCB0eXBlIEx1dGlBdXRob3JpemF0aW9uUmVxdWlyZWQgPSB7XG4gIGF1dGhvcml6YXRpb25Vcmw6IHN0cmluZztcbiAgc3RhdHVzOiBcImF1dGhvcml6YXRpb25fcmVxdWlyZWRcIjtcbn07XG5cbmV4cG9ydCB0eXBlIEx1dGlMYXVuY2hSZXN1bHQgPSBMdXRpUmVhZHlTZXNzaW9uIHwgTHV0aUF1dGhvcml6YXRpb25SZXF1aXJlZDtcblxuZXhwb3J0IHR5cGUgTHV0aUNvbmZpZyA9IHtcbiAgZW1iZWRVcmw6IHN0cmluZztcbiAgaW50ZWdyYXRpb25JZDogc3RyaW5nO1xuICBzZXNzaW9uUHJvdmlkZXI6IChjb250ZXh0OiB7XG4gICAgYm9vdHN0cmFwSWQ6IHN0cmluZztcbiAgICBpbnRlZ3JhdGlvbklkOiBzdHJpbmc7XG4gICAgb3JpZ2luOiBzdHJpbmc7XG4gIH0pID0+IFByb21pc2U8THV0aUxhdW5jaFJlc3VsdD47XG4gIGVuZFNlc3Npb24/OiAoKSA9PiBQcm9taXNlPHZvaWQ+O1xufTtcblxuZXhwb3J0IGNvbnN0IExVVElfQ09ORklHID0gbmV3IEluamVjdGlvblRva2VuPEx1dGlDb25maWc+KFwiTFVUSV9DT05GSUdcIik7XG5cbmV4cG9ydCBmdW5jdGlvbiBwcm92aWRlTHV0aShjb25maWc6IEx1dGlDb25maWcpOiBQcm92aWRlcltdIHtcbiAgcmV0dXJuIFt7IHByb3ZpZGU6IExVVElfQ09ORklHLCB1c2VWYWx1ZTogY29uZmlnIH1dO1xufVxuIl19
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { CommonModule } from "@angular/common";
|
|
2
|
+
import { DomSanitizer } from "@angular/platform-browser";
|
|
3
|
+
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, ViewChild, inject, input, output, signal, } from "@angular/core";
|
|
4
|
+
import { LUTI_CONFIG } from "./luti-config";
|
|
5
|
+
import { LutiService } from "./luti.service";
|
|
6
|
+
import { createAuthorizedMessage, createInitMessage, isLutiCloseMessage, isLutiReadyMessage, isLutiUnreadMessage, resolveTrustedEmbedOrigin, } from "./protocol";
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
export class LutiLauncherComponent {
|
|
9
|
+
config = inject(LUTI_CONFIG);
|
|
10
|
+
luti = inject(LutiService);
|
|
11
|
+
host = inject((ElementRef));
|
|
12
|
+
sanitizer = inject(DomSanitizer);
|
|
13
|
+
label = input("Open Luti");
|
|
14
|
+
disabled = input(false);
|
|
15
|
+
opened = output();
|
|
16
|
+
closed = output();
|
|
17
|
+
launchError = output();
|
|
18
|
+
authorizationRequired = output();
|
|
19
|
+
loading = signal(false);
|
|
20
|
+
error = signal(null);
|
|
21
|
+
embedUrl = signal(null);
|
|
22
|
+
frame;
|
|
23
|
+
trustedEmbedOrigin = null;
|
|
24
|
+
authorizationWindow = null;
|
|
25
|
+
bootstrapId = null;
|
|
26
|
+
constructor() {
|
|
27
|
+
window.addEventListener("message", this.handleMessage);
|
|
28
|
+
}
|
|
29
|
+
ngOnDestroy() {
|
|
30
|
+
window.removeEventListener("message", this.handleMessage);
|
|
31
|
+
}
|
|
32
|
+
async toggle() {
|
|
33
|
+
if (this.luti.isOpen()) {
|
|
34
|
+
this.close();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.luti.open();
|
|
38
|
+
this.opened.emit();
|
|
39
|
+
this.loading.set(true);
|
|
40
|
+
this.error.set(null);
|
|
41
|
+
try {
|
|
42
|
+
this.trustedEmbedOrigin = resolveTrustedEmbedOrigin(this.config.embedUrl);
|
|
43
|
+
this.embedUrl.set(this.sanitizer.bypassSecurityTrustResourceUrl(this.config.embedUrl));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const normalized = error instanceof Error ? error : new Error("Unable to load Embedded Luti.");
|
|
47
|
+
this.loading.set(false);
|
|
48
|
+
this.error.set(normalized.message);
|
|
49
|
+
this.launchError.emit(normalized);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
close() {
|
|
53
|
+
this.luti.close();
|
|
54
|
+
this.closed.emit();
|
|
55
|
+
}
|
|
56
|
+
async loadSession() {
|
|
57
|
+
if (!this.bootstrapId) {
|
|
58
|
+
this.loading.set(false);
|
|
59
|
+
this.error.set("Embedded Luti is not ready. Close it and try again.");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.loading.set(true);
|
|
63
|
+
this.error.set(null);
|
|
64
|
+
try {
|
|
65
|
+
const result = await this.config.sessionProvider({
|
|
66
|
+
bootstrapId: this.bootstrapId,
|
|
67
|
+
integrationId: this.config.integrationId,
|
|
68
|
+
origin: window.location.origin,
|
|
69
|
+
});
|
|
70
|
+
if (result.status === "authorization_required") {
|
|
71
|
+
this.authorizationRequired.emit(result.authorizationUrl);
|
|
72
|
+
this.authorizationWindow = window.open(result.authorizationUrl, "luti-authorization", "popup,width=540,height=720");
|
|
73
|
+
if (!this.authorizationWindow) {
|
|
74
|
+
throw new Error("Allow the Luti authorization popup, then try again.");
|
|
75
|
+
}
|
|
76
|
+
this.error.set("Complete authorization in the new window, then return here.");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.postAuthorization();
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const normalized = error instanceof Error ? error : new Error("Unable to start Embedded Luti.");
|
|
83
|
+
this.error.set(normalized.message);
|
|
84
|
+
this.launchError.emit(normalized);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
this.loading.set(false);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
onEscape() {
|
|
91
|
+
if (this.luti.isOpen()) {
|
|
92
|
+
this.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
onDocumentPointerDown(event) {
|
|
96
|
+
if (this.luti.isOpen() && !this.host.nativeElement.contains(event.target)) {
|
|
97
|
+
this.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
initializeFrame() {
|
|
101
|
+
this.bootstrapId = null;
|
|
102
|
+
const target = this.frame?.nativeElement.contentWindow;
|
|
103
|
+
if (!target || !this.trustedEmbedOrigin)
|
|
104
|
+
return;
|
|
105
|
+
target.postMessage(createInitMessage(this.config.integrationId), {
|
|
106
|
+
targetOrigin: this.trustedEmbedOrigin,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
restart() {
|
|
110
|
+
this.loading.set(true);
|
|
111
|
+
this.error.set(null);
|
|
112
|
+
this.initializeFrame();
|
|
113
|
+
}
|
|
114
|
+
handleMessage = (event) => {
|
|
115
|
+
if (event.source !== this.frame?.nativeElement.contentWindow) {
|
|
116
|
+
if (event.source === this.authorizationWindow &&
|
|
117
|
+
event.origin === window.location.origin &&
|
|
118
|
+
typeof event.data === "object" &&
|
|
119
|
+
event.data !== null &&
|
|
120
|
+
"type" in event.data &&
|
|
121
|
+
event.data.type === "luti:host-authorized") {
|
|
122
|
+
this.authorizationWindow = null;
|
|
123
|
+
this.restart();
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (!this.trustedEmbedOrigin || event.origin !== this.trustedEmbedOrigin) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (isLutiReadyMessage(event.data)) {
|
|
131
|
+
this.bootstrapId = event.data.bootstrapId;
|
|
132
|
+
void this.loadSession();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (isLutiCloseMessage(event.data)) {
|
|
136
|
+
this.close();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (isLutiUnreadMessage(event.data)) {
|
|
140
|
+
this.luti.setUnreadCount(event.data.count);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
postAuthorization() {
|
|
144
|
+
const target = this.frame?.nativeElement.contentWindow;
|
|
145
|
+
if (!target || !this.trustedEmbedOrigin || !this.bootstrapId)
|
|
146
|
+
return;
|
|
147
|
+
target.postMessage(createAuthorizedMessage(this.bootstrapId), {
|
|
148
|
+
targetOrigin: this.trustedEmbedOrigin,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiLauncherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
152
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LutiLauncherComponent, isStandalone: true, selector: "luti-launcher", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed", launchError: "launchError", authorizationRequired: "authorizationRequired" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:pointerdown": "onDocumentPointerDown($event)" } }, viewQueries: [{ propertyName: "frame", first: true, predicate: ["frame"], descendants: true }], ngImport: i0, template: `
|
|
153
|
+
<button
|
|
154
|
+
class="luti-launcher__button"
|
|
155
|
+
type="button"
|
|
156
|
+
[attr.aria-expanded]="luti.isOpen()"
|
|
157
|
+
[attr.aria-label]="label()"
|
|
158
|
+
[disabled]="disabled()"
|
|
159
|
+
(click)="toggle()"
|
|
160
|
+
>
|
|
161
|
+
<span class="luti-launcher__mark" aria-hidden="true">L</span>
|
|
162
|
+
<span class="luti-launcher__label">Luti</span>
|
|
163
|
+
@if (luti.unreadCount() > 0) {
|
|
164
|
+
<span class="luti-launcher__badge" aria-label="Unread Luti activity">
|
|
165
|
+
{{ luti.unreadCount() > 99 ? '99+' : luti.unreadCount() }}
|
|
166
|
+
</span>
|
|
167
|
+
}
|
|
168
|
+
</button>
|
|
169
|
+
|
|
170
|
+
@if (luti.isOpen()) {
|
|
171
|
+
<section class="luti-launcher__dropdown" aria-label="Embedded Luti">
|
|
172
|
+
@if (embedUrl()) {
|
|
173
|
+
<iframe
|
|
174
|
+
#frame
|
|
175
|
+
class="luti-launcher__frame"
|
|
176
|
+
allow="clipboard-write"
|
|
177
|
+
referrerpolicy="strict-origin"
|
|
178
|
+
sandbox="allow-forms allow-popups allow-same-origin allow-scripts"
|
|
179
|
+
title="Embedded Luti"
|
|
180
|
+
[src]="embedUrl()"
|
|
181
|
+
(load)="initializeFrame()"
|
|
182
|
+
></iframe>
|
|
183
|
+
}
|
|
184
|
+
@if (loading()) {
|
|
185
|
+
<div class="luti-launcher__state" role="status">
|
|
186
|
+
<span class="luti-launcher__spinner" aria-hidden="true"></span>
|
|
187
|
+
<strong>Opening Luti</strong>
|
|
188
|
+
<span>Securing your session…</span>
|
|
189
|
+
</div>
|
|
190
|
+
} @else if (error()) {
|
|
191
|
+
<div class="luti-launcher__state" role="alert">
|
|
192
|
+
<strong>Luti could not open</strong>
|
|
193
|
+
<span>{{ error() }}</span>
|
|
194
|
+
<button type="button" (click)="restart()">Try again</button>
|
|
195
|
+
</div>
|
|
196
|
+
}
|
|
197
|
+
</section>
|
|
198
|
+
}
|
|
199
|
+
`, isInline: true, styles: [":host{display:inline-block;position:relative;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.4;z-index:1000}.luti-launcher__button{align-items:center;appearance:none;background:#0b1725;border:1px solid rgba(118,151,184,.3);border-radius:999px;color:#f8fbff;cursor:pointer;display:inline-flex;font:inherit;font-size:14px;font-weight:650;gap:8px;min-height:40px;padding:6px 12px 6px 7px;transition:border-color .15s ease,box-shadow .15s ease,transform .15s ease}.luti-launcher__button:hover{border-color:#37acffb3;box-shadow:0 8px 24px #010c1938}.luti-launcher__button:focus-visible{outline:3px solid rgba(55,172,255,.38);outline-offset:2px}.luti-launcher__button:active{transform:translateY(1px)}.luti-launcher__button:disabled{cursor:wait;opacity:.7}.luti-launcher__mark{align-items:center;background:#37acff;border-radius:50%;color:#03101d;display:inline-flex;font-size:12px;font-weight:850;height:28px;justify-content:center;width:28px}.luti-launcher__badge{align-items:center;background:#37acff;border-radius:999px;color:#03101d;display:inline-flex;font-size:10px;font-weight:800;justify-content:center;min-width:20px;padding:2px 6px}.luti-launcher__dropdown{background:#07111e;border:1px solid rgba(118,151,184,.3);border-radius:22px;box-shadow:0 24px 70px #01091273;height:min(760px,calc(100vh - 88px));overflow:hidden;position:absolute;right:0;top:calc(100% + 10px);width:min(430px,calc(100vw - 24px))}.luti-launcher__frame{border:0;display:block;height:100%;width:100%}.luti-launcher__state{align-items:center;color:#aebac8;display:flex;flex-direction:column;gap:10px;background:#07111e;inset:0;justify-content:center;padding:28px;position:absolute;text-align:center}.luti-launcher__state strong{color:#f8fbff;font-size:17px}.luti-launcher__state span{font-size:13px}.luti-launcher__state button{background:#37acff;border:0;border-radius:10px;color:#03101d;cursor:pointer;font:inherit;font-weight:700;margin-top:6px;padding:9px 14px}.luti-launcher__spinner{animation:luti-spin .8s linear infinite;border:2px solid rgba(55,172,255,.28);border-radius:50%;border-top-color:#37acff;height:26px;width:26px}@keyframes luti-spin{to{transform:rotate(360deg)}}@media (max-width: 540px){.luti-launcher__label{display:none}.luti-launcher__dropdown{border-radius:18px;height:calc(100dvh - 76px);position:fixed;right:12px;top:64px;width:calc(100vw - 24px)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
200
|
+
}
|
|
201
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiLauncherComponent, decorators: [{
|
|
202
|
+
type: Component,
|
|
203
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule], selector: "luti-launcher", standalone: true, template: `
|
|
204
|
+
<button
|
|
205
|
+
class="luti-launcher__button"
|
|
206
|
+
type="button"
|
|
207
|
+
[attr.aria-expanded]="luti.isOpen()"
|
|
208
|
+
[attr.aria-label]="label()"
|
|
209
|
+
[disabled]="disabled()"
|
|
210
|
+
(click)="toggle()"
|
|
211
|
+
>
|
|
212
|
+
<span class="luti-launcher__mark" aria-hidden="true">L</span>
|
|
213
|
+
<span class="luti-launcher__label">Luti</span>
|
|
214
|
+
@if (luti.unreadCount() > 0) {
|
|
215
|
+
<span class="luti-launcher__badge" aria-label="Unread Luti activity">
|
|
216
|
+
{{ luti.unreadCount() > 99 ? '99+' : luti.unreadCount() }}
|
|
217
|
+
</span>
|
|
218
|
+
}
|
|
219
|
+
</button>
|
|
220
|
+
|
|
221
|
+
@if (luti.isOpen()) {
|
|
222
|
+
<section class="luti-launcher__dropdown" aria-label="Embedded Luti">
|
|
223
|
+
@if (embedUrl()) {
|
|
224
|
+
<iframe
|
|
225
|
+
#frame
|
|
226
|
+
class="luti-launcher__frame"
|
|
227
|
+
allow="clipboard-write"
|
|
228
|
+
referrerpolicy="strict-origin"
|
|
229
|
+
sandbox="allow-forms allow-popups allow-same-origin allow-scripts"
|
|
230
|
+
title="Embedded Luti"
|
|
231
|
+
[src]="embedUrl()"
|
|
232
|
+
(load)="initializeFrame()"
|
|
233
|
+
></iframe>
|
|
234
|
+
}
|
|
235
|
+
@if (loading()) {
|
|
236
|
+
<div class="luti-launcher__state" role="status">
|
|
237
|
+
<span class="luti-launcher__spinner" aria-hidden="true"></span>
|
|
238
|
+
<strong>Opening Luti</strong>
|
|
239
|
+
<span>Securing your session…</span>
|
|
240
|
+
</div>
|
|
241
|
+
} @else if (error()) {
|
|
242
|
+
<div class="luti-launcher__state" role="alert">
|
|
243
|
+
<strong>Luti could not open</strong>
|
|
244
|
+
<span>{{ error() }}</span>
|
|
245
|
+
<button type="button" (click)="restart()">Try again</button>
|
|
246
|
+
</div>
|
|
247
|
+
}
|
|
248
|
+
</section>
|
|
249
|
+
}
|
|
250
|
+
`, styles: [":host{display:inline-block;position:relative;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.4;z-index:1000}.luti-launcher__button{align-items:center;appearance:none;background:#0b1725;border:1px solid rgba(118,151,184,.3);border-radius:999px;color:#f8fbff;cursor:pointer;display:inline-flex;font:inherit;font-size:14px;font-weight:650;gap:8px;min-height:40px;padding:6px 12px 6px 7px;transition:border-color .15s ease,box-shadow .15s ease,transform .15s ease}.luti-launcher__button:hover{border-color:#37acffb3;box-shadow:0 8px 24px #010c1938}.luti-launcher__button:focus-visible{outline:3px solid rgba(55,172,255,.38);outline-offset:2px}.luti-launcher__button:active{transform:translateY(1px)}.luti-launcher__button:disabled{cursor:wait;opacity:.7}.luti-launcher__mark{align-items:center;background:#37acff;border-radius:50%;color:#03101d;display:inline-flex;font-size:12px;font-weight:850;height:28px;justify-content:center;width:28px}.luti-launcher__badge{align-items:center;background:#37acff;border-radius:999px;color:#03101d;display:inline-flex;font-size:10px;font-weight:800;justify-content:center;min-width:20px;padding:2px 6px}.luti-launcher__dropdown{background:#07111e;border:1px solid rgba(118,151,184,.3);border-radius:22px;box-shadow:0 24px 70px #01091273;height:min(760px,calc(100vh - 88px));overflow:hidden;position:absolute;right:0;top:calc(100% + 10px);width:min(430px,calc(100vw - 24px))}.luti-launcher__frame{border:0;display:block;height:100%;width:100%}.luti-launcher__state{align-items:center;color:#aebac8;display:flex;flex-direction:column;gap:10px;background:#07111e;inset:0;justify-content:center;padding:28px;position:absolute;text-align:center}.luti-launcher__state strong{color:#f8fbff;font-size:17px}.luti-launcher__state span{font-size:13px}.luti-launcher__state button{background:#37acff;border:0;border-radius:10px;color:#03101d;cursor:pointer;font:inherit;font-weight:700;margin-top:6px;padding:9px 14px}.luti-launcher__spinner{animation:luti-spin .8s linear infinite;border:2px solid rgba(55,172,255,.28);border-radius:50%;border-top-color:#37acff;height:26px;width:26px}@keyframes luti-spin{to{transform:rotate(360deg)}}@media (max-width: 540px){.luti-launcher__label{display:none}.luti-launcher__dropdown{border-radius:18px;height:calc(100dvh - 76px);position:fixed;right:12px;top:64px;width:calc(100vw - 24px)}}\n"] }]
|
|
251
|
+
}], ctorParameters: () => [], propDecorators: { frame: [{
|
|
252
|
+
type: ViewChild,
|
|
253
|
+
args: ["frame"]
|
|
254
|
+
}], onEscape: [{
|
|
255
|
+
type: HostListener,
|
|
256
|
+
args: ["document:keydown.escape"]
|
|
257
|
+
}], onDocumentPointerDown: [{
|
|
258
|
+
type: HostListener,
|
|
259
|
+
args: ["document:pointerdown", ["$event"]]
|
|
260
|
+
}] } });
|
|
261
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibHV0aS1sYXVuY2hlci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbHV0aS1sYXVuY2hlci5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQy9DLE9BQU8sRUFBRSxZQUFZLEVBQXdCLE1BQU0sMkJBQTJCLENBQUM7QUFDL0UsT0FBTyxFQUNMLHVCQUF1QixFQUN2QixTQUFTLEVBQ1QsVUFBVSxFQUNWLFlBQVksRUFFWixTQUFTLEVBQ1QsTUFBTSxFQUNOLEtBQUssRUFDTCxNQUFNLEVBQ04sTUFBTSxHQUNQLE1BQU0sZUFBZSxDQUFDO0FBRXZCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDNUMsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdDLE9BQU8sRUFDTCx1QkFBdUIsRUFDdkIsaUJBQWlCLEVBQ2pCLGtCQUFrQixFQUNsQixrQkFBa0IsRUFDbEIsbUJBQW1CLEVBQ25CLHlCQUF5QixHQUMxQixNQUFNLFlBQVksQ0FBQzs7QUFpTXBCLE1BQU0sT0FBTyxxQkFBcUI7SUFDdkIsTUFBTSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUM3QixJQUFJLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQzNCLElBQUksR0FBRyxNQUFNLENBQUMsQ0FBQSxVQUF1QixDQUFBLENBQUMsQ0FBQztJQUN2QyxTQUFTLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBRWpDLEtBQUssR0FBRyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDM0IsUUFBUSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN4QixNQUFNLEdBQUcsTUFBTSxFQUFRLENBQUM7SUFDeEIsTUFBTSxHQUFHLE1BQU0sRUFBUSxDQUFDO0lBQ3hCLFdBQVcsR0FBRyxNQUFNLEVBQVMsQ0FBQztJQUM5QixxQkFBcUIsR0FBRyxNQUFNLEVBQVUsQ0FBQztJQUV6QyxPQUFPLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3hCLEtBQUssR0FBRyxNQUFNLENBQWdCLElBQUksQ0FBQyxDQUFDO0lBQ3BDLFFBQVEsR0FBRyxNQUFNLENBQXlCLElBQUksQ0FBQyxDQUFDO0lBRTdCLEtBQUssQ0FBaUM7SUFFMUQsa0JBQWtCLEdBQWtCLElBQUksQ0FBQztJQUN6QyxtQkFBbUIsR0FBa0IsSUFBSSxDQUFDO0lBQzFDLFdBQVcsR0FBa0IsSUFBSSxDQUFDO0lBRTFDO1FBQ0UsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVELFdBQVc7UUFDVCxNQUFNLENBQUMsbUJBQW1CLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUM1RCxDQUFDO0lBRUQsS0FBSyxDQUFDLE1BQU07UUFDVixJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQztZQUN2QixJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDYixPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDakIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNuQixJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN2QixJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVyQixJQUFJLENBQUM7WUFDSCxJQUFJLENBQUMsa0JBQWtCLEdBQUcseUJBQXlCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUMxRSxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FDZixJQUFJLENBQUMsU0FBUyxDQUFDLDhCQUE4QixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQ3BFLENBQUM7UUFDSixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE1BQU0sVUFBVSxHQUFHLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxLQUFLLENBQUMsK0JBQStCLENBQUMsQ0FBQztZQUMvRixJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUN4QixJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDbkMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDcEMsQ0FBQztJQUNILENBQUM7SUFFRCxLQUFLO1FBQ0gsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNsQixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRCxLQUFLLENBQUMsV0FBVztRQUNmLElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDeEIsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMscURBQXFELENBQUMsQ0FBQztZQUN0RSxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRXJCLElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUM7Z0JBQy9DLFdBQVcsRUFBRSxJQUFJLENBQUMsV0FBVztnQkFDN0IsYUFBYSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsYUFBYTtnQkFDeEMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsTUFBTTthQUMvQixDQUFDLENBQUM7WUFFSCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssd0JBQXdCLEVBQUUsQ0FBQztnQkFDL0MsSUFBSSxDQUFDLHFCQUFxQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztnQkFDekQsSUFBSSxDQUFDLG1CQUFtQixHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQ3BDLE1BQU0sQ0FBQyxnQkFBZ0IsRUFDdkIsb0JBQW9CLEVBQ3BCLDRCQUE0QixDQUM3QixDQUFDO2dCQUNGLElBQUksQ0FBQyxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztvQkFDOUIsTUFBTSxJQUFJLEtBQUssQ0FBQyxxREFBcUQsQ0FBQyxDQUFDO2dCQUN6RSxDQUFDO2dCQUNELElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLDZEQUE2RCxDQUFDLENBQUM7Z0JBQzlFLE9BQU87WUFDVCxDQUFDO1lBRUQsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDM0IsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLFVBQVUsR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLGdDQUFnQyxDQUFDLENBQUM7WUFDaEcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ25DLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ3BDLENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzFCLENBQUM7SUFDSCxDQUFDO0lBR0QsUUFBUTtRQUNOLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDO1lBQ3ZCLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNmLENBQUM7SUFDSCxDQUFDO0lBR0QscUJBQXFCLENBQUMsS0FBbUI7UUFDdkMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxNQUFjLENBQUMsRUFBRSxDQUFDO1lBQ2xGLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNmLENBQUM7SUFDSCxDQUFDO0lBRUQsZUFBZTtRQUNiLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDO1FBQ3hCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLEVBQUUsYUFBYSxDQUFDLGFBQWEsQ0FBQztRQUV2RCxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQjtZQUFFLE9BQU87UUFDaEQsTUFBTSxDQUFDLFdBQVcsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxFQUFFO1lBQy9ELFlBQVksRUFBRSxJQUFJLENBQUMsa0JBQWtCO1NBQ3RDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxPQUFPO1FBQ0wsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkIsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDckIsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO0lBQ3pCLENBQUM7SUFFZ0IsYUFBYSxHQUFHLENBQUMsS0FBNEIsRUFBRSxFQUFFO1FBQ2hFLElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxJQUFJLENBQUMsS0FBSyxFQUFFLGFBQWEsQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUM3RCxJQUNFLEtBQUssQ0FBQyxNQUFNLEtBQUssSUFBSSxDQUFDLG1CQUFtQjtnQkFDekMsS0FBSyxDQUFDLE1BQU0sS0FBSyxNQUFNLENBQUMsUUFBUSxDQUFDLE1BQU07Z0JBQ3ZDLE9BQU8sS0FBSyxDQUFDLElBQUksS0FBSyxRQUFRO2dCQUM5QixLQUFLLENBQUMsSUFBSSxLQUFLLElBQUk7Z0JBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsSUFBSTtnQkFDcEIsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssc0JBQXNCLEVBQzFDLENBQUM7Z0JBQ0QsSUFBSSxDQUFDLG1CQUFtQixHQUFHLElBQUksQ0FBQztnQkFDaEMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2pCLENBQUM7WUFDRCxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUN6RSxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksa0JBQWtCLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDbkMsSUFBSSxDQUFDLFdBQVcsR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQztZQUMxQyxLQUFLLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUN4QixPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksa0JBQWtCLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDbkMsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ2IsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLG1CQUFtQixDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ3BDLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDN0MsQ0FBQztJQUNILENBQUMsQ0FBQztJQUVNLGlCQUFpQjtRQUN2QixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxFQUFFLGFBQWEsQ0FBQyxhQUFhLENBQUM7UUFFdkQsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXO1lBQUUsT0FBTztRQUVyRSxNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRTtZQUM1RCxZQUFZLEVBQUUsSUFBSSxDQUFDLGtCQUFrQjtTQUN0QyxDQUFDLENBQUM7SUFDTCxDQUFDO3dHQS9LVSxxQkFBcUI7NEZBQXJCLHFCQUFxQixzcUJBMUx0Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0ErQ1QsdTdFQWxEUyxZQUFZOzs0RkE2TFgscUJBQXFCO2tCQS9MakMsU0FBUztzQ0FDUyx1QkFBdUIsQ0FBQyxNQUFNLFdBQ3RDLENBQUMsWUFBWSxDQUFDLFlBQ2IsZUFBZSxjQUNiLElBQUksWUFDTjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0ErQ1Q7d0RBNEoyQixLQUFLO3NCQUFoQyxTQUFTO3VCQUFDLE9BQU87Z0JBcUZsQixRQUFRO3NCQURQLFlBQVk7dUJBQUMseUJBQXlCO2dCQVF2QyxxQkFBcUI7c0JBRHBCLFlBQVk7dUJBQUMsc0JBQXNCLEVBQUUsQ0FBQyxRQUFRLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21tb25Nb2R1bGUgfSBmcm9tIFwiQGFuZ3VsYXIvY29tbW9uXCI7XG5pbXBvcnQgeyBEb21TYW5pdGl6ZXIsIHR5cGUgU2FmZVJlc291cmNlVXJsIH0gZnJvbSBcIkBhbmd1bGFyL3BsYXRmb3JtLWJyb3dzZXJcIjtcbmltcG9ydCB7XG4gIENoYW5nZURldGVjdGlvblN0cmF0ZWd5LFxuICBDb21wb25lbnQsXG4gIEVsZW1lbnRSZWYsXG4gIEhvc3RMaXN0ZW5lcixcbiAgT25EZXN0cm95LFxuICBWaWV3Q2hpbGQsXG4gIGluamVjdCxcbiAgaW5wdXQsXG4gIG91dHB1dCxcbiAgc2lnbmFsLFxufSBmcm9tIFwiQGFuZ3VsYXIvY29yZVwiO1xuXG5pbXBvcnQgeyBMVVRJX0NPTkZJRyB9IGZyb20gXCIuL2x1dGktY29uZmlnXCI7XG5pbXBvcnQgeyBMdXRpU2VydmljZSB9IGZyb20gXCIuL2x1dGkuc2VydmljZVwiO1xuaW1wb3J0IHtcbiAgY3JlYXRlQXV0aG9yaXplZE1lc3NhZ2UsXG4gIGNyZWF0ZUluaXRNZXNzYWdlLFxuICBpc0x1dGlDbG9zZU1lc3NhZ2UsXG4gIGlzTHV0aVJlYWR5TWVzc2FnZSxcbiAgaXNMdXRpVW5yZWFkTWVzc2FnZSxcbiAgcmVzb2x2ZVRydXN0ZWRFbWJlZE9yaWdpbixcbn0gZnJvbSBcIi4vcHJvdG9jb2xcIjtcblxuQENvbXBvbmVudCh7XG4gIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlXSxcbiAgc2VsZWN0b3I6IFwibHV0aS1sYXVuY2hlclwiLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICB0ZW1wbGF0ZTogYFxuICAgIDxidXR0b25cbiAgICAgIGNsYXNzPVwibHV0aS1sYXVuY2hlcl9fYnV0dG9uXCJcbiAgICAgIHR5cGU9XCJidXR0b25cIlxuICAgICAgW2F0dHIuYXJpYS1leHBhbmRlZF09XCJsdXRpLmlzT3BlbigpXCJcbiAgICAgIFthdHRyLmFyaWEtbGFiZWxdPVwibGFiZWwoKVwiXG4gICAgICBbZGlzYWJsZWRdPVwiZGlzYWJsZWQoKVwiXG4gICAgICAoY2xpY2spPVwidG9nZ2xlKClcIlxuICAgID5cbiAgICAgIDxzcGFuIGNsYXNzPVwibHV0aS1sYXVuY2hlcl9fbWFya1wiIGFyaWEtaGlkZGVuPVwidHJ1ZVwiPkw8L3NwYW4+XG4gICAgICA8c3BhbiBjbGFzcz1cImx1dGktbGF1bmNoZXJfX2xhYmVsXCI+THV0aTwvc3Bhbj5cbiAgICAgIEBpZiAobHV0aS51bnJlYWRDb3VudCgpID4gMCkge1xuICAgICAgICA8c3BhbiBjbGFzcz1cImx1dGktbGF1bmNoZXJfX2JhZGdlXCIgYXJpYS1sYWJlbD1cIlVucmVhZCBMdXRpIGFjdGl2aXR5XCI+XG4gICAgICAgICAge3sgbHV0aS51bnJlYWRDb3VudCgpID4gOTkgPyAnOTkrJyA6IGx1dGkudW5yZWFkQ291bnQoKSB9fVxuICAgICAgICA8L3NwYW4+XG4gICAgICB9XG4gICAgPC9idXR0b24+XG5cbiAgICBAaWYgKGx1dGkuaXNPcGVuKCkpIHtcbiAgICAgIDxzZWN0aW9uIGNsYXNzPVwibHV0aS1sYXVuY2hlcl9fZHJvcGRvd25cIiBhcmlhLWxhYmVsPVwiRW1iZWRkZWQgTHV0aVwiPlxuICAgICAgICBAaWYgKGVtYmVkVXJsKCkpIHtcbiAgICAgICAgICA8aWZyYW1lXG4gICAgICAgICAgICAjZnJhbWVcbiAgICAgICAgICAgIGNsYXNzPVwibHV0aS1sYXVuY2hlcl9fZnJhbWVcIlxuICAgICAgICAgICAgYWxsb3c9XCJjbGlwYm9hcmQtd3JpdGVcIlxuICAgICAgICAgICAgcmVmZXJyZXJwb2xpY3k9XCJzdHJpY3Qtb3JpZ2luXCJcbiAgICAgICAgICAgIHNhbmRib3g9XCJhbGxvdy1mb3JtcyBhbGxvdy1wb3B1cHMgYWxsb3ctc2FtZS1vcmlnaW4gYWxsb3ctc2NyaXB0c1wiXG4gICAgICAgICAgICB0aXRsZT1cIkVtYmVkZGVkIEx1dGlcIlxuICAgICAgICAgICAgW3NyY109XCJlbWJlZFVybCgpXCJcbiAgICAgICAgICAgIChsb2FkKT1cImluaXRpYWxpemVGcmFtZSgpXCJcbiAgICAgICAgICA+PC9pZnJhbWU+XG4gICAgICAgIH1cbiAgICAgICAgQGlmIChsb2FkaW5nKCkpIHtcbiAgICAgICAgICA8ZGl2IGNsYXNzPVwibHV0aS1sYXVuY2hlcl9fc3RhdGVcIiByb2xlPVwic3RhdHVzXCI+XG4gICAgICAgICAgICA8c3BhbiBjbGFzcz1cImx1dGktbGF1bmNoZXJfX3NwaW5uZXJcIiBhcmlhLWhpZGRlbj1cInRydWVcIj48L3NwYW4+XG4gICAgICAgICAgICA8c3Ryb25nPk9wZW5pbmcgTHV0aTwvc3Ryb25nPlxuICAgICAgICAgICAgPHNwYW4+U2VjdXJpbmcgeW91ciBzZXNzaW9u4oCmPC9zcGFuPlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICB9IEBlbHNlIGlmIChlcnJvcigpKSB7XG4gICAgICAgICAgPGRpdiBjbGFzcz1cImx1dGktbGF1bmNoZXJfX3N0YXRlXCIgcm9sZT1cImFsZXJ0XCI+XG4gICAgICAgICAgICA8c3Ryb25nPkx1dGkgY291bGQgbm90IG9wZW48L3N0cm9uZz5cbiAgICAgICAgICAgIDxzcGFuPnt7IGVycm9yKCkgfX08L3NwYW4+XG4gICAgICAgICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiAoY2xpY2spPVwicmVzdGFydCgpXCI+VHJ5IGFnYWluPC9idXR0b24+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgIH1cbiAgICAgIDwvc2VjdGlvbj5cbiAgICB9XG4gIGAsXG4gIHN0eWxlczogYFxuICAgIDpob3N0IHtcbiAgICAgIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgICAgIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgICAgIGZvbnQtZmFtaWx5OiBJbnRlciwgdWktc2Fucy1zZXJpZiwgc3lzdGVtLXVpLCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsIFwiU2Vnb2UgVUlcIiwgc2Fucy1zZXJpZjtcbiAgICAgIGxpbmUtaGVpZ2h0OiAxLjQ7XG4gICAgICB6LWluZGV4OiAxMDAwO1xuICAgIH1cblxuICAgIC5sdXRpLWxhdW5jaGVyX19idXR0b24ge1xuICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICAgIGFwcGVhcmFuY2U6IG5vbmU7XG4gICAgICBiYWNrZ3JvdW5kOiAjMGIxNzI1O1xuICAgICAgYm9yZGVyOiAxcHggc29saWQgcmdiYSgxMTgsIDE1MSwgMTg0LCAuMyk7XG4gICAgICBib3JkZXItcmFkaXVzOiA5OTlweDtcbiAgICAgIGNvbG9yOiAjZjhmYmZmO1xuICAgICAgY3Vyc29yOiBwb2ludGVyO1xuICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICBmb250OiBpbmhlcml0O1xuICAgICAgZm9udC1zaXplOiAxNHB4O1xuICAgICAgZm9udC13ZWlnaHQ6IDY1MDtcbiAgICAgIGdhcDogOHB4O1xuICAgICAgbWluLWhlaWdodDogNDBweDtcbiAgICAgIHBhZGRpbmc6IDZweCAxMnB4IDZweCA3cHg7XG4gICAgICB0cmFuc2l0aW9uOiBib3JkZXItY29sb3IgMTUwbXMgZWFzZSwgYm94LXNoYWRvdyAxNTBtcyBlYXNlLCB0cmFuc2Zvcm0gMTUwbXMgZWFzZTtcbiAgICB9XG5cbiAgICAubHV0aS1sYXVuY2hlcl9fYnV0dG9uOmhvdmVyIHtcbiAgICAgIGJvcmRlci1jb2xvcjogcmdiYSg1NSwgMTcyLCAyNTUsIC43KTtcbiAgICAgIGJveC1zaGFkb3c6IDAgOHB4IDI0cHggcmdiYSgxLCAxMiwgMjUsIC4yMik7XG4gICAgfVxuXG4gICAgLmx1dGktbGF1bmNoZXJfX2J1dHRvbjpmb2N1cy12aXNpYmxlIHtcbiAgICAgIG91dGxpbmU6IDNweCBzb2xpZCByZ2JhKDU1LCAxNzIsIDI1NSwgLjM4KTtcbiAgICAgIG91dGxpbmUtb2Zmc2V0OiAycHg7XG4gICAgfVxuXG4gICAgLmx1dGktbGF1bmNoZXJfX2J1dHRvbjphY3RpdmUgeyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVkoMXB4KTsgfVxuICAgIC5sdXRpLWxhdW5jaGVyX19idXR0b246ZGlzYWJsZWQgeyBjdXJzb3I6IHdhaXQ7IG9wYWNpdHk6IC43OyB9XG5cbiAgICAubHV0aS1sYXVuY2hlcl9fbWFyayB7XG4gICAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgICAgYmFja2dyb3VuZDogIzM3YWNmZjtcbiAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICAgIGNvbG9yOiAjMDMxMDFkO1xuICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICBmb250LXNpemU6IDEycHg7XG4gICAgICBmb250LXdlaWdodDogODUwO1xuICAgICAgaGVpZ2h0OiAyOHB4O1xuICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgICB3aWR0aDogMjhweDtcbiAgICB9XG5cbiAgICAubHV0aS1sYXVuY2hlcl9fYmFkZ2Uge1xuICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICAgIGJhY2tncm91bmQ6ICMzN2FjZmY7XG4gICAgICBib3JkZXItcmFkaXVzOiA5OTlweDtcbiAgICAgIGNvbG9yOiAjMDMxMDFkO1xuICAgICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgICBmb250LXNpemU6IDEwcHg7XG4gICAgICBmb250LXdlaWdodDogODAwO1xuICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgICBtaW4td2lkdGg6IDIwcHg7XG4gICAgICBwYWRkaW5nOiAycHggNnB4O1xuICAgIH1cblxuICAgIC5sdXRpLWxhdW5jaGVyX19kcm9wZG93biB7XG4gICAgICBiYWNrZ3JvdW5kOiAjMDcxMTFlO1xuICAgICAgYm9yZGVyOiAxcHggc29saWQgcmdiYSgxMTgsIDE1MSwgMTg0LCAuMyk7XG4gICAgICBib3JkZXItcmFkaXVzOiAyMnB4O1xuICAgICAgYm94LXNoYWRvdzogMCAyNHB4IDcwcHggcmdiYSgxLCA5LCAxOCwgLjQ1KTtcbiAgICAgIGhlaWdodDogbWluKDc2MHB4LCBjYWxjKDEwMHZoIC0gODhweCkpO1xuICAgICAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICAgIHJpZ2h0OiAwO1xuICAgICAgdG9wOiBjYWxjKDEwMCUgKyAxMHB4KTtcbiAgICAgIHdpZHRoOiBtaW4oNDMwcHgsIGNhbGMoMTAwdncgLSAyNHB4KSk7XG4gICAgfVxuXG4gICAgLmx1dGktbGF1bmNoZXJfX2ZyYW1lIHtcbiAgICAgIGJvcmRlcjogMDtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgaGVpZ2h0OiAxMDAlO1xuICAgICAgd2lkdGg6IDEwMCU7XG4gICAgfVxuXG4gICAgLmx1dGktbGF1bmNoZXJfX3N0YXRlIHtcbiAgICAgIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gICAgICBjb2xvcjogI2FlYmFjODtcbiAgICAgIGRpc3BsYXk6IGZsZXg7XG4gICAgICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICAgICAgZ2FwOiAxMHB4O1xuICAgICAgYmFja2dyb3VuZDogIzA3MTExZTtcbiAgICAgIGluc2V0OiAwO1xuICAgICAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gICAgICBwYWRkaW5nOiAyOHB4O1xuICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuICAgICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIH1cblxuICAgIC5sdXRpLWxhdW5jaGVyX19zdGF0ZSBzdHJvbmcgeyBjb2xvcjogI2Y4ZmJmZjsgZm9udC1zaXplOiAxN3B4OyB9XG4gICAgLmx1dGktbGF1bmNoZXJfX3N0YXRlIHNwYW4geyBmb250LXNpemU6IDEzcHg7IH1cbiAgICAubHV0aS1sYXVuY2hlcl9fc3RhdGUgYnV0dG9uIHtcbiAgICAgIGJhY2tncm91bmQ6ICMzN2FjZmY7XG4gICAgICBib3JkZXI6IDA7XG4gICAgICBib3JkZXItcmFkaXVzOiAxMHB4O1xuICAgICAgY29sb3I6ICMwMzEwMWQ7XG4gICAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgICBmb250OiBpbmhlcml0O1xuICAgICAgZm9udC13ZWlnaHQ6IDcwMDtcbiAgICAgIG1hcmdpbi10b3A6IDZweDtcbiAgICAgIHBhZGRpbmc6IDlweCAxNHB4O1xuICAgIH1cblxuICAgIC5sdXRpLWxhdW5jaGVyX19zcGlubmVyIHtcbiAgICAgIGFuaW1hdGlvbjogbHV0aS1zcGluIDgwMG1zIGxpbmVhciBpbmZpbml0ZTtcbiAgICAgIGJvcmRlcjogMnB4IHNvbGlkIHJnYmEoNTUsIDE3MiwgMjU1LCAuMjgpO1xuICAgICAgYm9yZGVyLXJhZGl1czogNTAlO1xuICAgICAgYm9yZGVyLXRvcC1jb2xvcjogIzM3YWNmZjtcbiAgICAgIGhlaWdodDogMjZweDtcbiAgICAgIHdpZHRoOiAyNnB4O1xuICAgIH1cblxuICAgIEBrZXlmcmFtZXMgbHV0aS1zcGluIHsgdG8geyB0cmFuc2Zvcm06IHJvdGF0ZSgzNjBkZWcpOyB9IH1cblxuICAgIEBtZWRpYSAobWF4LXdpZHRoOiA1NDBweCkge1xuICAgICAgLmx1dGktbGF1bmNoZXJfX2xhYmVsIHsgZGlzcGxheTogbm9uZTsgfVxuICAgICAgLmx1dGktbGF1bmNoZXJfX2Ryb3Bkb3duIHtcbiAgICAgICAgYm9yZGVyLXJhZGl1czogMThweDtcbiAgICAgICAgaGVpZ2h0OiBjYWxjKDEwMGR2aCAtIDc2cHgpO1xuICAgICAgICBwb3NpdGlvbjogZml4ZWQ7XG4gICAgICAgIHJpZ2h0OiAxMnB4O1xuICAgICAgICB0b3A6IDY0cHg7XG4gICAgICAgIHdpZHRoOiBjYWxjKDEwMHZ3IC0gMjRweCk7XG4gICAgICB9XG4gICAgfVxuICBgLFxufSlcbmV4cG9ydCBjbGFzcyBMdXRpTGF1bmNoZXJDb21wb25lbnQgaW1wbGVtZW50cyBPbkRlc3Ryb3kge1xuICByZWFkb25seSBjb25maWcgPSBpbmplY3QoTFVUSV9DT05GSUcpO1xuICByZWFkb25seSBsdXRpID0gaW5qZWN0KEx1dGlTZXJ2aWNlKTtcbiAgcmVhZG9ubHkgaG9zdCA9IGluamVjdChFbGVtZW50UmVmPEhUTUxFbGVtZW50Pik7XG4gIHJlYWRvbmx5IHNhbml0aXplciA9IGluamVjdChEb21TYW5pdGl6ZXIpO1xuXG4gIHJlYWRvbmx5IGxhYmVsID0gaW5wdXQoXCJPcGVuIEx1dGlcIik7XG4gIHJlYWRvbmx5IGRpc2FibGVkID0gaW5wdXQoZmFsc2UpO1xuICByZWFkb25seSBvcGVuZWQgPSBvdXRwdXQ8dm9pZD4oKTtcbiAgcmVhZG9ubHkgY2xvc2VkID0gb3V0cHV0PHZvaWQ+KCk7XG4gIHJlYWRvbmx5IGxhdW5jaEVycm9yID0gb3V0cHV0PEVycm9yPigpO1xuICByZWFkb25seSBhdXRob3JpemF0aW9uUmVxdWlyZWQgPSBvdXRwdXQ8c3RyaW5nPigpO1xuXG4gIHJlYWRvbmx5IGxvYWRpbmcgPSBzaWduYWwoZmFsc2UpO1xuICByZWFkb25seSBlcnJvciA9IHNpZ25hbDxzdHJpbmcgfCBudWxsPihudWxsKTtcbiAgcmVhZG9ubHkgZW1iZWRVcmwgPSBzaWduYWw8U2FmZVJlc291cmNlVXJsIHwgbnVsbD4obnVsbCk7XG5cbiAgQFZpZXdDaGlsZChcImZyYW1lXCIpIHByaXZhdGUgZnJhbWU/OiBFbGVtZW50UmVmPEhUTUxJRnJhbWVFbGVtZW50PjtcblxuICBwcml2YXRlIHRydXN0ZWRFbWJlZE9yaWdpbjogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgYXV0aG9yaXphdGlvbldpbmRvdzogV2luZG93IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgYm9vdHN0cmFwSWQ6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKFwibWVzc2FnZVwiLCB0aGlzLmhhbmRsZU1lc3NhZ2UpO1xuICB9XG5cbiAgbmdPbkRlc3Ryb3koKSB7XG4gICAgd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoXCJtZXNzYWdlXCIsIHRoaXMuaGFuZGxlTWVzc2FnZSk7XG4gIH1cblxuICBhc3luYyB0b2dnbGUoKSB7XG4gICAgaWYgKHRoaXMubHV0aS5pc09wZW4oKSkge1xuICAgICAgdGhpcy5jbG9zZSgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRoaXMubHV0aS5vcGVuKCk7XG4gICAgdGhpcy5vcGVuZWQuZW1pdCgpO1xuICAgIHRoaXMubG9hZGluZy5zZXQodHJ1ZSk7XG4gICAgdGhpcy5lcnJvci5zZXQobnVsbCk7XG5cbiAgICB0cnkge1xuICAgICAgdGhpcy50cnVzdGVkRW1iZWRPcmlnaW4gPSByZXNvbHZlVHJ1c3RlZEVtYmVkT3JpZ2luKHRoaXMuY29uZmlnLmVtYmVkVXJsKTtcbiAgICAgIHRoaXMuZW1iZWRVcmwuc2V0KFxuICAgICAgICB0aGlzLnNhbml0aXplci5ieXBhc3NTZWN1cml0eVRydXN0UmVzb3VyY2VVcmwodGhpcy5jb25maWcuZW1iZWRVcmwpLFxuICAgICAgKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgY29uc3Qgbm9ybWFsaXplZCA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvciA6IG5ldyBFcnJvcihcIlVuYWJsZSB0byBsb2FkIEVtYmVkZGVkIEx1dGkuXCIpO1xuICAgICAgdGhpcy5sb2FkaW5nLnNldChmYWxzZSk7XG4gICAgICB0aGlzLmVycm9yLnNldChub3JtYWxpemVkLm1lc3NhZ2UpO1xuICAgICAgdGhpcy5sYXVuY2hFcnJvci5lbWl0KG5vcm1hbGl6ZWQpO1xuICAgIH1cbiAgfVxuXG4gIGNsb3NlKCkge1xuICAgIHRoaXMubHV0aS5jbG9zZSgpO1xuICAgIHRoaXMuY2xvc2VkLmVtaXQoKTtcbiAgfVxuXG4gIGFzeW5jIGxvYWRTZXNzaW9uKCkge1xuICAgIGlmICghdGhpcy5ib290c3RyYXBJZCkge1xuICAgICAgdGhpcy5sb2FkaW5nLnNldChmYWxzZSk7XG4gICAgICB0aGlzLmVycm9yLnNldChcIkVtYmVkZGVkIEx1dGkgaXMgbm90IHJlYWR5LiBDbG9zZSBpdCBhbmQgdHJ5IGFnYWluLlwiKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLmxvYWRpbmcuc2V0KHRydWUpO1xuICAgIHRoaXMuZXJyb3Iuc2V0KG51bGwpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHRoaXMuY29uZmlnLnNlc3Npb25Qcm92aWRlcih7XG4gICAgICAgIGJvb3RzdHJhcElkOiB0aGlzLmJvb3RzdHJhcElkLFxuICAgICAgICBpbnRlZ3JhdGlvbklkOiB0aGlzLmNvbmZpZy5pbnRlZ3JhdGlvbklkLFxuICAgICAgICBvcmlnaW46IHdpbmRvdy5sb2NhdGlvbi5vcmlnaW4sXG4gICAgICB9KTtcblxuICAgICAgaWYgKHJlc3VsdC5zdGF0dXMgPT09IFwiYXV0aG9yaXphdGlvbl9yZXF1aXJlZFwiKSB7XG4gICAgICAgIHRoaXMuYXV0aG9yaXphdGlvblJlcXVpcmVkLmVtaXQocmVzdWx0LmF1dGhvcml6YXRpb25VcmwpO1xuICAgICAgICB0aGlzLmF1dGhvcml6YXRpb25XaW5kb3cgPSB3aW5kb3cub3BlbihcbiAgICAgICAgICByZXN1bHQuYXV0aG9yaXphdGlvblVybCxcbiAgICAgICAgICBcImx1dGktYXV0aG9yaXphdGlvblwiLFxuICAgICAgICAgIFwicG9wdXAsd2lkdGg9NTQwLGhlaWdodD03MjBcIixcbiAgICAgICAgKTtcbiAgICAgICAgaWYgKCF0aGlzLmF1dGhvcml6YXRpb25XaW5kb3cpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJBbGxvdyB0aGUgTHV0aSBhdXRob3JpemF0aW9uIHBvcHVwLCB0aGVuIHRyeSBhZ2Fpbi5cIik7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5lcnJvci5zZXQoXCJDb21wbGV0ZSBhdXRob3JpemF0aW9uIGluIHRoZSBuZXcgd2luZG93LCB0aGVuIHJldHVybiBoZXJlLlwiKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICB0aGlzLnBvc3RBdXRob3JpemF0aW9uKCk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGNvbnN0IG5vcm1hbGl6ZWQgPSBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoXCJVbmFibGUgdG8gc3RhcnQgRW1iZWRkZWQgTHV0aS5cIik7XG4gICAgICB0aGlzLmVycm9yLnNldChub3JtYWxpemVkLm1lc3NhZ2UpO1xuICAgICAgdGhpcy5sYXVuY2hFcnJvci5lbWl0KG5vcm1hbGl6ZWQpO1xuICAgIH0gZmluYWxseSB7XG4gICAgICB0aGlzLmxvYWRpbmcuc2V0KGZhbHNlKTtcbiAgICB9XG4gIH1cblxuICBASG9zdExpc3RlbmVyKFwiZG9jdW1lbnQ6a2V5ZG93bi5lc2NhcGVcIilcbiAgb25Fc2NhcGUoKSB7XG4gICAgaWYgKHRoaXMubHV0aS5pc09wZW4oKSkge1xuICAgICAgdGhpcy5jbG9zZSgpO1xuICAgIH1cbiAgfVxuXG4gIEBIb3N0TGlzdGVuZXIoXCJkb2N1bWVudDpwb2ludGVyZG93blwiLCBbXCIkZXZlbnRcIl0pXG4gIG9uRG9jdW1lbnRQb2ludGVyRG93bihldmVudDogUG9pbnRlckV2ZW50KSB7XG4gICAgaWYgKHRoaXMubHV0aS5pc09wZW4oKSAmJiAhdGhpcy5ob3N0Lm5hdGl2ZUVsZW1lbnQuY29udGFpbnMoZXZlbnQudGFyZ2V0IGFzIE5vZGUpKSB7XG4gICAgICB0aGlzLmNsb3NlKCk7XG4gICAgfVxuICB9XG5cbiAgaW5pdGlhbGl6ZUZyYW1lKCkge1xuICAgIHRoaXMuYm9vdHN0cmFwSWQgPSBudWxsO1xuICAgIGNvbnN0IHRhcmdldCA9IHRoaXMuZnJhbWU/Lm5hdGl2ZUVsZW1lbnQuY29udGVudFdpbmRvdztcblxuICAgIGlmICghdGFyZ2V0IHx8ICF0aGlzLnRydXN0ZWRFbWJlZE9yaWdpbikgcmV0dXJuO1xuICAgIHRhcmdldC5wb3N0TWVzc2FnZShjcmVhdGVJbml0TWVzc2FnZSh0aGlzLmNvbmZpZy5pbnRlZ3JhdGlvbklkKSwge1xuICAgICAgdGFyZ2V0T3JpZ2luOiB0aGlzLnRydXN0ZWRFbWJlZE9yaWdpbixcbiAgICB9KTtcbiAgfVxuXG4gIHJlc3RhcnQoKSB7XG4gICAgdGhpcy5sb2FkaW5nLnNldCh0cnVlKTtcbiAgICB0aGlzLmVycm9yLnNldChudWxsKTtcbiAgICB0aGlzLmluaXRpYWxpemVGcmFtZSgpO1xuICB9XG5cbiAgcHJpdmF0ZSByZWFkb25seSBoYW5kbGVNZXNzYWdlID0gKGV2ZW50OiBNZXNzYWdlRXZlbnQ8dW5rbm93bj4pID0+IHtcbiAgICBpZiAoZXZlbnQuc291cmNlICE9PSB0aGlzLmZyYW1lPy5uYXRpdmVFbGVtZW50LmNvbnRlbnRXaW5kb3cpIHtcbiAgICAgIGlmIChcbiAgICAgICAgZXZlbnQuc291cmNlID09PSB0aGlzLmF1dGhvcml6YXRpb25XaW5kb3cgJiZcbiAgICAgICAgZXZlbnQub3JpZ2luID09PSB3aW5kb3cubG9jYXRpb24ub3JpZ2luICYmXG4gICAgICAgIHR5cGVvZiBldmVudC5kYXRhID09PSBcIm9iamVjdFwiICYmXG4gICAgICAgIGV2ZW50LmRhdGEgIT09IG51bGwgJiZcbiAgICAgICAgXCJ0eXBlXCIgaW4gZXZlbnQuZGF0YSAmJlxuICAgICAgICBldmVudC5kYXRhLnR5cGUgPT09IFwibHV0aTpob3N0LWF1dGhvcml6ZWRcIlxuICAgICAgKSB7XG4gICAgICAgIHRoaXMuYXV0aG9yaXphdGlvbldpbmRvdyA9IG51bGw7XG4gICAgICAgIHRoaXMucmVzdGFydCgpO1xuICAgICAgfVxuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmICghdGhpcy50cnVzdGVkRW1iZWRPcmlnaW4gfHwgZXZlbnQub3JpZ2luICE9PSB0aGlzLnRydXN0ZWRFbWJlZE9yaWdpbikge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChpc0x1dGlSZWFkeU1lc3NhZ2UoZXZlbnQuZGF0YSkpIHtcbiAgICAgIHRoaXMuYm9vdHN0cmFwSWQgPSBldmVudC5kYXRhLmJvb3RzdHJhcElkO1xuICAgICAgdm9pZCB0aGlzLmxvYWRTZXNzaW9uKCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKGlzTHV0aUNsb3NlTWVzc2FnZShldmVudC5kYXRhKSkge1xuICAgICAgdGhpcy5jbG9zZSgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChpc0x1dGlVbnJlYWRNZXNzYWdlKGV2ZW50LmRhdGEpKSB7XG4gICAgICB0aGlzLmx1dGkuc2V0VW5yZWFkQ291bnQoZXZlbnQuZGF0YS5jb3VudCk7XG4gICAgfVxuICB9O1xuXG4gIHByaXZhdGUgcG9zdEF1dGhvcml6YXRpb24oKSB7XG4gICAgY29uc3QgdGFyZ2V0ID0gdGhpcy5mcmFtZT8ubmF0aXZlRWxlbWVudC5jb250ZW50V2luZG93O1xuXG4gICAgaWYgKCF0YXJnZXQgfHwgIXRoaXMudHJ1c3RlZEVtYmVkT3JpZ2luIHx8ICF0aGlzLmJvb3RzdHJhcElkKSByZXR1cm47XG5cbiAgICB0YXJnZXQucG9zdE1lc3NhZ2UoY3JlYXRlQXV0aG9yaXplZE1lc3NhZ2UodGhpcy5ib290c3RyYXBJZCksIHtcbiAgICAgIHRhcmdldE9yaWdpbjogdGhpcy50cnVzdGVkRW1iZWRPcmlnaW4sXG4gICAgfSk7XG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Injectable, inject, signal } from "@angular/core";
|
|
2
|
+
import { LUTI_CONFIG } from "./luti-config";
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export class LutiService {
|
|
5
|
+
config = inject(LUTI_CONFIG);
|
|
6
|
+
isOpen = signal(false);
|
|
7
|
+
unreadCount = signal(0);
|
|
8
|
+
open() {
|
|
9
|
+
this.isOpen.set(true);
|
|
10
|
+
}
|
|
11
|
+
close() {
|
|
12
|
+
this.isOpen.set(false);
|
|
13
|
+
}
|
|
14
|
+
toggle() {
|
|
15
|
+
this.isOpen.update((isOpen) => !isOpen);
|
|
16
|
+
}
|
|
17
|
+
setUnreadCount(count) {
|
|
18
|
+
this.unreadCount.set(Math.max(0, Math.floor(count)));
|
|
19
|
+
}
|
|
20
|
+
async endSession() {
|
|
21
|
+
await this.config.endSession?.();
|
|
22
|
+
this.close();
|
|
23
|
+
this.setUnreadCount(0);
|
|
24
|
+
}
|
|
25
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
26
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiService, providedIn: "root" });
|
|
27
|
+
}
|
|
28
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiService, decorators: [{
|
|
29
|
+
type: Injectable,
|
|
30
|
+
args: [{ providedIn: "root" }]
|
|
31
|
+
}] });
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibHV0aS5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2x1dGkuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFM0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGVBQWUsQ0FBQzs7QUFHNUMsTUFBTSxPQUFPLFdBQVc7SUFDTCxNQUFNLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBRXJDLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDdkIsV0FBVyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUVqQyxJQUFJO1FBQ0YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEIsQ0FBQztJQUVELEtBQUs7UUFDSCxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN6QixDQUFDO0lBRUQsTUFBTTtRQUNKLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzFDLENBQUM7SUFFRCxjQUFjLENBQUMsS0FBYTtRQUMxQixJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN2RCxDQUFDO0lBRUQsS0FBSyxDQUFDLFVBQVU7UUFDZCxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQztRQUNqQyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDYixJQUFJLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3pCLENBQUM7d0dBMUJVLFdBQVc7NEdBQVgsV0FBVyxjQURFLE1BQU07OzRGQUNuQixXQUFXO2tCQUR2QixVQUFVO21CQUFDLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUsIGluamVjdCwgc2lnbmFsIH0gZnJvbSBcIkBhbmd1bGFyL2NvcmVcIjtcblxuaW1wb3J0IHsgTFVUSV9DT05GSUcgfSBmcm9tIFwiLi9sdXRpLWNvbmZpZ1wiO1xuXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46IFwicm9vdFwiIH0pXG5leHBvcnQgY2xhc3MgTHV0aVNlcnZpY2Uge1xuICBwcml2YXRlIHJlYWRvbmx5IGNvbmZpZyA9IGluamVjdChMVVRJX0NPTkZJRyk7XG5cbiAgcmVhZG9ubHkgaXNPcGVuID0gc2lnbmFsKGZhbHNlKTtcbiAgcmVhZG9ubHkgdW5yZWFkQ291bnQgPSBzaWduYWwoMCk7XG5cbiAgb3BlbigpIHtcbiAgICB0aGlzLmlzT3Blbi5zZXQodHJ1ZSk7XG4gIH1cblxuICBjbG9zZSgpIHtcbiAgICB0aGlzLmlzT3Blbi5zZXQoZmFsc2UpO1xuICB9XG5cbiAgdG9nZ2xlKCkge1xuICAgIHRoaXMuaXNPcGVuLnVwZGF0ZSgoaXNPcGVuKSA9PiAhaXNPcGVuKTtcbiAgfVxuXG4gIHNldFVucmVhZENvdW50KGNvdW50OiBudW1iZXIpIHtcbiAgICB0aGlzLnVucmVhZENvdW50LnNldChNYXRoLm1heCgwLCBNYXRoLmZsb29yKGNvdW50KSkpO1xuICB9XG5cbiAgYXN5bmMgZW5kU2Vzc2lvbigpIHtcbiAgICBhd2FpdCB0aGlzLmNvbmZpZy5lbmRTZXNzaW9uPy4oKTtcbiAgICB0aGlzLmNsb3NlKCk7XG4gICAgdGhpcy5zZXRVbnJlYWRDb3VudCgwKTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const LUTI_EMBED_PROTOCOL_VERSION = 1;
|
|
2
|
+
function isProtocolRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null;
|
|
4
|
+
}
|
|
5
|
+
export function isLutiReadyMessage(value) {
|
|
6
|
+
return (isProtocolRecord(value) &&
|
|
7
|
+
value.type === "luti:ready" &&
|
|
8
|
+
value.version === LUTI_EMBED_PROTOCOL_VERSION &&
|
|
9
|
+
typeof value.bootstrapId === "string" &&
|
|
10
|
+
/^[0-9a-f-]{36}$/i.test(value.bootstrapId));
|
|
11
|
+
}
|
|
12
|
+
export function isLutiCloseMessage(value) {
|
|
13
|
+
return (isProtocolRecord(value) &&
|
|
14
|
+
value.type === "luti:close" &&
|
|
15
|
+
value.version === LUTI_EMBED_PROTOCOL_VERSION);
|
|
16
|
+
}
|
|
17
|
+
export function isLutiUnreadMessage(value) {
|
|
18
|
+
return (isProtocolRecord(value) &&
|
|
19
|
+
value.type === "luti:unread" &&
|
|
20
|
+
value.version === LUTI_EMBED_PROTOCOL_VERSION &&
|
|
21
|
+
typeof value.count === "number" &&
|
|
22
|
+
Number.isInteger(value.count) &&
|
|
23
|
+
value.count >= 0);
|
|
24
|
+
}
|
|
25
|
+
export function createInitMessage(integrationId) {
|
|
26
|
+
return {
|
|
27
|
+
integrationId,
|
|
28
|
+
type: "luti:init",
|
|
29
|
+
version: LUTI_EMBED_PROTOCOL_VERSION,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function createAuthorizedMessage(bootstrapId) {
|
|
33
|
+
return {
|
|
34
|
+
bootstrapId,
|
|
35
|
+
type: "luti:authorized",
|
|
36
|
+
version: LUTI_EMBED_PROTOCOL_VERSION,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function resolveTrustedEmbedOrigin(embedUrl) {
|
|
40
|
+
const url = new URL(embedUrl);
|
|
41
|
+
const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
42
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLocalhost)) {
|
|
43
|
+
throw new Error("Embedded Luti URL must use HTTPS, except for localhost development.");
|
|
44
|
+
}
|
|
45
|
+
return url.origin;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdG9jb2wuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcHJvdG9jb2wudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsTUFBTSxDQUFDLE1BQU0sMkJBQTJCLEdBQUcsQ0FBVSxDQUFDO0FBK0J0RCxTQUFTLGdCQUFnQixDQUFDLEtBQWM7SUFDdEMsT0FBTyxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLElBQUksQ0FBQztBQUNyRCxDQUFDO0FBRUQsTUFBTSxVQUFVLGtCQUFrQixDQUFDLEtBQWM7SUFDL0MsT0FBTyxDQUNMLGdCQUFnQixDQUFDLEtBQUssQ0FBQztRQUN2QixLQUFLLENBQUMsSUFBSSxLQUFLLFlBQVk7UUFDM0IsS0FBSyxDQUFDLE9BQU8sS0FBSywyQkFBMkI7UUFDN0MsT0FBTyxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVE7UUFDckMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FDM0MsQ0FBQztBQUNKLENBQUM7QUFFRCxNQUFNLFVBQVUsa0JBQWtCLENBQUMsS0FBYztJQUMvQyxPQUFPLENBQ0wsZ0JBQWdCLENBQUMsS0FBSyxDQUFDO1FBQ3ZCLEtBQUssQ0FBQyxJQUFJLEtBQUssWUFBWTtRQUMzQixLQUFLLENBQUMsT0FBTyxLQUFLLDJCQUEyQixDQUM5QyxDQUFDO0FBQ0osQ0FBQztBQUVELE1BQU0sVUFBVSxtQkFBbUIsQ0FBQyxLQUFjO0lBQ2hELE9BQU8sQ0FDTCxnQkFBZ0IsQ0FBQyxLQUFLLENBQUM7UUFDdkIsS0FBSyxDQUFDLElBQUksS0FBSyxhQUFhO1FBQzVCLEtBQUssQ0FBQyxPQUFPLEtBQUssMkJBQTJCO1FBQzdDLE9BQU8sS0FBSyxDQUFDLEtBQUssS0FBSyxRQUFRO1FBQy9CLE1BQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQztRQUM3QixLQUFLLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FDakIsQ0FBQztBQUNKLENBQUM7QUFFRCxNQUFNLFVBQVUsaUJBQWlCLENBQUMsYUFBcUI7SUFDckQsT0FBTztRQUNMLGFBQWE7UUFDYixJQUFJLEVBQUUsV0FBVztRQUNqQixPQUFPLEVBQUUsMkJBQTJCO0tBQ3JDLENBQUM7QUFDSixDQUFDO0FBRUQsTUFBTSxVQUFVLHVCQUF1QixDQUFDLFdBQW1CO0lBQ3pELE9BQU87UUFDTCxXQUFXO1FBQ1gsSUFBSSxFQUFFLGlCQUFpQjtRQUN2QixPQUFPLEVBQUUsMkJBQTJCO0tBQ3JDLENBQUM7QUFDSixDQUFDO0FBRUQsTUFBTSxVQUFVLHlCQUF5QixDQUFDLFFBQWdCO0lBQ3hELE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzlCLE1BQU0sV0FBVyxHQUNmLEdBQUcsQ0FBQyxRQUFRLEtBQUssV0FBVyxJQUFJLEdBQUcsQ0FBQyxRQUFRLEtBQUssV0FBVyxJQUFJLEdBQUcsQ0FBQyxRQUFRLEtBQUssT0FBTyxDQUFDO0lBRTNGLElBQUksR0FBRyxDQUFDLFFBQVEsS0FBSyxRQUFRLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEtBQUssT0FBTyxJQUFJLFdBQVcsQ0FBQyxFQUFFLENBQUM7UUFDNUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxxRUFBcUUsQ0FBQyxDQUFDO0lBQ3pGLENBQUM7SUFFRCxPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUM7QUFDcEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBjb25zdCBMVVRJX0VNQkVEX1BST1RPQ09MX1ZFUlNJT04gPSAxIGFzIGNvbnN0O1xuXG5leHBvcnQgdHlwZSBMdXRpUmVhZHlNZXNzYWdlID0ge1xuICBib290c3RyYXBJZDogc3RyaW5nO1xuICB0eXBlOiBcImx1dGk6cmVhZHlcIjtcbiAgdmVyc2lvbjogdHlwZW9mIExVVElfRU1CRURfUFJPVE9DT0xfVkVSU0lPTjtcbn07XG5cbmV4cG9ydCB0eXBlIEx1dGlJbml0TWVzc2FnZSA9IHtcbiAgaW50ZWdyYXRpb25JZDogc3RyaW5nO1xuICB0eXBlOiBcImx1dGk6aW5pdFwiO1xuICB2ZXJzaW9uOiB0eXBlb2YgTFVUSV9FTUJFRF9QUk9UT0NPTF9WRVJTSU9OO1xufTtcblxuZXhwb3J0IHR5cGUgTHV0aUF1dGhvcml6ZWRNZXNzYWdlID0ge1xuICBib290c3RyYXBJZDogc3RyaW5nO1xuICB0eXBlOiBcImx1dGk6YXV0aG9yaXplZFwiO1xuICB2ZXJzaW9uOiB0eXBlb2YgTFVUSV9FTUJFRF9QUk9UT0NPTF9WRVJTSU9OO1xufTtcblxuZXhwb3J0IHR5cGUgTHV0aUNsb3NlTWVzc2FnZSA9IHtcbiAgdHlwZTogXCJsdXRpOmNsb3NlXCI7XG4gIHZlcnNpb246IHR5cGVvZiBMVVRJX0VNQkVEX1BST1RPQ09MX1ZFUlNJT047XG59O1xuXG5leHBvcnQgdHlwZSBMdXRpVW5yZWFkTWVzc2FnZSA9IHtcbiAgY291bnQ6IG51bWJlcjtcbiAgdHlwZTogXCJsdXRpOnVucmVhZFwiO1xuICB2ZXJzaW9uOiB0eXBlb2YgTFVUSV9FTUJFRF9QUk9UT0NPTF9WRVJTSU9OO1xufTtcblxuZnVuY3Rpb24gaXNQcm90b2NvbFJlY29yZCh2YWx1ZTogdW5rbm93bik6IHZhbHVlIGlzIFJlY29yZDxzdHJpbmcsIHVua25vd24+IHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PT0gXCJvYmplY3RcIiAmJiB2YWx1ZSAhPT0gbnVsbDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzTHV0aVJlYWR5TWVzc2FnZSh2YWx1ZTogdW5rbm93bik6IHZhbHVlIGlzIEx1dGlSZWFkeU1lc3NhZ2Uge1xuICByZXR1cm4gKFxuICAgIGlzUHJvdG9jb2xSZWNvcmQodmFsdWUpICYmXG4gICAgdmFsdWUudHlwZSA9PT0gXCJsdXRpOnJlYWR5XCIgJiZcbiAgICB2YWx1ZS52ZXJzaW9uID09PSBMVVRJX0VNQkVEX1BST1RPQ09MX1ZFUlNJT04gJiZcbiAgICB0eXBlb2YgdmFsdWUuYm9vdHN0cmFwSWQgPT09IFwic3RyaW5nXCIgJiZcbiAgICAvXlswLTlhLWYtXXszNn0kL2kudGVzdCh2YWx1ZS5ib290c3RyYXBJZClcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzTHV0aUNsb3NlTWVzc2FnZSh2YWx1ZTogdW5rbm93bik6IHZhbHVlIGlzIEx1dGlDbG9zZU1lc3NhZ2Uge1xuICByZXR1cm4gKFxuICAgIGlzUHJvdG9jb2xSZWNvcmQodmFsdWUpICYmXG4gICAgdmFsdWUudHlwZSA9PT0gXCJsdXRpOmNsb3NlXCIgJiZcbiAgICB2YWx1ZS52ZXJzaW9uID09PSBMVVRJX0VNQkVEX1BST1RPQ09MX1ZFUlNJT05cbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzTHV0aVVucmVhZE1lc3NhZ2UodmFsdWU6IHVua25vd24pOiB2YWx1ZSBpcyBMdXRpVW5yZWFkTWVzc2FnZSB7XG4gIHJldHVybiAoXG4gICAgaXNQcm90b2NvbFJlY29yZCh2YWx1ZSkgJiZcbiAgICB2YWx1ZS50eXBlID09PSBcImx1dGk6dW5yZWFkXCIgJiZcbiAgICB2YWx1ZS52ZXJzaW9uID09PSBMVVRJX0VNQkVEX1BST1RPQ09MX1ZFUlNJT04gJiZcbiAgICB0eXBlb2YgdmFsdWUuY291bnQgPT09IFwibnVtYmVyXCIgJiZcbiAgICBOdW1iZXIuaXNJbnRlZ2VyKHZhbHVlLmNvdW50KSAmJlxuICAgIHZhbHVlLmNvdW50ID49IDBcbiAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUluaXRNZXNzYWdlKGludGVncmF0aW9uSWQ6IHN0cmluZyk6IEx1dGlJbml0TWVzc2FnZSB7XG4gIHJldHVybiB7XG4gICAgaW50ZWdyYXRpb25JZCxcbiAgICB0eXBlOiBcImx1dGk6aW5pdFwiLFxuICAgIHZlcnNpb246IExVVElfRU1CRURfUFJPVE9DT0xfVkVSU0lPTixcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUF1dGhvcml6ZWRNZXNzYWdlKGJvb3RzdHJhcElkOiBzdHJpbmcpOiBMdXRpQXV0aG9yaXplZE1lc3NhZ2Uge1xuICByZXR1cm4ge1xuICAgIGJvb3RzdHJhcElkLFxuICAgIHR5cGU6IFwibHV0aTphdXRob3JpemVkXCIsXG4gICAgdmVyc2lvbjogTFVUSV9FTUJFRF9QUk9UT0NPTF9WRVJTSU9OLFxuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVRydXN0ZWRFbWJlZE9yaWdpbihlbWJlZFVybDogc3RyaW5nKSB7XG4gIGNvbnN0IHVybCA9IG5ldyBVUkwoZW1iZWRVcmwpO1xuICBjb25zdCBpc0xvY2FsaG9zdCA9XG4gICAgdXJsLmhvc3RuYW1lID09PSBcImxvY2FsaG9zdFwiIHx8IHVybC5ob3N0bmFtZSA9PT0gXCIxMjcuMC4wLjFcIiB8fCB1cmwuaG9zdG5hbWUgPT09IFwiWzo6MV1cIjtcblxuICBpZiAodXJsLnByb3RvY29sICE9PSBcImh0dHBzOlwiICYmICEodXJsLnByb3RvY29sID09PSBcImh0dHA6XCIgJiYgaXNMb2NhbGhvc3QpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFwiRW1iZWRkZWQgTHV0aSBVUkwgbXVzdCB1c2UgSFRUUFMsIGV4Y2VwdCBmb3IgbG9jYWxob3N0IGRldmVsb3BtZW50LlwiKTtcbiAgfVxuXG4gIHJldHVybiB1cmwub3JpZ2luO1xufVxuIl19
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from "./luti-config";
|
|
2
|
+
export * from "./luti-launcher.component";
|
|
3
|
+
export * from "./luti.service";
|
|
4
|
+
export * from "./protocol";
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wdWJsaWMtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLFlBQVksQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCAqIGZyb20gXCIuL2x1dGktY29uZmlnXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9sdXRpLWxhdW5jaGVyLmNvbXBvbmVudFwiO1xuZXhwb3J0ICogZnJvbSBcIi4vbHV0aS5zZXJ2aWNlXCI7XG5leHBvcnQgKiBmcm9tIFwiLi9wcm90b2NvbFwiO1xuIl19
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, signal, Injectable, ElementRef, input, output, HostListener, ViewChild, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
5
|
+
|
|
6
|
+
const LUTI_CONFIG = new InjectionToken("LUTI_CONFIG");
|
|
7
|
+
function provideLuti(config) {
|
|
8
|
+
return [{ provide: LUTI_CONFIG, useValue: config }];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class LutiService {
|
|
12
|
+
config = inject(LUTI_CONFIG);
|
|
13
|
+
isOpen = signal(false);
|
|
14
|
+
unreadCount = signal(0);
|
|
15
|
+
open() {
|
|
16
|
+
this.isOpen.set(true);
|
|
17
|
+
}
|
|
18
|
+
close() {
|
|
19
|
+
this.isOpen.set(false);
|
|
20
|
+
}
|
|
21
|
+
toggle() {
|
|
22
|
+
this.isOpen.update((isOpen) => !isOpen);
|
|
23
|
+
}
|
|
24
|
+
setUnreadCount(count) {
|
|
25
|
+
this.unreadCount.set(Math.max(0, Math.floor(count)));
|
|
26
|
+
}
|
|
27
|
+
async endSession() {
|
|
28
|
+
await this.config.endSession?.();
|
|
29
|
+
this.close();
|
|
30
|
+
this.setUnreadCount(0);
|
|
31
|
+
}
|
|
32
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
33
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiService, providedIn: "root" });
|
|
34
|
+
}
|
|
35
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiService, decorators: [{
|
|
36
|
+
type: Injectable,
|
|
37
|
+
args: [{ providedIn: "root" }]
|
|
38
|
+
}] });
|
|
39
|
+
|
|
40
|
+
const LUTI_EMBED_PROTOCOL_VERSION = 1;
|
|
41
|
+
function isProtocolRecord(value) {
|
|
42
|
+
return typeof value === "object" && value !== null;
|
|
43
|
+
}
|
|
44
|
+
function isLutiReadyMessage(value) {
|
|
45
|
+
return (isProtocolRecord(value) &&
|
|
46
|
+
value.type === "luti:ready" &&
|
|
47
|
+
value.version === LUTI_EMBED_PROTOCOL_VERSION &&
|
|
48
|
+
typeof value.bootstrapId === "string" &&
|
|
49
|
+
/^[0-9a-f-]{36}$/i.test(value.bootstrapId));
|
|
50
|
+
}
|
|
51
|
+
function isLutiCloseMessage(value) {
|
|
52
|
+
return (isProtocolRecord(value) &&
|
|
53
|
+
value.type === "luti:close" &&
|
|
54
|
+
value.version === LUTI_EMBED_PROTOCOL_VERSION);
|
|
55
|
+
}
|
|
56
|
+
function isLutiUnreadMessage(value) {
|
|
57
|
+
return (isProtocolRecord(value) &&
|
|
58
|
+
value.type === "luti:unread" &&
|
|
59
|
+
value.version === LUTI_EMBED_PROTOCOL_VERSION &&
|
|
60
|
+
typeof value.count === "number" &&
|
|
61
|
+
Number.isInteger(value.count) &&
|
|
62
|
+
value.count >= 0);
|
|
63
|
+
}
|
|
64
|
+
function createInitMessage(integrationId) {
|
|
65
|
+
return {
|
|
66
|
+
integrationId,
|
|
67
|
+
type: "luti:init",
|
|
68
|
+
version: LUTI_EMBED_PROTOCOL_VERSION,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function createAuthorizedMessage(bootstrapId) {
|
|
72
|
+
return {
|
|
73
|
+
bootstrapId,
|
|
74
|
+
type: "luti:authorized",
|
|
75
|
+
version: LUTI_EMBED_PROTOCOL_VERSION,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function resolveTrustedEmbedOrigin(embedUrl) {
|
|
79
|
+
const url = new URL(embedUrl);
|
|
80
|
+
const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
81
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLocalhost)) {
|
|
82
|
+
throw new Error("Embedded Luti URL must use HTTPS, except for localhost development.");
|
|
83
|
+
}
|
|
84
|
+
return url.origin;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class LutiLauncherComponent {
|
|
88
|
+
config = inject(LUTI_CONFIG);
|
|
89
|
+
luti = inject(LutiService);
|
|
90
|
+
host = inject((ElementRef));
|
|
91
|
+
sanitizer = inject(DomSanitizer);
|
|
92
|
+
label = input("Open Luti");
|
|
93
|
+
disabled = input(false);
|
|
94
|
+
opened = output();
|
|
95
|
+
closed = output();
|
|
96
|
+
launchError = output();
|
|
97
|
+
authorizationRequired = output();
|
|
98
|
+
loading = signal(false);
|
|
99
|
+
error = signal(null);
|
|
100
|
+
embedUrl = signal(null);
|
|
101
|
+
frame;
|
|
102
|
+
trustedEmbedOrigin = null;
|
|
103
|
+
authorizationWindow = null;
|
|
104
|
+
bootstrapId = null;
|
|
105
|
+
constructor() {
|
|
106
|
+
window.addEventListener("message", this.handleMessage);
|
|
107
|
+
}
|
|
108
|
+
ngOnDestroy() {
|
|
109
|
+
window.removeEventListener("message", this.handleMessage);
|
|
110
|
+
}
|
|
111
|
+
async toggle() {
|
|
112
|
+
if (this.luti.isOpen()) {
|
|
113
|
+
this.close();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.luti.open();
|
|
117
|
+
this.opened.emit();
|
|
118
|
+
this.loading.set(true);
|
|
119
|
+
this.error.set(null);
|
|
120
|
+
try {
|
|
121
|
+
this.trustedEmbedOrigin = resolveTrustedEmbedOrigin(this.config.embedUrl);
|
|
122
|
+
this.embedUrl.set(this.sanitizer.bypassSecurityTrustResourceUrl(this.config.embedUrl));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
const normalized = error instanceof Error ? error : new Error("Unable to load Embedded Luti.");
|
|
126
|
+
this.loading.set(false);
|
|
127
|
+
this.error.set(normalized.message);
|
|
128
|
+
this.launchError.emit(normalized);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
close() {
|
|
132
|
+
this.luti.close();
|
|
133
|
+
this.closed.emit();
|
|
134
|
+
}
|
|
135
|
+
async loadSession() {
|
|
136
|
+
if (!this.bootstrapId) {
|
|
137
|
+
this.loading.set(false);
|
|
138
|
+
this.error.set("Embedded Luti is not ready. Close it and try again.");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.loading.set(true);
|
|
142
|
+
this.error.set(null);
|
|
143
|
+
try {
|
|
144
|
+
const result = await this.config.sessionProvider({
|
|
145
|
+
bootstrapId: this.bootstrapId,
|
|
146
|
+
integrationId: this.config.integrationId,
|
|
147
|
+
origin: window.location.origin,
|
|
148
|
+
});
|
|
149
|
+
if (result.status === "authorization_required") {
|
|
150
|
+
this.authorizationRequired.emit(result.authorizationUrl);
|
|
151
|
+
this.authorizationWindow = window.open(result.authorizationUrl, "luti-authorization", "popup,width=540,height=720");
|
|
152
|
+
if (!this.authorizationWindow) {
|
|
153
|
+
throw new Error("Allow the Luti authorization popup, then try again.");
|
|
154
|
+
}
|
|
155
|
+
this.error.set("Complete authorization in the new window, then return here.");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.postAuthorization();
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const normalized = error instanceof Error ? error : new Error("Unable to start Embedded Luti.");
|
|
162
|
+
this.error.set(normalized.message);
|
|
163
|
+
this.launchError.emit(normalized);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
this.loading.set(false);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
onEscape() {
|
|
170
|
+
if (this.luti.isOpen()) {
|
|
171
|
+
this.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
onDocumentPointerDown(event) {
|
|
175
|
+
if (this.luti.isOpen() && !this.host.nativeElement.contains(event.target)) {
|
|
176
|
+
this.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
initializeFrame() {
|
|
180
|
+
this.bootstrapId = null;
|
|
181
|
+
const target = this.frame?.nativeElement.contentWindow;
|
|
182
|
+
if (!target || !this.trustedEmbedOrigin)
|
|
183
|
+
return;
|
|
184
|
+
target.postMessage(createInitMessage(this.config.integrationId), {
|
|
185
|
+
targetOrigin: this.trustedEmbedOrigin,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
restart() {
|
|
189
|
+
this.loading.set(true);
|
|
190
|
+
this.error.set(null);
|
|
191
|
+
this.initializeFrame();
|
|
192
|
+
}
|
|
193
|
+
handleMessage = (event) => {
|
|
194
|
+
if (event.source !== this.frame?.nativeElement.contentWindow) {
|
|
195
|
+
if (event.source === this.authorizationWindow &&
|
|
196
|
+
event.origin === window.location.origin &&
|
|
197
|
+
typeof event.data === "object" &&
|
|
198
|
+
event.data !== null &&
|
|
199
|
+
"type" in event.data &&
|
|
200
|
+
event.data.type === "luti:host-authorized") {
|
|
201
|
+
this.authorizationWindow = null;
|
|
202
|
+
this.restart();
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!this.trustedEmbedOrigin || event.origin !== this.trustedEmbedOrigin) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (isLutiReadyMessage(event.data)) {
|
|
210
|
+
this.bootstrapId = event.data.bootstrapId;
|
|
211
|
+
void this.loadSession();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (isLutiCloseMessage(event.data)) {
|
|
215
|
+
this.close();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (isLutiUnreadMessage(event.data)) {
|
|
219
|
+
this.luti.setUnreadCount(event.data.count);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
postAuthorization() {
|
|
223
|
+
const target = this.frame?.nativeElement.contentWindow;
|
|
224
|
+
if (!target || !this.trustedEmbedOrigin || !this.bootstrapId)
|
|
225
|
+
return;
|
|
226
|
+
target.postMessage(createAuthorizedMessage(this.bootstrapId), {
|
|
227
|
+
targetOrigin: this.trustedEmbedOrigin,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiLauncherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
231
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LutiLauncherComponent, isStandalone: true, selector: "luti-launcher", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed", launchError: "launchError", authorizationRequired: "authorizationRequired" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:pointerdown": "onDocumentPointerDown($event)" } }, viewQueries: [{ propertyName: "frame", first: true, predicate: ["frame"], descendants: true }], ngImport: i0, template: `
|
|
232
|
+
<button
|
|
233
|
+
class="luti-launcher__button"
|
|
234
|
+
type="button"
|
|
235
|
+
[attr.aria-expanded]="luti.isOpen()"
|
|
236
|
+
[attr.aria-label]="label()"
|
|
237
|
+
[disabled]="disabled()"
|
|
238
|
+
(click)="toggle()"
|
|
239
|
+
>
|
|
240
|
+
<span class="luti-launcher__mark" aria-hidden="true">L</span>
|
|
241
|
+
<span class="luti-launcher__label">Luti</span>
|
|
242
|
+
@if (luti.unreadCount() > 0) {
|
|
243
|
+
<span class="luti-launcher__badge" aria-label="Unread Luti activity">
|
|
244
|
+
{{ luti.unreadCount() > 99 ? '99+' : luti.unreadCount() }}
|
|
245
|
+
</span>
|
|
246
|
+
}
|
|
247
|
+
</button>
|
|
248
|
+
|
|
249
|
+
@if (luti.isOpen()) {
|
|
250
|
+
<section class="luti-launcher__dropdown" aria-label="Embedded Luti">
|
|
251
|
+
@if (embedUrl()) {
|
|
252
|
+
<iframe
|
|
253
|
+
#frame
|
|
254
|
+
class="luti-launcher__frame"
|
|
255
|
+
allow="clipboard-write"
|
|
256
|
+
referrerpolicy="strict-origin"
|
|
257
|
+
sandbox="allow-forms allow-popups allow-same-origin allow-scripts"
|
|
258
|
+
title="Embedded Luti"
|
|
259
|
+
[src]="embedUrl()"
|
|
260
|
+
(load)="initializeFrame()"
|
|
261
|
+
></iframe>
|
|
262
|
+
}
|
|
263
|
+
@if (loading()) {
|
|
264
|
+
<div class="luti-launcher__state" role="status">
|
|
265
|
+
<span class="luti-launcher__spinner" aria-hidden="true"></span>
|
|
266
|
+
<strong>Opening Luti</strong>
|
|
267
|
+
<span>Securing your session…</span>
|
|
268
|
+
</div>
|
|
269
|
+
} @else if (error()) {
|
|
270
|
+
<div class="luti-launcher__state" role="alert">
|
|
271
|
+
<strong>Luti could not open</strong>
|
|
272
|
+
<span>{{ error() }}</span>
|
|
273
|
+
<button type="button" (click)="restart()">Try again</button>
|
|
274
|
+
</div>
|
|
275
|
+
}
|
|
276
|
+
</section>
|
|
277
|
+
}
|
|
278
|
+
`, isInline: true, styles: [":host{display:inline-block;position:relative;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.4;z-index:1000}.luti-launcher__button{align-items:center;appearance:none;background:#0b1725;border:1px solid rgba(118,151,184,.3);border-radius:999px;color:#f8fbff;cursor:pointer;display:inline-flex;font:inherit;font-size:14px;font-weight:650;gap:8px;min-height:40px;padding:6px 12px 6px 7px;transition:border-color .15s ease,box-shadow .15s ease,transform .15s ease}.luti-launcher__button:hover{border-color:#37acffb3;box-shadow:0 8px 24px #010c1938}.luti-launcher__button:focus-visible{outline:3px solid rgba(55,172,255,.38);outline-offset:2px}.luti-launcher__button:active{transform:translateY(1px)}.luti-launcher__button:disabled{cursor:wait;opacity:.7}.luti-launcher__mark{align-items:center;background:#37acff;border-radius:50%;color:#03101d;display:inline-flex;font-size:12px;font-weight:850;height:28px;justify-content:center;width:28px}.luti-launcher__badge{align-items:center;background:#37acff;border-radius:999px;color:#03101d;display:inline-flex;font-size:10px;font-weight:800;justify-content:center;min-width:20px;padding:2px 6px}.luti-launcher__dropdown{background:#07111e;border:1px solid rgba(118,151,184,.3);border-radius:22px;box-shadow:0 24px 70px #01091273;height:min(760px,calc(100vh - 88px));overflow:hidden;position:absolute;right:0;top:calc(100% + 10px);width:min(430px,calc(100vw - 24px))}.luti-launcher__frame{border:0;display:block;height:100%;width:100%}.luti-launcher__state{align-items:center;color:#aebac8;display:flex;flex-direction:column;gap:10px;background:#07111e;inset:0;justify-content:center;padding:28px;position:absolute;text-align:center}.luti-launcher__state strong{color:#f8fbff;font-size:17px}.luti-launcher__state span{font-size:13px}.luti-launcher__state button{background:#37acff;border:0;border-radius:10px;color:#03101d;cursor:pointer;font:inherit;font-weight:700;margin-top:6px;padding:9px 14px}.luti-launcher__spinner{animation:luti-spin .8s linear infinite;border:2px solid rgba(55,172,255,.28);border-radius:50%;border-top-color:#37acff;height:26px;width:26px}@keyframes luti-spin{to{transform:rotate(360deg)}}@media (max-width: 540px){.luti-launcher__label{display:none}.luti-launcher__dropdown{border-radius:18px;height:calc(100dvh - 76px);position:fixed;right:12px;top:64px;width:calc(100vw - 24px)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
279
|
+
}
|
|
280
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LutiLauncherComponent, decorators: [{
|
|
281
|
+
type: Component,
|
|
282
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule], selector: "luti-launcher", standalone: true, template: `
|
|
283
|
+
<button
|
|
284
|
+
class="luti-launcher__button"
|
|
285
|
+
type="button"
|
|
286
|
+
[attr.aria-expanded]="luti.isOpen()"
|
|
287
|
+
[attr.aria-label]="label()"
|
|
288
|
+
[disabled]="disabled()"
|
|
289
|
+
(click)="toggle()"
|
|
290
|
+
>
|
|
291
|
+
<span class="luti-launcher__mark" aria-hidden="true">L</span>
|
|
292
|
+
<span class="luti-launcher__label">Luti</span>
|
|
293
|
+
@if (luti.unreadCount() > 0) {
|
|
294
|
+
<span class="luti-launcher__badge" aria-label="Unread Luti activity">
|
|
295
|
+
{{ luti.unreadCount() > 99 ? '99+' : luti.unreadCount() }}
|
|
296
|
+
</span>
|
|
297
|
+
}
|
|
298
|
+
</button>
|
|
299
|
+
|
|
300
|
+
@if (luti.isOpen()) {
|
|
301
|
+
<section class="luti-launcher__dropdown" aria-label="Embedded Luti">
|
|
302
|
+
@if (embedUrl()) {
|
|
303
|
+
<iframe
|
|
304
|
+
#frame
|
|
305
|
+
class="luti-launcher__frame"
|
|
306
|
+
allow="clipboard-write"
|
|
307
|
+
referrerpolicy="strict-origin"
|
|
308
|
+
sandbox="allow-forms allow-popups allow-same-origin allow-scripts"
|
|
309
|
+
title="Embedded Luti"
|
|
310
|
+
[src]="embedUrl()"
|
|
311
|
+
(load)="initializeFrame()"
|
|
312
|
+
></iframe>
|
|
313
|
+
}
|
|
314
|
+
@if (loading()) {
|
|
315
|
+
<div class="luti-launcher__state" role="status">
|
|
316
|
+
<span class="luti-launcher__spinner" aria-hidden="true"></span>
|
|
317
|
+
<strong>Opening Luti</strong>
|
|
318
|
+
<span>Securing your session…</span>
|
|
319
|
+
</div>
|
|
320
|
+
} @else if (error()) {
|
|
321
|
+
<div class="luti-launcher__state" role="alert">
|
|
322
|
+
<strong>Luti could not open</strong>
|
|
323
|
+
<span>{{ error() }}</span>
|
|
324
|
+
<button type="button" (click)="restart()">Try again</button>
|
|
325
|
+
</div>
|
|
326
|
+
}
|
|
327
|
+
</section>
|
|
328
|
+
}
|
|
329
|
+
`, styles: [":host{display:inline-block;position:relative;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.4;z-index:1000}.luti-launcher__button{align-items:center;appearance:none;background:#0b1725;border:1px solid rgba(118,151,184,.3);border-radius:999px;color:#f8fbff;cursor:pointer;display:inline-flex;font:inherit;font-size:14px;font-weight:650;gap:8px;min-height:40px;padding:6px 12px 6px 7px;transition:border-color .15s ease,box-shadow .15s ease,transform .15s ease}.luti-launcher__button:hover{border-color:#37acffb3;box-shadow:0 8px 24px #010c1938}.luti-launcher__button:focus-visible{outline:3px solid rgba(55,172,255,.38);outline-offset:2px}.luti-launcher__button:active{transform:translateY(1px)}.luti-launcher__button:disabled{cursor:wait;opacity:.7}.luti-launcher__mark{align-items:center;background:#37acff;border-radius:50%;color:#03101d;display:inline-flex;font-size:12px;font-weight:850;height:28px;justify-content:center;width:28px}.luti-launcher__badge{align-items:center;background:#37acff;border-radius:999px;color:#03101d;display:inline-flex;font-size:10px;font-weight:800;justify-content:center;min-width:20px;padding:2px 6px}.luti-launcher__dropdown{background:#07111e;border:1px solid rgba(118,151,184,.3);border-radius:22px;box-shadow:0 24px 70px #01091273;height:min(760px,calc(100vh - 88px));overflow:hidden;position:absolute;right:0;top:calc(100% + 10px);width:min(430px,calc(100vw - 24px))}.luti-launcher__frame{border:0;display:block;height:100%;width:100%}.luti-launcher__state{align-items:center;color:#aebac8;display:flex;flex-direction:column;gap:10px;background:#07111e;inset:0;justify-content:center;padding:28px;position:absolute;text-align:center}.luti-launcher__state strong{color:#f8fbff;font-size:17px}.luti-launcher__state span{font-size:13px}.luti-launcher__state button{background:#37acff;border:0;border-radius:10px;color:#03101d;cursor:pointer;font:inherit;font-weight:700;margin-top:6px;padding:9px 14px}.luti-launcher__spinner{animation:luti-spin .8s linear infinite;border:2px solid rgba(55,172,255,.28);border-radius:50%;border-top-color:#37acff;height:26px;width:26px}@keyframes luti-spin{to{transform:rotate(360deg)}}@media (max-width: 540px){.luti-launcher__label{display:none}.luti-launcher__dropdown{border-radius:18px;height:calc(100dvh - 76px);position:fixed;right:12px;top:64px;width:calc(100vw - 24px)}}\n"] }]
|
|
330
|
+
}], ctorParameters: () => [], propDecorators: { frame: [{
|
|
331
|
+
type: ViewChild,
|
|
332
|
+
args: ["frame"]
|
|
333
|
+
}], onEscape: [{
|
|
334
|
+
type: HostListener,
|
|
335
|
+
args: ["document:keydown.escape"]
|
|
336
|
+
}], onDocumentPointerDown: [{
|
|
337
|
+
type: HostListener,
|
|
338
|
+
args: ["document:pointerdown", ["$event"]]
|
|
339
|
+
}] } });
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Generated bundle index. Do not edit.
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
export { LUTI_CONFIG, LUTI_EMBED_PROTOCOL_VERSION, LutiLauncherComponent, LutiService, createAuthorizedMessage, createInitMessage, isLutiCloseMessage, isLutiReadyMessage, isLutiUnreadMessage, provideLuti, resolveTrustedEmbedOrigin };
|
|
346
|
+
//# sourceMappingURL=luti-angular.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"luti-angular.mjs","sources":["../../src/luti-config.ts","../../src/luti.service.ts","../../src/protocol.ts","../../src/luti-launcher.component.ts","../../src/luti-angular.ts"],"sourcesContent":["import { InjectionToken, type Provider } from \"@angular/core\";\n\nexport type LutiReadySession = {\n expiresAt: Date | string;\n status: \"ready\";\n};\n\nexport type LutiAuthorizationRequired = {\n authorizationUrl: string;\n status: \"authorization_required\";\n};\n\nexport type LutiLaunchResult = LutiReadySession | LutiAuthorizationRequired;\n\nexport type LutiConfig = {\n embedUrl: string;\n integrationId: string;\n sessionProvider: (context: {\n bootstrapId: string;\n integrationId: string;\n origin: string;\n }) => Promise<LutiLaunchResult>;\n endSession?: () => Promise<void>;\n};\n\nexport const LUTI_CONFIG = new InjectionToken<LutiConfig>(\"LUTI_CONFIG\");\n\nexport function provideLuti(config: LutiConfig): Provider[] {\n return [{ provide: LUTI_CONFIG, useValue: config }];\n}\n","import { Injectable, inject, signal } from \"@angular/core\";\n\nimport { LUTI_CONFIG } from \"./luti-config\";\n\n@Injectable({ providedIn: \"root\" })\nexport class LutiService {\n private readonly config = inject(LUTI_CONFIG);\n\n readonly isOpen = signal(false);\n readonly unreadCount = signal(0);\n\n open() {\n this.isOpen.set(true);\n }\n\n close() {\n this.isOpen.set(false);\n }\n\n toggle() {\n this.isOpen.update((isOpen) => !isOpen);\n }\n\n setUnreadCount(count: number) {\n this.unreadCount.set(Math.max(0, Math.floor(count)));\n }\n\n async endSession() {\n await this.config.endSession?.();\n this.close();\n this.setUnreadCount(0);\n }\n}\n","export const LUTI_EMBED_PROTOCOL_VERSION = 1 as const;\n\nexport type LutiReadyMessage = {\n bootstrapId: string;\n type: \"luti:ready\";\n version: typeof LUTI_EMBED_PROTOCOL_VERSION;\n};\n\nexport type LutiInitMessage = {\n integrationId: string;\n type: \"luti:init\";\n version: typeof LUTI_EMBED_PROTOCOL_VERSION;\n};\n\nexport type LutiAuthorizedMessage = {\n bootstrapId: string;\n type: \"luti:authorized\";\n version: typeof LUTI_EMBED_PROTOCOL_VERSION;\n};\n\nexport type LutiCloseMessage = {\n type: \"luti:close\";\n version: typeof LUTI_EMBED_PROTOCOL_VERSION;\n};\n\nexport type LutiUnreadMessage = {\n count: number;\n type: \"luti:unread\";\n version: typeof LUTI_EMBED_PROTOCOL_VERSION;\n};\n\nfunction isProtocolRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function isLutiReadyMessage(value: unknown): value is LutiReadyMessage {\n return (\n isProtocolRecord(value) &&\n value.type === \"luti:ready\" &&\n value.version === LUTI_EMBED_PROTOCOL_VERSION &&\n typeof value.bootstrapId === \"string\" &&\n /^[0-9a-f-]{36}$/i.test(value.bootstrapId)\n );\n}\n\nexport function isLutiCloseMessage(value: unknown): value is LutiCloseMessage {\n return (\n isProtocolRecord(value) &&\n value.type === \"luti:close\" &&\n value.version === LUTI_EMBED_PROTOCOL_VERSION\n );\n}\n\nexport function isLutiUnreadMessage(value: unknown): value is LutiUnreadMessage {\n return (\n isProtocolRecord(value) &&\n value.type === \"luti:unread\" &&\n value.version === LUTI_EMBED_PROTOCOL_VERSION &&\n typeof value.count === \"number\" &&\n Number.isInteger(value.count) &&\n value.count >= 0\n );\n}\n\nexport function createInitMessage(integrationId: string): LutiInitMessage {\n return {\n integrationId,\n type: \"luti:init\",\n version: LUTI_EMBED_PROTOCOL_VERSION,\n };\n}\n\nexport function createAuthorizedMessage(bootstrapId: string): LutiAuthorizedMessage {\n return {\n bootstrapId,\n type: \"luti:authorized\",\n version: LUTI_EMBED_PROTOCOL_VERSION,\n };\n}\n\nexport function resolveTrustedEmbedOrigin(embedUrl: string) {\n const url = new URL(embedUrl);\n const isLocalhost =\n url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\" || url.hostname === \"[::1]\";\n\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && isLocalhost)) {\n throw new Error(\"Embedded Luti URL must use HTTPS, except for localhost development.\");\n }\n\n return url.origin;\n}\n","import { CommonModule } from \"@angular/common\";\nimport { DomSanitizer, type SafeResourceUrl } from \"@angular/platform-browser\";\nimport {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n HostListener,\n OnDestroy,\n ViewChild,\n inject,\n input,\n output,\n signal,\n} from \"@angular/core\";\n\nimport { LUTI_CONFIG } from \"./luti-config\";\nimport { LutiService } from \"./luti.service\";\nimport {\n createAuthorizedMessage,\n createInitMessage,\n isLutiCloseMessage,\n isLutiReadyMessage,\n isLutiUnreadMessage,\n resolveTrustedEmbedOrigin,\n} from \"./protocol\";\n\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [CommonModule],\n selector: \"luti-launcher\",\n standalone: true,\n template: `\n <button\n class=\"luti-launcher__button\"\n type=\"button\"\n [attr.aria-expanded]=\"luti.isOpen()\"\n [attr.aria-label]=\"label()\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n >\n <span class=\"luti-launcher__mark\" aria-hidden=\"true\">L</span>\n <span class=\"luti-launcher__label\">Luti</span>\n @if (luti.unreadCount() > 0) {\n <span class=\"luti-launcher__badge\" aria-label=\"Unread Luti activity\">\n {{ luti.unreadCount() > 99 ? '99+' : luti.unreadCount() }}\n </span>\n }\n </button>\n\n @if (luti.isOpen()) {\n <section class=\"luti-launcher__dropdown\" aria-label=\"Embedded Luti\">\n @if (embedUrl()) {\n <iframe\n #frame\n class=\"luti-launcher__frame\"\n allow=\"clipboard-write\"\n referrerpolicy=\"strict-origin\"\n sandbox=\"allow-forms allow-popups allow-same-origin allow-scripts\"\n title=\"Embedded Luti\"\n [src]=\"embedUrl()\"\n (load)=\"initializeFrame()\"\n ></iframe>\n }\n @if (loading()) {\n <div class=\"luti-launcher__state\" role=\"status\">\n <span class=\"luti-launcher__spinner\" aria-hidden=\"true\"></span>\n <strong>Opening Luti</strong>\n <span>Securing your session…</span>\n </div>\n } @else if (error()) {\n <div class=\"luti-launcher__state\" role=\"alert\">\n <strong>Luti could not open</strong>\n <span>{{ error() }}</span>\n <button type=\"button\" (click)=\"restart()\">Try again</button>\n </div>\n }\n </section>\n }\n `,\n styles: `\n :host {\n display: inline-block;\n position: relative;\n font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n line-height: 1.4;\n z-index: 1000;\n }\n\n .luti-launcher__button {\n align-items: center;\n appearance: none;\n background: #0b1725;\n border: 1px solid rgba(118, 151, 184, .3);\n border-radius: 999px;\n color: #f8fbff;\n cursor: pointer;\n display: inline-flex;\n font: inherit;\n font-size: 14px;\n font-weight: 650;\n gap: 8px;\n min-height: 40px;\n padding: 6px 12px 6px 7px;\n transition: border-color 150ms ease, box-shadow 150ms ease, transform 150ms ease;\n }\n\n .luti-launcher__button:hover {\n border-color: rgba(55, 172, 255, .7);\n box-shadow: 0 8px 24px rgba(1, 12, 25, .22);\n }\n\n .luti-launcher__button:focus-visible {\n outline: 3px solid rgba(55, 172, 255, .38);\n outline-offset: 2px;\n }\n\n .luti-launcher__button:active { transform: translateY(1px); }\n .luti-launcher__button:disabled { cursor: wait; opacity: .7; }\n\n .luti-launcher__mark {\n align-items: center;\n background: #37acff;\n border-radius: 50%;\n color: #03101d;\n display: inline-flex;\n font-size: 12px;\n font-weight: 850;\n height: 28px;\n justify-content: center;\n width: 28px;\n }\n\n .luti-launcher__badge {\n align-items: center;\n background: #37acff;\n border-radius: 999px;\n color: #03101d;\n display: inline-flex;\n font-size: 10px;\n font-weight: 800;\n justify-content: center;\n min-width: 20px;\n padding: 2px 6px;\n }\n\n .luti-launcher__dropdown {\n background: #07111e;\n border: 1px solid rgba(118, 151, 184, .3);\n border-radius: 22px;\n box-shadow: 0 24px 70px rgba(1, 9, 18, .45);\n height: min(760px, calc(100vh - 88px));\n overflow: hidden;\n position: absolute;\n right: 0;\n top: calc(100% + 10px);\n width: min(430px, calc(100vw - 24px));\n }\n\n .luti-launcher__frame {\n border: 0;\n display: block;\n height: 100%;\n width: 100%;\n }\n\n .luti-launcher__state {\n align-items: center;\n color: #aebac8;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: #07111e;\n inset: 0;\n justify-content: center;\n padding: 28px;\n position: absolute;\n text-align: center;\n }\n\n .luti-launcher__state strong { color: #f8fbff; font-size: 17px; }\n .luti-launcher__state span { font-size: 13px; }\n .luti-launcher__state button {\n background: #37acff;\n border: 0;\n border-radius: 10px;\n color: #03101d;\n cursor: pointer;\n font: inherit;\n font-weight: 700;\n margin-top: 6px;\n padding: 9px 14px;\n }\n\n .luti-launcher__spinner {\n animation: luti-spin 800ms linear infinite;\n border: 2px solid rgba(55, 172, 255, .28);\n border-radius: 50%;\n border-top-color: #37acff;\n height: 26px;\n width: 26px;\n }\n\n @keyframes luti-spin { to { transform: rotate(360deg); } }\n\n @media (max-width: 540px) {\n .luti-launcher__label { display: none; }\n .luti-launcher__dropdown {\n border-radius: 18px;\n height: calc(100dvh - 76px);\n position: fixed;\n right: 12px;\n top: 64px;\n width: calc(100vw - 24px);\n }\n }\n `,\n})\nexport class LutiLauncherComponent implements OnDestroy {\n readonly config = inject(LUTI_CONFIG);\n readonly luti = inject(LutiService);\n readonly host = inject(ElementRef<HTMLElement>);\n readonly sanitizer = inject(DomSanitizer);\n\n readonly label = input(\"Open Luti\");\n readonly disabled = input(false);\n readonly opened = output<void>();\n readonly closed = output<void>();\n readonly launchError = output<Error>();\n readonly authorizationRequired = output<string>();\n\n readonly loading = signal(false);\n readonly error = signal<string | null>(null);\n readonly embedUrl = signal<SafeResourceUrl | null>(null);\n\n @ViewChild(\"frame\") private frame?: ElementRef<HTMLIFrameElement>;\n\n private trustedEmbedOrigin: string | null = null;\n private authorizationWindow: Window | null = null;\n private bootstrapId: string | null = null;\n\n constructor() {\n window.addEventListener(\"message\", this.handleMessage);\n }\n\n ngOnDestroy() {\n window.removeEventListener(\"message\", this.handleMessage);\n }\n\n async toggle() {\n if (this.luti.isOpen()) {\n this.close();\n return;\n }\n\n this.luti.open();\n this.opened.emit();\n this.loading.set(true);\n this.error.set(null);\n\n try {\n this.trustedEmbedOrigin = resolveTrustedEmbedOrigin(this.config.embedUrl);\n this.embedUrl.set(\n this.sanitizer.bypassSecurityTrustResourceUrl(this.config.embedUrl),\n );\n } catch (error) {\n const normalized = error instanceof Error ? error : new Error(\"Unable to load Embedded Luti.\");\n this.loading.set(false);\n this.error.set(normalized.message);\n this.launchError.emit(normalized);\n }\n }\n\n close() {\n this.luti.close();\n this.closed.emit();\n }\n\n async loadSession() {\n if (!this.bootstrapId) {\n this.loading.set(false);\n this.error.set(\"Embedded Luti is not ready. Close it and try again.\");\n return;\n }\n\n this.loading.set(true);\n this.error.set(null);\n\n try {\n const result = await this.config.sessionProvider({\n bootstrapId: this.bootstrapId,\n integrationId: this.config.integrationId,\n origin: window.location.origin,\n });\n\n if (result.status === \"authorization_required\") {\n this.authorizationRequired.emit(result.authorizationUrl);\n this.authorizationWindow = window.open(\n result.authorizationUrl,\n \"luti-authorization\",\n \"popup,width=540,height=720\",\n );\n if (!this.authorizationWindow) {\n throw new Error(\"Allow the Luti authorization popup, then try again.\");\n }\n this.error.set(\"Complete authorization in the new window, then return here.\");\n return;\n }\n\n this.postAuthorization();\n } catch (error) {\n const normalized = error instanceof Error ? error : new Error(\"Unable to start Embedded Luti.\");\n this.error.set(normalized.message);\n this.launchError.emit(normalized);\n } finally {\n this.loading.set(false);\n }\n }\n\n @HostListener(\"document:keydown.escape\")\n onEscape() {\n if (this.luti.isOpen()) {\n this.close();\n }\n }\n\n @HostListener(\"document:pointerdown\", [\"$event\"])\n onDocumentPointerDown(event: PointerEvent) {\n if (this.luti.isOpen() && !this.host.nativeElement.contains(event.target as Node)) {\n this.close();\n }\n }\n\n initializeFrame() {\n this.bootstrapId = null;\n const target = this.frame?.nativeElement.contentWindow;\n\n if (!target || !this.trustedEmbedOrigin) return;\n target.postMessage(createInitMessage(this.config.integrationId), {\n targetOrigin: this.trustedEmbedOrigin,\n });\n }\n\n restart() {\n this.loading.set(true);\n this.error.set(null);\n this.initializeFrame();\n }\n\n private readonly handleMessage = (event: MessageEvent<unknown>) => {\n if (event.source !== this.frame?.nativeElement.contentWindow) {\n if (\n event.source === this.authorizationWindow &&\n event.origin === window.location.origin &&\n typeof event.data === \"object\" &&\n event.data !== null &&\n \"type\" in event.data &&\n event.data.type === \"luti:host-authorized\"\n ) {\n this.authorizationWindow = null;\n this.restart();\n }\n return;\n }\n\n if (!this.trustedEmbedOrigin || event.origin !== this.trustedEmbedOrigin) {\n return;\n }\n\n if (isLutiReadyMessage(event.data)) {\n this.bootstrapId = event.data.bootstrapId;\n void this.loadSession();\n return;\n }\n\n if (isLutiCloseMessage(event.data)) {\n this.close();\n return;\n }\n\n if (isLutiUnreadMessage(event.data)) {\n this.luti.setUnreadCount(event.data.count);\n }\n };\n\n private postAuthorization() {\n const target = this.frame?.nativeElement.contentWindow;\n\n if (!target || !this.trustedEmbedOrigin || !this.bootstrapId) return;\n\n target.postMessage(createAuthorizedMessage(this.bootstrapId), {\n targetOrigin: this.trustedEmbedOrigin,\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/index.d.ts
ADDED
package/luti-config.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InjectionToken, type Provider } from "@angular/core";
|
|
2
|
+
export type LutiReadySession = {
|
|
3
|
+
expiresAt: Date | string;
|
|
4
|
+
status: "ready";
|
|
5
|
+
};
|
|
6
|
+
export type LutiAuthorizationRequired = {
|
|
7
|
+
authorizationUrl: string;
|
|
8
|
+
status: "authorization_required";
|
|
9
|
+
};
|
|
10
|
+
export type LutiLaunchResult = LutiReadySession | LutiAuthorizationRequired;
|
|
11
|
+
export type LutiConfig = {
|
|
12
|
+
embedUrl: string;
|
|
13
|
+
integrationId: string;
|
|
14
|
+
sessionProvider: (context: {
|
|
15
|
+
bootstrapId: string;
|
|
16
|
+
integrationId: string;
|
|
17
|
+
origin: string;
|
|
18
|
+
}) => Promise<LutiLaunchResult>;
|
|
19
|
+
endSession?: () => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export declare const LUTI_CONFIG: InjectionToken<LutiConfig>;
|
|
22
|
+
export declare function provideLuti(config: LutiConfig): Provider[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DomSanitizer, type SafeResourceUrl } from "@angular/platform-browser";
|
|
2
|
+
import { ElementRef, OnDestroy } from "@angular/core";
|
|
3
|
+
import { LutiService } from "./luti.service";
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
export declare class LutiLauncherComponent implements OnDestroy {
|
|
6
|
+
readonly config: import("./luti-config").LutiConfig;
|
|
7
|
+
readonly luti: LutiService;
|
|
8
|
+
readonly host: ElementRef<any>;
|
|
9
|
+
readonly sanitizer: DomSanitizer;
|
|
10
|
+
readonly label: import("@angular/core").InputSignal<string>;
|
|
11
|
+
readonly disabled: import("@angular/core").InputSignal<boolean>;
|
|
12
|
+
readonly opened: import("@angular/core").OutputEmitterRef<void>;
|
|
13
|
+
readonly closed: import("@angular/core").OutputEmitterRef<void>;
|
|
14
|
+
readonly launchError: import("@angular/core").OutputEmitterRef<Error>;
|
|
15
|
+
readonly authorizationRequired: import("@angular/core").OutputEmitterRef<string>;
|
|
16
|
+
readonly loading: import("@angular/core").WritableSignal<boolean>;
|
|
17
|
+
readonly error: import("@angular/core").WritableSignal<string>;
|
|
18
|
+
readonly embedUrl: import("@angular/core").WritableSignal<SafeResourceUrl>;
|
|
19
|
+
private frame?;
|
|
20
|
+
private trustedEmbedOrigin;
|
|
21
|
+
private authorizationWindow;
|
|
22
|
+
private bootstrapId;
|
|
23
|
+
constructor();
|
|
24
|
+
ngOnDestroy(): void;
|
|
25
|
+
toggle(): Promise<void>;
|
|
26
|
+
close(): void;
|
|
27
|
+
loadSession(): Promise<void>;
|
|
28
|
+
onEscape(): void;
|
|
29
|
+
onDocumentPointerDown(event: PointerEvent): void;
|
|
30
|
+
initializeFrame(): void;
|
|
31
|
+
restart(): void;
|
|
32
|
+
private readonly handleMessage;
|
|
33
|
+
private postAuthorization;
|
|
34
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LutiLauncherComponent, never>;
|
|
35
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LutiLauncherComponent, "luti-launcher", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "opened": "opened"; "closed": "closed"; "launchError": "launchError"; "authorizationRequired": "authorizationRequired"; }, never, never, true, never>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
export declare class LutiService {
|
|
3
|
+
private readonly config;
|
|
4
|
+
readonly isOpen: import("@angular/core").WritableSignal<boolean>;
|
|
5
|
+
readonly unreadCount: import("@angular/core").WritableSignal<number>;
|
|
6
|
+
open(): void;
|
|
7
|
+
close(): void;
|
|
8
|
+
toggle(): void;
|
|
9
|
+
setUnreadCount(count: number): void;
|
|
10
|
+
endSession(): Promise<void>;
|
|
11
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LutiService, never>;
|
|
12
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<LutiService>;
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@luti/angular",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"peerDependencies": {
|
|
10
|
+
"@angular/common": ">=18.0.0 <23.0.0",
|
|
11
|
+
"@angular/core": ">=18.0.0 <23.0.0",
|
|
12
|
+
"@angular/platform-browser": ">=18.0.0 <23.0.0",
|
|
13
|
+
"rxjs": "^6.5.3 || ^7.4.0"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"tslib": "^2.8.1"
|
|
17
|
+
},
|
|
18
|
+
"module": "fesm2022/luti-angular.mjs",
|
|
19
|
+
"typings": "index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
"./package.json": {
|
|
22
|
+
"default": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./index.d.ts",
|
|
26
|
+
"esm2022": "./esm2022/luti-angular.mjs",
|
|
27
|
+
"esm": "./esm2022/luti-angular.mjs",
|
|
28
|
+
"default": "./fesm2022/luti-angular.mjs"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/protocol.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const LUTI_EMBED_PROTOCOL_VERSION: 1;
|
|
2
|
+
export type LutiReadyMessage = {
|
|
3
|
+
bootstrapId: string;
|
|
4
|
+
type: "luti:ready";
|
|
5
|
+
version: typeof LUTI_EMBED_PROTOCOL_VERSION;
|
|
6
|
+
};
|
|
7
|
+
export type LutiInitMessage = {
|
|
8
|
+
integrationId: string;
|
|
9
|
+
type: "luti:init";
|
|
10
|
+
version: typeof LUTI_EMBED_PROTOCOL_VERSION;
|
|
11
|
+
};
|
|
12
|
+
export type LutiAuthorizedMessage = {
|
|
13
|
+
bootstrapId: string;
|
|
14
|
+
type: "luti:authorized";
|
|
15
|
+
version: typeof LUTI_EMBED_PROTOCOL_VERSION;
|
|
16
|
+
};
|
|
17
|
+
export type LutiCloseMessage = {
|
|
18
|
+
type: "luti:close";
|
|
19
|
+
version: typeof LUTI_EMBED_PROTOCOL_VERSION;
|
|
20
|
+
};
|
|
21
|
+
export type LutiUnreadMessage = {
|
|
22
|
+
count: number;
|
|
23
|
+
type: "luti:unread";
|
|
24
|
+
version: typeof LUTI_EMBED_PROTOCOL_VERSION;
|
|
25
|
+
};
|
|
26
|
+
export declare function isLutiReadyMessage(value: unknown): value is LutiReadyMessage;
|
|
27
|
+
export declare function isLutiCloseMessage(value: unknown): value is LutiCloseMessage;
|
|
28
|
+
export declare function isLutiUnreadMessage(value: unknown): value is LutiUnreadMessage;
|
|
29
|
+
export declare function createInitMessage(integrationId: string): LutiInitMessage;
|
|
30
|
+
export declare function createAuthorizedMessage(bootstrapId: string): LutiAuthorizedMessage;
|
|
31
|
+
export declare function resolveTrustedEmbedOrigin(embedUrl: string): string;
|
package/public-api.d.ts
ADDED