acinguiux-dnr-utils 0.0.2 → 0.0.4

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 CHANGED
@@ -29,7 +29,12 @@ From the application layout it can be integrated like
29
29
  <html lang="en">
30
30
  <head>
31
31
  <link rel="icon" href="favicon.ico" />
32
- <link rel="stylesheet" href="https://shell-fonts.azureedge.net/index.css" />
32
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
33
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
34
+ <link
35
+ rel="stylesheet"
36
+ href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:ital,wght@0,100..900,1,100..900&display=swap"
37
+ />
33
38
  </head>
34
39
  <body>
35
40
  <Application session={session} aiConnectionString={process.env.INSIGHTS_CONNECTION_STRING} sidebar={hasAccess}>
package/package.json CHANGED
@@ -1,42 +1,23 @@
1
1
  {
2
2
  "name": "acinguiux-dnr-utils",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "dependencies": {
5
5
  "@auth/core": "^0.41.1",
6
- "@grafana/faro-web-sdk": "^1.19.0",
7
- "@microsoft/applicationinsights-clickanalytics-js": "^3.3.10",
8
- "@microsoft/applicationinsights-react-js": "^17.3.6",
9
- "@microsoft/applicationinsights-web": "^3.3.10",
10
- "@opentelemetry/core": "^2.2.0",
11
- "@opentelemetry/exporter-logs-otlp-http": "^0.204.0",
12
- "@opentelemetry/exporter-metrics-otlp-http": "^0.202.0",
13
- "@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
14
- "@opentelemetry/instrumentation-http": "^0.203.0",
15
- "@opentelemetry/instrumentation-undici": "^0.13.2",
16
- "@opentelemetry/instrumentation-winston": "^0.50.2",
17
- "@opentelemetry/resources": "^2.2.0",
18
- "@opentelemetry/sdk-node": "^0.203.0",
19
- "@opentelemetry/semantic-conventions": "^1.37.0",
20
- "@opentelemetry/winston-transport": "^0.16.2",
21
- "next": "14.2.35",
22
- "winston": "^3.18.3"
6
+ "next": "14.2.35"
23
7
  },
24
8
  "devDependencies": {
25
9
  "acinguiux-dnr-typescript": "0.0.1",
26
- "acinguiux-dnr-vitest": "0.0.1",
10
+ "acinguiux-dnr-vitest": "0.0.2",
27
11
  "@types/node": "^22.19.0",
28
12
  "@types/react": "^18.0.0"
29
13
  },
30
14
  "exports": {
31
- "./application-insights": "./src/application-insights/index.ts",
32
15
  "./authentication": "./src/authentication/index.ts",
33
16
  "./authentication/middleware": "./src/authentication/middleware.ts",
34
17
  "./authentication/client-auth-guard": "./src/authentication/client-auth-guard.ts",
35
18
  "./ms-graph": "./src/ms-graph/index.ts",
36
19
  "./formatters": "./src/formatters/index.ts",
37
20
  "./mappers": "./src/mappers/index.ts",
38
- "./instrumentation/node": "./src/instrumentation/node.ts",
39
- "./instrumentation/browser": "./src/instrumentation/browser.ts",
40
21
  "./loggers": "./src/loggers/index.ts"
41
22
  },
42
23
  "peerDependencies": {
@@ -4,17 +4,11 @@ import type { Session } from "next-auth";
4
4
  import { callbacks } from "./callbacks";
5
5
  import PingID from "./provider";
6
6
 
7
- type AuthSession = Session & {
8
- expires_at?: number;
9
- access_token?: string;
10
- refresh_token?: string;
11
- };
12
-
13
7
  const isAuthDisabled = ["true", "1", "yes"].includes(
14
8
  (process.env.AUTH_DISABLED || "").toLowerCase(),
15
9
  );
16
10
 
17
- const mockSession: AuthSession = {
11
+ const mockSession: Session = {
18
12
  user: {
19
13
  name: "Local User",
20
14
  email: "local.user@example.com",
@@ -2,19 +2,7 @@ import type { NextRequest } from "next/server.js";
2
2
  import type { Account, Session } from "next-auth";
3
3
  import type { JWT } from "next-auth/jwt";
4
4
 
5
- type AuthJWT = JWT & {
6
- expires_at?: number;
7
- access_token?: string;
8
- refresh_token?: string;
9
- };
10
-
11
- type AuthSession = Session & {
12
- expires_at?: number;
13
- access_token?: string;
14
- refresh_token?: string;
15
- };
16
-
17
- async function jwt({ token, account }: { token: AuthJWT; account?: Account | null }) {
5
+ async function jwt({ token, account }: { token: JWT; account?: Account | null }) {
18
6
  // The first time jwt is called, account exists. We map fields from account to token
19
7
  // which is passed to the session callback. User is also available in this callback which
20
8
  // comes from the profile function in the provider. We can use this in the future to surface
@@ -58,7 +46,7 @@ async function jwt({ token, account }: { token: AuthJWT; account?: Account | nul
58
46
  return token;
59
47
  }
60
48
 
61
- async function session({ session, token }: { session: AuthSession; token: AuthJWT }) {
49
+ async function session({ session, token }: { session: Session; token: JWT }) {
62
50
  // We map fields from token to make them available in the session.
63
51
  session.expires_at = token.expires_at;
64
52
  session.access_token = token.access_token;
@@ -66,7 +54,7 @@ async function session({ session, token }: { session: AuthSession; token: AuthJW
66
54
  return session;
67
55
  }
68
56
 
69
- async function authorized({ auth }: { request: NextRequest; auth: AuthSession | null }) {
57
+ async function authorized({ auth }: { request: NextRequest; auth: Session | null }) {
70
58
  // If there is no access token, the user is not authorized. Returning false from this
71
59
  // callback will redirect the user to the sign in page.
72
60
 
@@ -1,37 +0,0 @@
1
- import { ClickAnalyticsPlugin } from "@microsoft/applicationinsights-clickanalytics-js";
2
- import { ReactPlugin } from "@microsoft/applicationinsights-react-js";
3
- import { ApplicationInsights, type Snippet } from "@microsoft/applicationinsights-web";
4
- import type { Session } from "next-auth";
5
-
6
- export let applicationInsights: ApplicationInsights | undefined;
7
-
8
- export const initApplicationInsights = (connectionString?: string, user?: Session["user"]) => {
9
- if (!connectionString) return;
10
- if (applicationInsights) return;
11
- if (typeof window === "undefined") return;
12
-
13
- const reactPlugin = new ReactPlugin();
14
- const clickAnalyticsPlugin = new ClickAnalyticsPlugin();
15
- const config: Snippet["config"] = {
16
- connectionString,
17
- enableCorsCorrelation: true,
18
- enableAjaxPerfTracking: true,
19
- enableAutoRouteTracking: false,
20
- enableRequestHeaderTracking: true,
21
- enableResponseHeaderTracking: true,
22
- isBrowserLinkTrackingEnabled: true,
23
- extensions: [reactPlugin, clickAnalyticsPlugin] as Snippet["config"]["extensions"],
24
- extensionConfig: {
25
- [clickAnalyticsPlugin.identifier]: { autoCapture: true },
26
- },
27
- };
28
-
29
- applicationInsights = new ApplicationInsights({ config });
30
-
31
- applicationInsights.loadAppInsights();
32
- applicationInsights.setAuthenticatedUserContext(
33
- user?.email || "anonymous@shell.com",
34
- undefined,
35
- true,
36
- );
37
- };
@@ -1,118 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- // Mock only Grafana Faro SDK
4
- const mockInitializeFaro = vi.fn();
5
- const mockGetWebInstrumentations = vi.fn(() => []);
6
- const mockFaro = { api: null };
7
-
8
- vi.mock("@grafana/faro-web-sdk", () => ({
9
- faro: mockFaro,
10
- initializeFaro: mockInitializeFaro,
11
- getWebInstrumentations: mockGetWebInstrumentations,
12
- }));
13
-
14
- describe("browser instrumentation", () => {
15
- const originalEnv = process.env.NEXT_PUBLIC_FARO_URL;
16
-
17
- beforeEach(() => {
18
- vi.clearAllMocks();
19
- delete process.env.NEXT_PUBLIC_FARO_URL;
20
- mockFaro.api = null;
21
- });
22
-
23
- afterEach(() => {
24
- if (originalEnv) {
25
- process.env.NEXT_PUBLIC_FARO_URL = originalEnv;
26
- }
27
- });
28
-
29
- it("should return null when faro.api is already initialized", async () => {
30
- mockFaro.api = {} as any;
31
- process.env.NEXT_PUBLIC_FARO_URL = "https://faro.example.com";
32
-
33
- const { instrument } = await import("./browser");
34
- const result = instrument("test-product", { name: "Test", email: "test@example.com" });
35
-
36
- expect(result).toBeNull();
37
- expect(mockInitializeFaro).not.toHaveBeenCalled();
38
- });
39
-
40
- it("should return null when NEXT_PUBLIC_FARO_URL is not set", async () => {
41
- delete process.env.NEXT_PUBLIC_FARO_URL;
42
-
43
- const { instrument } = await import("./browser");
44
- const result = instrument("test-product", { name: "Test", email: "test@example.com" });
45
-
46
- expect(result).toBeNull();
47
- expect(mockInitializeFaro).not.toHaveBeenCalled();
48
- });
49
-
50
- it("should initialize Faro when NEXT_PUBLIC_FARO_URL is set and faro.api is null", async () => {
51
- process.env.NEXT_PUBLIC_FARO_URL = "https://faro.example.com";
52
-
53
- const { instrument } = await import("./browser");
54
- instrument("my-product", { name: "John Doe", email: "john@example.com" });
55
-
56
- expect(mockInitializeFaro).toHaveBeenCalledWith({
57
- url: "https://faro.example.com/collect",
58
- app: {
59
- name: "my-product-browser",
60
- namespace: "geneva-my-product",
61
- },
62
- user: {
63
- email: "john@example.com",
64
- fullName: "John Doe",
65
- },
66
- pageTracking: {
67
- generatePageId: expect.any(Function),
68
- },
69
- trackGeolocation: true,
70
- instrumentations: [],
71
- });
72
- });
73
-
74
- it("should handle user with empty email", async () => {
75
- process.env.NEXT_PUBLIC_FARO_URL = "https://faro.example.com";
76
-
77
- const { instrument } = await import("./browser");
78
- instrument("test-product", { email: undefined, name: "Test User" });
79
-
80
- expect(mockInitializeFaro).toHaveBeenCalledWith(
81
- expect.objectContaining({
82
- user: {
83
- email: "",
84
- fullName: "Test User",
85
- },
86
- }),
87
- );
88
- });
89
-
90
- it("should handle user with empty name", async () => {
91
- process.env.NEXT_PUBLIC_FARO_URL = "https://faro.example.com";
92
-
93
- const { instrument } = await import("./browser");
94
- instrument("test-product", { email: "test@example.com", name: undefined });
95
-
96
- expect(mockInitializeFaro).toHaveBeenCalledWith(
97
- expect.objectContaining({
98
- user: {
99
- email: "test@example.com",
100
- fullName: "",
101
- },
102
- }),
103
- );
104
- });
105
-
106
- it("should create correct page ID from URL", async () => {
107
- process.env.NEXT_PUBLIC_FARO_URL = "https://faro.example.com";
108
-
109
- const { instrument } = await import("./browser");
110
- instrument("test-product", { email: "test@example.com", name: "Test" });
111
-
112
- const call = mockInitializeFaro.mock.calls[0][0];
113
- const generatePageId = call.pageTracking.generatePageId;
114
-
115
- expect(generatePageId({ pathname: "/home" })).toBe("/home");
116
- expect(generatePageId({ pathname: "/about" })).toBe("/about");
117
- });
118
- });
@@ -1,24 +0,0 @@
1
- import { faro, getWebInstrumentations, initializeFaro } from "@grafana/faro-web-sdk";
2
- import type { Session } from "next-auth";
3
-
4
- export const instrument = (productName: string, user: Session["user"]) => {
5
- if (faro.api) return null;
6
- if (!process.env.NEXT_PUBLIC_FARO_URL) return null;
7
-
8
- initializeFaro({
9
- url: `${process.env.NEXT_PUBLIC_FARO_URL}/collect`,
10
- app: {
11
- name: `${productName}-browser`,
12
- namespace: `geneva-${productName}`,
13
- },
14
- user: {
15
- email: user?.email || "",
16
- fullName: user?.name || "",
17
- },
18
- pageTracking: {
19
- generatePageId: (url) => url.pathname,
20
- },
21
- trackGeolocation: true,
22
- instrumentations: [...getWebInstrumentations()],
23
- });
24
- };
@@ -1,63 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- // Mock only the heavy external dependencies that prevent module loading
4
- vi.mock("@opentelemetry/winston-transport", () => ({
5
- OpenTelemetryTransportV3: vi.fn(),
6
- }));
7
-
8
- vi.mock("winston", () => ({
9
- createLogger: vi.fn(() => ({
10
- info: vi.fn(),
11
- error: vi.fn(),
12
- warn: vi.fn(),
13
- })),
14
- }));
15
-
16
- describe("node instrumentation", () => {
17
- const originalEnv = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
18
-
19
- beforeEach(() => {
20
- delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
21
- });
22
-
23
- afterEach(() => {
24
- if (originalEnv) {
25
- process.env.OTEL_EXPORTER_OTLP_ENDPOINT = originalEnv;
26
- }
27
- });
28
-
29
- it("should export an instrument function", async () => {
30
- const { instrument } = await import("./node");
31
- expect(typeof instrument).toBe("function");
32
- });
33
-
34
- it("should export a logger", async () => {
35
- const { logger } = await import("./node");
36
- expect(logger).toBeDefined();
37
- expect(typeof logger.info).toBe("function");
38
- expect(typeof logger.error).toBe("function");
39
- });
40
-
41
- it("should return early when OTEL_EXPORTER_OTLP_ENDPOINT is not set", async () => {
42
- delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
43
-
44
- const { instrument } = await import("./node");
45
- const result = instrument("test-product");
46
-
47
- expect(result).toBeUndefined();
48
- });
49
-
50
- it("should accept product name parameter without throwing", async () => {
51
- const { instrument } = await import("./node");
52
- expect(() => instrument("service-name")).not.toThrow();
53
- });
54
-
55
- it("should initialize SDK when OTEL_EXPORTER_OTLP_ENDPOINT is set", async () => {
56
- process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://otel.example.com";
57
-
58
- const { instrument } = await import("./node");
59
-
60
- // Should not throw when endpoint is configured
61
- expect(() => instrument("test-service")).not.toThrow();
62
- });
63
- });
@@ -1,81 +0,0 @@
1
- /** biome-ignore-all lint/suspicious/noExplicitAny: supress noExplicitAny */
2
-
3
- import { W3CTraceContextPropagator } from "@opentelemetry/core";
4
- import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
5
- import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
6
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
7
- import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
8
- import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
9
- import { WinstonInstrumentation } from "@opentelemetry/instrumentation-winston";
10
- import { resourceFromAttributes } from "@opentelemetry/resources";
11
- import { api, logs, metrics, NodeSDK, node as traces } from "@opentelemetry/sdk-node";
12
- import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
13
- import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
14
- import { createLogger } from "winston";
15
-
16
- export const logger = createLogger({
17
- transports: [new OpenTelemetryTransportV3()],
18
- });
19
-
20
- class DisableExtractContextPropagator implements api.TextMapPropagator {
21
- constructor(private readonly propagator: W3CTraceContextPropagator) {}
22
- inject = (context: api.Context, carrier: any, setter = api.defaultTextMapSetter) =>
23
- this.propagator.inject(context, carrier, setter);
24
- extract = (context: api.Context, _carrier: any, _getter = api.defaultTextMapGetter) => context;
25
- fields = () => this.propagator.fields();
26
- }
27
-
28
- export const instrument = (productName: string) => {
29
- if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return;
30
-
31
- api.propagation.setGlobalPropagator(
32
- new DisableExtractContextPropagator(new W3CTraceContextPropagator()),
33
- );
34
-
35
- const sdk = new NodeSDK({
36
- resource: resourceFromAttributes({
37
- [ATTR_SERVICE_NAME]: `${productName}-ui`,
38
- }),
39
- metricReader: new metrics.PeriodicExportingMetricReader({
40
- exporter: new OTLPMetricExporter({
41
- url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/metrics`,
42
- }),
43
- exportIntervalMillis: 10000,
44
- }),
45
- spanProcessors: [
46
- new traces.BatchSpanProcessor(
47
- new OTLPTraceExporter({
48
- url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`,
49
- }),
50
- ),
51
- ],
52
- logRecordProcessors: [
53
- new logs.BatchLogRecordProcessor(
54
- new OTLPLogExporter({
55
- url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/logs`,
56
- }),
57
- ),
58
- ],
59
- instrumentations: [
60
- new HttpInstrumentation({
61
- ignoreIncomingRequestHook: (request) => {
62
- const ignoreUrls = ["/_next/static", "/_next/image", "/favicon.ico", "/api/auth"];
63
-
64
- return ignoreUrls.some((url) => request.url?.includes(url));
65
- },
66
- }),
67
- new UndiciInstrumentation({
68
- ignoreRequestHook: (request) => {
69
- const ignoreUrls = ["/v1/traces"];
70
-
71
- return ignoreUrls.some((url) => request.path?.includes(url));
72
- },
73
- }),
74
- new WinstonInstrumentation({
75
- disableLogSending: true,
76
- }),
77
- ],
78
- });
79
-
80
- sdk.start();
81
- };