@statsig/client-core 0.0.1-beta.35 → 0.0.1-beta.37

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
@@ -1,8 +1,5 @@
1
1
  # Statsig - Core
2
2
 
3
- > [!IMPORTANT]
4
- > This version of the SDK is still in beta. The non-beta version can be found [here](https://github.com/statsig-io/js-client).
5
-
6
3
  The package that contains all the common logic shared by the various Statsig Javascript packages.
7
4
 
8
5
  Learn more by visiting: https://docs.statsig.com/client/javascript-sdk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statsig/client-core",
3
- "version": "0.0.1-beta.35",
3
+ "version": "0.0.1-beta.37",
4
4
  "dependencies": {},
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -2,6 +2,7 @@ import { DownloadConfigSpecsResponse } from './DownloadConfigSpecsResponse';
2
2
  import { ErrorBoundary } from './ErrorBoundary';
3
3
  import { DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions, LayerEvaluationOptions } from './EvaluationOptions';
4
4
  import { InitializeResponseWithUpdates } from './InitializeResponse';
5
+ import { StatsigSession } from './SessionID';
5
6
  import { StatsigClientEventEmitterInterface } from './StatsigClientEventEmitter';
6
7
  import { EvaluationsDataAdapter, SpecsDataAdapter } from './StatsigDataAdapter';
7
8
  import { StatsigEvent } from './StatsigEvent';
@@ -22,7 +23,7 @@ export type CommonContext = {
22
23
  errorBoundary: ErrorBoundary;
23
24
  };
24
25
  export type AsyncCommonContext = {
25
- sessionID: string;
26
+ session: StatsigSession;
26
27
  stableID: string;
27
28
  };
28
29
  export type OnDeviceEvaluationsContext = CommonContext & {
@@ -12,8 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.EventLogger = void 0;
13
13
  const Hashing_1 = require("./Hashing");
14
14
  const Log_1 = require("./Log");
15
- const NetworkDefaults_1 = require("./NetworkDefaults");
16
- const NetworkParams_1 = require("./NetworkParams");
15
+ const NetworkConfig_1 = require("./NetworkConfig");
17
16
  const SafeJs_1 = require("./SafeJs");
18
17
  const StatsigEvent_1 = require("./StatsigEvent");
19
18
  const StorageProvider_1 = require("./StorageProvider");
@@ -59,8 +58,8 @@ class EventLogger {
59
58
  }, flushInterval);
60
59
  this._flushTimer = intervalId;
61
60
  const config = _options === null || _options === void 0 ? void 0 : _options.networkConfig;
62
- this._logEventUrl = (0, UrlOverrides_1._getOverridableUrl)(config === null || config === void 0 ? void 0 : config.logEventUrl, config === null || config === void 0 ? void 0 : config.api, '/rgstr', NetworkDefaults_1.NetworkDefault.eventsApi);
63
- this._logEventBeaconUrl = (0, UrlOverrides_1._getOverridableUrl)(config === null || config === void 0 ? void 0 : config.logEventBeaconUrl, config === null || config === void 0 ? void 0 : config.api, '/log_event_beacon', NetworkDefaults_1.NetworkDefault.eventsApi);
61
+ this._logEventUrl = (0, UrlOverrides_1._getOverridableUrl)(config === null || config === void 0 ? void 0 : config.logEventUrl, config === null || config === void 0 ? void 0 : config.api, '/rgstr', NetworkConfig_1.NetworkDefault.eventsApi);
62
+ this._logEventBeaconUrl = (0, UrlOverrides_1._getOverridableUrl)(config === null || config === void 0 ? void 0 : config.logEventBeaconUrl, config === null || config === void 0 ? void 0 : config.api, '/log_event_beacon', NetworkConfig_1.NetworkDefault.eventsApi);
64
63
  (0, VisibilityObserving_1._subscribeToVisiblityChanged)((visibility) => {
65
64
  if (visibility === 'background') {
66
65
  _safeFlushAndForget(_sdkKey);
@@ -182,7 +181,7 @@ class EventLogger {
182
181
  url: this._logEventUrl,
183
182
  retries: 3,
184
183
  params: {
185
- [NetworkParams_1.NetworkParam.EventCount]: String(events.length),
184
+ [NetworkConfig_1.NetworkParam.EventCount]: String(events.length),
186
185
  },
187
186
  });
188
187
  const response = (result === null || result === void 0 ? void 0 : result.body)
@@ -0,0 +1,14 @@
1
+ export declare const NetworkDefault: {
2
+ eventsApi: "https://prodregistryv2.org/v1";
3
+ initializeApi: "https://featureassets.org/v1";
4
+ specsApi: "https://assetsconfigcdn.org/v1";
5
+ };
6
+ export declare enum NetworkParam {
7
+ EventCount = "ec",
8
+ SdkKey = "k",
9
+ SdkType = "st",
10
+ SdkVersion = "sv",
11
+ Time = "t",
12
+ SessionID = "sid",
13
+ StatsigEncoded = "se"
14
+ }
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NetworkParam = void 0;
3
+ exports.NetworkParam = exports.NetworkDefault = void 0;
4
+ exports.NetworkDefault = {
5
+ eventsApi: 'https://prodregistryv2.org/v1',
6
+ initializeApi: 'https://featureassets.org/v1',
7
+ specsApi: 'https://assetsconfigcdn.org/v1',
8
+ };
4
9
  var NetworkParam;
5
10
  (function (NetworkParam) {
6
11
  NetworkParam["EventCount"] = "ec";
@@ -13,7 +13,7 @@ exports.NetworkCore = void 0;
13
13
  require("./$_StatsigGlobal");
14
14
  const Diagnostics_1 = require("./Diagnostics");
15
15
  const Log_1 = require("./Log");
16
- const NetworkParams_1 = require("./NetworkParams");
16
+ const NetworkConfig_1 = require("./NetworkConfig");
17
17
  const SDKType_1 = require("./SDKType");
18
18
  const SessionID_1 = require("./SessionID");
19
19
  const StableID_1 = require("./StableID");
@@ -96,7 +96,7 @@ class NetworkCore {
96
96
  }
97
97
  _getPopulatedURL(args) {
98
98
  return __awaiter(this, void 0, void 0, function* () {
99
- const params = Object.assign({ [NetworkParams_1.NetworkParam.SdkKey]: args.sdkKey, [NetworkParams_1.NetworkParam.SdkType]: SDKType_1.SDKType._get(args.sdkKey), [NetworkParams_1.NetworkParam.SdkVersion]: StatsigMetadata_1.SDK_VERSION, [NetworkParams_1.NetworkParam.Time]: String(Date.now()), [NetworkParams_1.NetworkParam.SessionID]: yield SessionID_1.SessionID.get(args.sdkKey) }, args.params);
99
+ const params = Object.assign({ [NetworkConfig_1.NetworkParam.SdkKey]: args.sdkKey, [NetworkConfig_1.NetworkParam.SdkType]: SDKType_1.SDKType._get(args.sdkKey), [NetworkConfig_1.NetworkParam.SdkVersion]: StatsigMetadata_1.SDK_VERSION, [NetworkConfig_1.NetworkParam.Time]: String(Date.now()), [NetworkConfig_1.NetworkParam.SessionID]: yield SessionID_1.SessionID.get(args.sdkKey) }, args.params);
100
100
  const query = Object.entries(params)
101
101
  .map(([key, value]) => {
102
102
  return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
@@ -125,7 +125,7 @@ class NetworkCore {
125
125
  !window.btoa) {
126
126
  return input;
127
127
  }
128
- args.params = Object.assign(Object.assign({}, ((_b = args.params) !== null && _b !== void 0 ? _b : {})), { [NetworkParams_1.NetworkParam.StatsigEncoded]: '1' });
128
+ args.params = Object.assign(Object.assign({}, ((_b = args.params) !== null && _b !== void 0 ? _b : {})), { [NetworkConfig_1.NetworkParam.StatsigEncoded]: '1' });
129
129
  return (_c = window.btoa(input).split('').reverse().join('')) !== null && _c !== void 0 ? _c : input;
130
130
  }
131
131
  }
@@ -1,12 +1,19 @@
1
- type SessionState = {
2
- emitFunction: () => void;
3
- ageTimeoutID: ReturnType<typeof setTimeout> | null;
4
- idleTimeoutID: ReturnType<typeof setTimeout> | null;
1
+ type SessionTimeoutID = ReturnType<typeof setTimeout>;
2
+ type SessionData = {
3
+ sessionID: string;
4
+ startTime: number;
5
+ lastUpdate: number;
6
+ };
7
+ export type StatsigSession = {
8
+ data: SessionData;
9
+ sdkKey: string;
10
+ ageTimeoutID?: SessionTimeoutID;
11
+ idleTimeoutID?: SessionTimeoutID;
5
12
  };
6
13
  export declare const SessionID: {
7
14
  get: (sdkKey: string) => Promise<string>;
8
- _getPromise: (sdkKey: string) => Promise<string>;
9
- _setEmitFunction: (eFunction: () => void, sdkKey: string) => void;
10
- _resetTimeout: (sessionState: SessionState, timeoutID: ReturnType<typeof setTimeout> | null, duration: number) => ReturnType<typeof setTimeout> | null;
15
+ };
16
+ export declare const StatsigSession: {
17
+ get: (sdkKey: string) => Promise<StatsigSession>;
11
18
  };
12
19
  export {};
package/src/SessionID.js CHANGED
@@ -9,75 +9,80 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.SessionID = void 0;
12
+ exports.StatsigSession = exports.SessionID = void 0;
13
13
  const Hashing_1 = require("./Hashing");
14
14
  const Log_1 = require("./Log");
15
15
  const StorageProvider_1 = require("./StorageProvider");
16
16
  const UUID_1 = require("./UUID");
17
- const SESSION_ID_MAP = {};
18
- const SESSION_STATE_MAP = {};
19
- const PROMISE_MAP = {};
20
17
  const MAX_SESSION_IDLE_TIME = 30 * 60 * 1000; // 30 minutes
21
18
  const MAX_SESSION_AGE = 4 * 60 * 60 * 1000; // 4 hours
19
+ const PROMISE_MAP = {};
22
20
  exports.SessionID = {
23
- get: (sdkKey) => {
24
- if (PROMISE_MAP[sdkKey] != null) {
25
- return PROMISE_MAP[sdkKey];
21
+ get: (sdkKey) => __awaiter(void 0, void 0, void 0, function* () {
22
+ return exports.StatsigSession.get(sdkKey).then((x) => x.data.sessionID);
23
+ }),
24
+ };
25
+ exports.StatsigSession = {
26
+ get: (sdkKey) => __awaiter(void 0, void 0, void 0, function* () {
27
+ if (PROMISE_MAP[sdkKey] == null) {
28
+ PROMISE_MAP[sdkKey] = _loadSession(sdkKey);
26
29
  }
27
- return (PROMISE_MAP[sdkKey] = exports.SessionID._getPromise(sdkKey));
28
- },
29
- _getPromise: (sdkKey) => __awaiter(void 0, void 0, void 0, function* () {
30
- var _a;
31
- let session = SESSION_ID_MAP[sdkKey];
30
+ const session = yield PROMISE_MAP[sdkKey];
31
+ return _bumpSession(session);
32
+ }),
33
+ };
34
+ function _loadSession(sdkKey) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ let data = yield _loadFromStorage(sdkKey);
32
37
  const now = Date.now();
33
- if (session == null) {
34
- let tempSession = yield _loadFromStorage(sdkKey);
35
- if (tempSession == null) {
36
- tempSession = {
37
- sessionID: (0, UUID_1.getUUID)(),
38
- startTime: now,
39
- lastUpdate: now,
40
- };
41
- }
42
- session = tempSession;
43
- SESSION_ID_MAP[sdkKey] = session;
38
+ if (!data) {
39
+ data = {
40
+ sessionID: (0, UUID_1.getUUID)(),
41
+ startTime: now,
42
+ lastUpdate: now,
43
+ };
44
44
  }
45
- const sessionState = (_a = SESSION_STATE_MAP[sdkKey]) !== null && _a !== void 0 ? _a : {
46
- ageTimeoutID: null,
47
- idleTimeoutID: null,
48
- emitFunction: () => {
49
- return;
50
- },
45
+ return {
46
+ data,
47
+ sdkKey,
51
48
  };
52
- if (now - session.startTime > MAX_SESSION_AGE ||
53
- now - session.lastUpdate > MAX_SESSION_IDLE_TIME) {
54
- session.sessionID = (0, UUID_1.getUUID)();
55
- session.startTime = now;
49
+ });
50
+ }
51
+ function _bumpSession(session) {
52
+ const now = Date.now();
53
+ const data = session.data;
54
+ if (_isIdle(data) || _hasRunTooLong(data)) {
55
+ data.sessionID = (0, UUID_1.getUUID)();
56
+ data.startTime = now;
57
+ }
58
+ data.lastUpdate = now;
59
+ _persistToStorage(data, session.sdkKey);
60
+ session.data = data;
61
+ clearTimeout(session.idleTimeoutID);
62
+ clearTimeout(session.ageTimeoutID);
63
+ const lifetime = now - data.startTime;
64
+ const sdkKey = session.sdkKey;
65
+ return {
66
+ data,
67
+ sdkKey,
68
+ // idleTimeoutID: _createSessionTimeout(sdkKey, MAX_SESSION_IDLE_TIME),
69
+ ageTimeoutID: _createSessionTimeout(sdkKey, MAX_SESSION_AGE - lifetime),
70
+ };
71
+ }
72
+ function _createSessionTimeout(sdkKey, duration) {
73
+ return setTimeout(() => {
74
+ const client = __STATSIG__ === null || __STATSIG__ === void 0 ? void 0 : __STATSIG__.instance(sdkKey);
75
+ if (client) {
76
+ client.$emt({ name: 'session_expired' });
56
77
  }
57
- session.lastUpdate = now;
58
- _persistToStorage(session, sdkKey);
59
- sessionState.idleTimeoutID = exports.SessionID._resetTimeout(sessionState, sessionState.idleTimeoutID, MAX_SESSION_IDLE_TIME);
60
- sessionState.ageTimeoutID = exports.SessionID._resetTimeout(sessionState, sessionState.ageTimeoutID, MAX_SESSION_AGE - (now - session.startTime));
61
- SESSION_ID_MAP[sdkKey] = session;
62
- SESSION_STATE_MAP[sdkKey] = sessionState;
63
- PROMISE_MAP[sdkKey] = null;
64
- return session.sessionID;
65
- }),
66
- _setEmitFunction: (eFunction, sdkKey) => {
67
- var _a;
68
- const sessionState = (_a = SESSION_STATE_MAP[sdkKey]) !== null && _a !== void 0 ? _a : {
69
- ageTimeoutID: null,
70
- idleTimeoutID: null,
71
- emitFunction: eFunction,
72
- };
73
- sessionState.emitFunction = eFunction;
74
- SESSION_STATE_MAP[sdkKey] = sessionState;
75
- },
76
- _resetTimeout: (sessionState, timeoutID, duration) => {
77
- clearTimeout(timeoutID !== null && timeoutID !== void 0 ? timeoutID : undefined);
78
- return setTimeout(sessionState.emitFunction, duration);
79
- },
80
- };
78
+ }, duration);
79
+ }
80
+ function _isIdle({ lastUpdate }) {
81
+ return Date.now() - lastUpdate > MAX_SESSION_IDLE_TIME;
82
+ }
83
+ function _hasRunTooLong({ startTime }) {
84
+ return Date.now() - startTime > MAX_SESSION_AGE;
85
+ }
81
86
  function _getSessionIDStorageKey(sdkKey) {
82
87
  return `statsig.session_id.${(0, Hashing_1.DJB2)(sdkKey)}`;
83
88
  }
@@ -59,8 +59,8 @@ export declare abstract class StatsigClientBase<TAdapter extends EvaluationsData
59
59
  * @see {@link on} for subscribing to events.
60
60
  */
61
61
  off<T extends StatsigClientEventName>(event: T, listener: StatsigClientEventCallback<T>): void;
62
- __on<T extends StatsigClientEventName>(event: T, listener: StatsigClientEventCallback<T>): void;
63
- protected _emit(event: AnyStatsigClientEvent): void;
62
+ $on<T extends StatsigClientEventName>(event: T, listener: StatsigClientEventCallback<T>): void;
63
+ $emt(event: AnyStatsigClientEvent): void;
64
64
  protected _setStatus(newStatus: StatsigLoadingStatus, values: DataAdapterResult | null): void;
65
65
  protected _enqueueExposure(name: string, exposure: StatsigEventInternal, options?: EvaluationOptionsCommon): void;
66
66
  }
@@ -16,7 +16,6 @@ const ErrorBoundary_1 = require("./ErrorBoundary");
16
16
  const EventLogger_1 = require("./EventLogger");
17
17
  const Log_1 = require("./Log");
18
18
  const SafeJs_1 = require("./SafeJs");
19
- const SessionID_1 = require("./SessionID");
20
19
  const StableID_1 = require("./StableID");
21
20
  const StorageProvider_1 = require("./StorageProvider");
22
21
  class StatsigClientBase {
@@ -24,7 +23,7 @@ class StatsigClientBase {
24
23
  var _a, _b;
25
24
  this.loadingStatus = 'Uninitialized';
26
25
  this._listeners = {};
27
- const emitter = this._emit.bind(this);
26
+ const emitter = this.$emt.bind(this);
28
27
  const statsigGlobal = (0, __StatsigGlobal_1._getStatsigGlobal)();
29
28
  const instances = (_a = statsigGlobal.instances) !== null && _a !== void 0 ? _a : {};
30
29
  const inst = this;
@@ -40,9 +39,6 @@ class StatsigClientBase {
40
39
  this._options = options !== null && options !== void 0 ? options : {};
41
40
  this._overrideAdapter = (_b = options === null || options === void 0 ? void 0 : options.overrideAdapter) !== null && _b !== void 0 ? _b : null;
42
41
  this._logger = new EventLogger_1.EventLogger(sdkKey, emitter, network, options);
43
- SessionID_1.SessionID._setEmitFunction(() => {
44
- this._emit({ name: 'session_expired' });
45
- }, sdkKey);
46
42
  if (instances[sdkKey] != null && (0, SafeJs_1._isBrowserEnv)()) {
47
43
  Log_1.Log.warn('Creating multiple Statsig clients with the same SDK key can lead to unexpected behavior. Multi-instance support requires different SDK keys.');
48
44
  }
@@ -82,7 +78,7 @@ class StatsigClientBase {
82
78
  */
83
79
  shutdown() {
84
80
  return __awaiter(this, void 0, void 0, function* () {
85
- this._emit({ name: 'pre_shutdown' });
81
+ this.$emt({ name: 'pre_shutdown' });
86
82
  yield this._logger.shutdown();
87
83
  });
88
84
  }
@@ -115,11 +111,11 @@ class StatsigClientBase {
115
111
  }
116
112
  }
117
113
  }
118
- __on(event, listener) {
114
+ $on(event, listener) {
119
115
  listener.__isInternal = true;
120
116
  this.on(event, listener);
121
117
  }
122
- _emit(event) {
118
+ $emt(event) {
123
119
  var _a;
124
120
  const barrier = (listener) => {
125
121
  try {
@@ -127,7 +123,7 @@ class StatsigClientBase {
127
123
  }
128
124
  catch (error) {
129
125
  if (listener.__isInternal === true) {
130
- this._errorBoundary.logError(`_emit:${event.name}`, error);
126
+ this._errorBoundary.logError(`__emit:${event.name}`, error);
131
127
  return;
132
128
  }
133
129
  Log_1.Log.error(`An error occurred in a StatsigClientEvent listener. This is not an issue with Statsig.`, event);
@@ -140,7 +136,7 @@ class StatsigClientBase {
140
136
  }
141
137
  _setStatus(newStatus, values) {
142
138
  this.loadingStatus = newStatus;
143
- this._emit({ name: 'values_updated', status: newStatus, values });
139
+ this.$emt({ name: 'values_updated', status: newStatus, values });
144
140
  }
145
141
  _enqueueExposure(name, exposure, options) {
146
142
  if ((options === null || options === void 0 ? void 0 : options.disableExposureLog) === true) {
@@ -65,6 +65,10 @@ export interface StatsigClientEventEmitterInterface {
65
65
  /**
66
66
  * (Statsig Use Only) - Same as .on() but logs errors to sdk_exception
67
67
  */
68
- __on<T extends StatsigClientEventName>(event: T, listener: StatsigClientEventCallback<T>): void;
68
+ $on<T extends StatsigClientEventName>(event: T, listener: StatsigClientEventCallback<T>): void;
69
+ /**
70
+ * (Statsig Use Only) - Emit StatsigClientEvents
71
+ */
72
+ $emt(event: AnyStatsigClientEvent): void;
69
73
  }
70
74
  export {};
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "0.0.1-beta.35";
1
+ export declare const SDK_VERSION = "0.0.1-beta.37";
2
2
  export type StatsigMetadata = {
3
3
  readonly [key: string]: string | undefined;
4
4
  readonly appVersion?: string;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StatsigMetadataProvider = exports.SDK_VERSION = void 0;
4
- exports.SDK_VERSION = '0.0.1-beta.35';
4
+ exports.SDK_VERSION = '0.0.1-beta.37';
5
5
  let metadata = {
6
6
  sdkVersion: exports.SDK_VERSION,
7
7
  sdkType: 'js-mono', // js-mono is overwritten by Precomp and OnDevice clients
package/src/index.d.ts CHANGED
@@ -13,9 +13,8 @@ export * from './EvaluationTypes';
13
13
  export * from './Hashing';
14
14
  export * from './InitializeResponse';
15
15
  export * from './Log';
16
- export * from './NetworkParams';
17
16
  export * from './NetworkCore';
18
- export * from './NetworkDefaults';
17
+ export * from './NetworkConfig';
19
18
  export * from './OverrideAdapter';
20
19
  export * from './SafeJs';
21
20
  export * from './SDKType';
package/src/index.js CHANGED
@@ -35,9 +35,8 @@ __exportStar(require("./EvaluationTypes"), exports);
35
35
  __exportStar(require("./Hashing"), exports);
36
36
  __exportStar(require("./InitializeResponse"), exports);
37
37
  __exportStar(require("./Log"), exports);
38
- __exportStar(require("./NetworkParams"), exports);
39
38
  __exportStar(require("./NetworkCore"), exports);
40
- __exportStar(require("./NetworkDefaults"), exports);
39
+ __exportStar(require("./NetworkConfig"), exports);
41
40
  __exportStar(require("./OverrideAdapter"), exports);
42
41
  __exportStar(require("./SafeJs"), exports);
43
42
  __exportStar(require("./SDKType"), exports);
@@ -1,5 +0,0 @@
1
- export declare const NetworkDefault: {
2
- eventsApi: "https://events.statsigapi.net/v1";
3
- initializeApi: "https://featuregates.org/v1";
4
- specsApi: "https://api.statsigcdn.com/v1";
5
- };
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NetworkDefault = void 0;
4
- exports.NetworkDefault = {
5
- eventsApi: 'https://events.statsigapi.net/v1',
6
- initializeApi: 'https://featuregates.org/v1',
7
- specsApi: 'https://api.statsigcdn.com/v1',
8
- };
@@ -1,9 +0,0 @@
1
- export declare enum NetworkParam {
2
- EventCount = "ec",
3
- SdkKey = "k",
4
- SdkType = "st",
5
- SdkVersion = "sv",
6
- Time = "t",
7
- SessionID = "sid",
8
- StatsigEncoded = "se"
9
- }