/
StackContextManager.ts
125 lines (115 loc) · 3.55 KB
/
StackContextManager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Context, ContextManager, ROOT_CONTEXT } from '@opentelemetry/api';
/**
* Stack Context Manager for managing the state in web
* it doesn't fully support the async calls though
*/
export class StackContextManager implements ContextManager {
/**
* whether the context manager is enabled or not
*/
private _enabled = false;
/**
* Keeps the reference to current context
*/
public _currentContext = ROOT_CONTEXT;
/**
*
* @param context
* @param target Function to be executed within the context
*/
// eslint-disable-next-line @typescript-eslint/ban-types
private _bindFunction<T extends Function>(
context = ROOT_CONTEXT,
target: T
): T {
const manager = this;
const contextWrapper = function (this: unknown, ...args: unknown[]) {
return manager.with(context, () => target.apply(this, args));
};
Object.defineProperty(contextWrapper, 'length', {
enumerable: false,
configurable: true,
writable: false,
value: target.length,
});
return contextWrapper as unknown as T;
}
/**
* Returns the active context
*/
active(): Context {
return this._currentContext;
}
/**
* Binds a the certain context or the active one to the target function and then returns the target
* @param context A context (span) to be bind to target
* @param target a function or event emitter. When target or one of its callbacks is called,
* the provided context will be used as the active context for the duration of the call.
*/
bind<T>(context: Context, target: T): T {
// if no specific context to propagate is given, we use the current one
if (context === undefined) {
context = this.active();
}
if (typeof target === 'function') {
return this._bindFunction(context, target);
}
return target;
}
/**
* Disable the context manager (clears the current context)
*/
disable(): this {
this._currentContext = ROOT_CONTEXT;
this._enabled = false;
return this;
}
/**
* Enables the context manager and creates a default(root) context
*/
enable(): this {
if (this._enabled) {
return this;
}
this._enabled = true;
this._currentContext = ROOT_CONTEXT;
return this;
}
/**
* Calls the callback function [fn] with the provided [context]. If [context] is undefined then it will use the window.
* The context will be set as active
* @param context
* @param fn Callback function
* @param thisArg optional receiver to be used for calling fn
* @param args optional arguments forwarded to fn
*/
with<A extends unknown[], F extends (...args: A) => ReturnType<F>>(
context: Context | null,
fn: F,
thisArg?: ThisParameterType<F>,
...args: A
): ReturnType<F> {
const previousContext = this._currentContext;
this._currentContext = context || ROOT_CONTEXT;
try {
return fn.call(thisArg, ...args);
} finally {
this._currentContext = previousContext;
}
}
}