@empirica/core
Version:
Empirica Core
1,455 lines (1,435 loc) • 39.4 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/admin/user.ts
var user_exports = {};
__export(user_exports, {
AdminConnection: () => AdminConnection,
Attribute: () => Attribute,
Attributes: () => Attributes2,
EventContext: () => EventContext,
ListenersCollector: () => ListenersCollector,
Scope: () => Scope2,
Scopes: () => Scopes2,
Subscriptions: () => Subscriptions,
TajribaConnection: () => TajribaConnection,
TajribaEvent: () => TajribaEvent,
participantsSub: () => participantsSub,
transitionsSub: () => transitionsSub
});
module.exports = __toCommonJS(user_exports);
// src/shared/attributes.ts
var import_rxjs = require("rxjs");
// src/utils/console.ts
var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
var logsMock;
var colorHex = {
[1 /* Bold */]: "font-weight: bold",
[30 /* Black */]: "color: #000000",
[31 /* Red */]: "color: #cc0000",
[32 /* Green */]: "color: #4e9a06",
[33 /* Yellow */]: "color: #c4a000",
[34 /* Blue */]: "color: #729fcf",
[35 /* Magenta */]: "color: #75507b",
[36 /* Cyan */]: "color: #06989a",
[37 /* White */]: "color: #d3d7cf",
[90 /* DarkGray */]: "color: #555753"
};
var levels = {
trace: 0,
debug: 1,
log: 2,
info: 2,
warn: 3,
error: 4
};
var reversLevels = {};
for (const key in levels) {
reversLevels[levels[key]] = key;
}
var currentLevel = 2;
function formatConsoleDate(date, level) {
var hour = date.getHours();
var minutes = date.getMinutes();
var seconds = date.getSeconds();
var milliseconds = date.getMilliseconds();
const str = (hour < 10 ? "0" + hour : hour) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + ("00" + milliseconds).slice(-3);
if (isBrowser) {
const ts = colorize(str, 90 /* DarkGray */).concat(level);
return [ts[0] + " " + level[0], ts[1], level[1]];
}
return colorize(str, 90 /* DarkGray */).concat(level);
}
var createLogger = (lvl, level) => {
return (...args) => {
if (lvl < currentLevel) {
return;
}
if (logsMock) {
logsMock.log({ level: reversLevels[lvl], args });
return;
}
if (args.length === 1) {
switch (typeof args[0]) {
case "string":
for (const line of args[0].split("\n")) {
console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line));
}
return;
case "object":
if (args[0] instanceof Error) {
const error2 = args[0];
const prettyErr = error2.name + ": " + error2.message.replace(new RegExp(`^${error2.name}[: ]*`), "") + "\n" + (error2.stack || "").split("\n").map((line) => line.trim()).map((line) => {
if (line.startsWith(error2.name + ": " + error2.message))
return null;
if (line.startsWith("at")) {
return " " + line;
}
return line;
}).filter(Boolean).join("\n");
for (const line of prettyErr.split("\n")) {
console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line));
}
return;
}
}
}
console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(args));
};
};
function colorize(s, ...cc) {
if (isBrowser) {
const attr = [];
for (const c of cc) {
attr.push(colorHex[c]);
}
return [`%c${s}`, attr.join("; ")];
}
let out = "";
for (const c of cc) {
out += `\x1B[${c}m`;
}
out += `${s}\x1B[0m`;
return [out];
}
var trace = createLogger(0, colorize("TRC", 35 /* Magenta */));
var debug = createLogger(1, colorize("DBG", 33 /* Yellow */));
var log = createLogger(2, colorize("LOG", 33 /* Yellow */));
var info = createLogger(2, colorize("INF", 32 /* Green */));
var warn = createLogger(3, colorize("WRN", 36 /* Cyan */));
var error = createLogger(4, colorize("ERR", 31 /* Red */, 1 /* Bold */));
// src/shared/attributes.ts
var Attributes = class {
constructor(attributesObs, donesObs, setAttributes) {
this.setAttributes = setAttributes;
this.attrs = /* @__PURE__ */ new Map();
this.updates = /* @__PURE__ */ new Map();
attributesObs.subscribe({
next: ({ attribute, removed }) => {
this.update(attribute, removed);
}
});
donesObs.subscribe({
next: (scopeIDs) => {
this.next(scopeIDs);
}
});
}
attribute(scopeID, key) {
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.attrs.set(scopeID, scopeMap);
}
let attr = scopeMap.get(key);
if (!attr) {
attr = new Attribute(this.setAttributes, scopeID, key);
scopeMap.set(key, attr);
}
return attr;
}
attributes(scopeID) {
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.attrs.set(scopeID, scopeMap);
}
return Array.from(scopeMap.values());
}
attributePeek(scopeID, key) {
let scopeUpdateMap = this.updates.get(scopeID);
if (scopeUpdateMap) {
const updated = scopeUpdateMap.get(key);
if (updated) {
if (typeof updated === "boolean") {
return;
} else {
if (!updated.val) {
return;
} else {
const attr2 = new Attribute(this.setAttributes, scopeID, key);
attr2._update(updated);
return attr2;
}
}
}
}
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
return;
}
let attr = scopeMap.get(key);
if (!attr) {
return;
}
if (attr.value === void 0) {
return;
}
return attr;
}
nextAttributeValue(scopeID, key) {
const attr = this.attributePeek(scopeID, key);
if (!attr) {
return;
}
return attr.value;
}
update(attr, removed) {
let nodeID = attr.nodeID;
if (!nodeID) {
if (!attr.node?.id) {
error(`new attribute without node ID`);
return;
}
nodeID = attr.node.id;
}
let scopeMap = this.updates.get(nodeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.updates.set(nodeID, scopeMap);
}
if (removed) {
scopeMap.set(attr.key, true);
} else {
let key = attr.key;
if (attr.index !== void 0 && attr.index !== null) {
key = `${key}[${attr.index}]`;
}
scopeMap.set(key, attr);
}
}
scopeWasUpdated(scopeID) {
if (!scopeID) {
return false;
}
return this.updates.has(scopeID);
}
next(scopeIDs) {
for (const [scopeID, attrs] of this.updates) {
if (!scopeIDs.includes(scopeID)) {
continue;
}
let scopeMap = this.attrs.get(scopeID);
if (!scopeMap) {
scopeMap = /* @__PURE__ */ new Map();
this.attrs.set(scopeID, scopeMap);
}
for (const [key, attrOrDel] of attrs) {
if (typeof attrOrDel === "boolean") {
let attr = scopeMap.get(key);
if (attr) {
attr._update(void 0);
}
} else {
let attr = scopeMap.get(attrOrDel.key);
if (!attr) {
attr = new Attribute(this.setAttributes, scopeID, attrOrDel.key);
scopeMap.set(attrOrDel.key, attr);
}
attr._update(attrOrDel);
}
}
}
for (const scopeID of scopeIDs) {
this.updates.delete(scopeID);
}
}
};
var Attribute = class {
constructor(setAttributes, scopeID, key) {
this.setAttributes = setAttributes;
this.scopeID = scopeID;
this.key = key;
this.val = new import_rxjs.BehaviorSubject(void 0);
}
get id() {
return this.attr?.id;
}
get createdAt() {
return this.attr ? new Date(this.attr.createdAt) : null;
}
get obs() {
return this.val;
}
get value() {
return this.val.getValue();
}
get nodeID() {
return this.scopeID;
}
// items returns the attribute changes for the current attribute, if it is a
// vector. Otherwise it returns null;
get items() {
if (!this.attrs) {
return null;
}
return this.attrs;
}
set(value, ao) {
const attrProps = this._prepSet(value, ao);
if (!attrProps) {
return;
}
this.setAttributes([attrProps]);
trace(`SET ${this.key} = ${value} (${this.scopeID})`);
}
_prepSet(value, ao, item) {
if (ao?.append !== void 0 && ao.index !== void 0) {
error(`cannot set both append and index`);
throw new Error(`cannot set both append and index`);
}
const serVal = JSON.stringify(value);
if (!item && (ao?.index !== void 0 || ao?.append)) {
let index = ao.index || 0;
if (ao?.append) {
index = this.attrs?.length || 0;
}
if (!this.attrs) {
this.attrs = [];
}
if (!this.attrs[index]) {
this.attrs[index] = new Attribute(
this.setAttributes,
this.scopeID,
this.key
);
} else {
const existing = this.attrs[index];
if (existing && existing.serVal === serVal) {
return;
}
}
this.attrs[index]._prepSet(value, ao, true);
const v = this._recalcVectorVal();
this.val.next(v);
} else {
if (this.serVal === serVal) {
return;
}
this.val.next(value);
}
this.serVal = serVal;
const attrProps = {
key: this.key,
nodeID: this.scopeID,
val: serVal
};
if (ao) {
attrProps.private = ao.private;
attrProps.protected = ao.protected;
attrProps.immutable = ao.immutable;
attrProps.ephemeral = ao.ephemeral;
attrProps.append = ao.append;
attrProps.index = ao.index;
}
return attrProps;
}
_recalcVectorVal() {
return this.attrs.map(
(a) => !a || a.val == void 0 ? null : a.value || null
);
}
// internal only
_update(attr, item) {
if (attr && this.attr && this.attr.id === attr.id) {
return;
}
if (attr && attr.vector && !item) {
if (attr.index === void 0) {
error(`vector attribute missing index`);
return;
}
if (this.attrs == void 0) {
this.attrs = [];
}
while (this.attrs.length < attr.index + 1) {
const newAttr2 = new Attribute(
this.setAttributes,
this.scopeID,
this.key
);
this.attrs.push(newAttr2);
}
const newAttr = new Attribute(this.setAttributes, this.scopeID, this.key);
newAttr._update(attr, true);
this.attrs[attr.index] = newAttr;
const value2 = this._recalcVectorVal();
this.val.next(value2);
return;
}
this.attr = attr;
this.serVal = attr?.val === void 0 || attr?.val === null ? "" : attr.val;
let value = void 0;
if (this.attr?.val) {
value = JSON.parse(this.attr.val);
}
this.val.next(value);
}
};
// src/shared/tajriba_connection.ts
var import_tajriba = require("@empirica/tajriba");
// src/utils/object.ts
var import_rxjs2 = require("rxjs");
function bs(init) {
return new import_rxjs2.BehaviorSubject(init);
}
function bsu(init = void 0) {
return new import_rxjs2.BehaviorSubject(init);
}
// src/shared/tajriba_connection.ts
var ErrNotConnected = new Error("not connected");
var TajribaConnection = class {
constructor(url) {
this.url = url;
this._connected = bs(false);
this._connecting = bs(true);
this._stopped = bs(false);
this.tajriba = import_tajriba.Tajriba.connect(this.url);
this._connected.next(this.tajriba.connected);
this.tajriba.on("connected", () => {
this._connected.next(true);
this._connecting.next(false);
});
this.tajriba.on("disconnected", () => {
if (this._connected.getValue()) {
this._connected.next(false);
}
if (!this._connecting.getValue()) {
this._connecting.next(true);
}
});
this.tajriba.on("error", (err) => {
error("connection error", err);
});
}
get connecting() {
return this._connecting;
}
get connected() {
return this._connected;
}
get stopped() {
return this._stopped;
}
async sessionParticipant(token, pident) {
if (!this._connected.getValue()) {
throw ErrNotConnected;
}
return await this.tajriba.sessionParticipant(token, pident);
}
async sessionAdmin(token) {
if (!this._connected.getValue()) {
throw ErrNotConnected;
}
return await this.tajriba.sessionAdmin(token);
}
stop() {
if (this._stopped.getValue()) {
return;
}
if (this.tajriba) {
this.tajriba.removeAllListeners("connected");
this.tajriba.removeAllListeners("disconnected");
this.tajriba.stop();
}
this._connecting.next(false);
this._connected.next(false);
this._stopped.next(true);
}
};
// src/admin/attributes.ts
var import_rxjs3 = require("rxjs");
var Attributes2 = class extends Attributes {
constructor() {
super(...arguments);
this.attrsByKind = /* @__PURE__ */ new Map();
this.attribSubs = /* @__PURE__ */ new Map();
}
subscribeAttribute(kind, key) {
if (!this.attribSubs.has(kind)) {
this.attribSubs.set(kind, /* @__PURE__ */ new Map());
}
const keyMap = this.attribSubs.get(kind);
let sub = keyMap.get(key);
if (!sub) {
sub = new import_rxjs3.ReplaySubject();
keyMap.set(key, sub);
const attrByScopeID = this.attrsByKind.get(kind);
setTimeout(() => {
if (!attrByScopeID) {
sub.next({ done: true });
return;
}
let attrs = [];
for (const [_, attrByKey] of attrByScopeID?.entries()) {
for (const [_2, attr] of attrByKey) {
if (attr.key === key) {
attrs.push(attr);
}
}
}
if (attrs.length > 0) {
let count = 0;
for (const attr of attrs) {
count++;
sub.next({ attribute: attr, done: count == attrs.length });
}
} else {
sub.next({ done: true });
}
}, 0);
}
return sub;
}
next(scopeIDs) {
const byKind = /* @__PURE__ */ new Map();
for (const [scopeID, attrs] of this.updates) {
if (!scopeIDs.includes(scopeID)) {
continue;
}
for (const [_, attr] of attrs) {
if (typeof attr === "boolean") {
continue;
}
const kind = attr.node?.kind;
if (kind) {
let kindAttrs = byKind.get(kind);
if (!kindAttrs) {
kindAttrs = [];
byKind.set(kind, kindAttrs);
}
kindAttrs.push(attr);
}
}
}
const updates = [];
for (const [kind, attrs] of byKind) {
for (const attr of attrs) {
if (!attr.nodeID && !attr.node?.id) {
warn(`found attribute change without node ID`);
continue;
}
if (!scopeIDs.includes(attr.nodeID || attr.node.id)) {
continue;
}
updates.push([kind, attr.key, attr]);
}
}
super.next(scopeIDs);
for (const [kind, key, attrChange] of updates) {
const nodeID = attrChange.nodeID || attrChange.node.id;
if (!scopeIDs.includes(nodeID)) {
continue;
}
const attr = this.attrs.get(nodeID).get(key);
const sub = this.attribSubs.get(kind)?.get(key);
if (sub) {
sub.next({ attribute: attr, done: true });
} else {
let kAttrs = this.attrsByKind.get(kind);
if (!kAttrs) {
kAttrs = /* @__PURE__ */ new Map();
this.attrsByKind.set(kind, kAttrs);
}
let kkAttrs = kAttrs.get(nodeID);
if (!kkAttrs) {
kkAttrs = /* @__PURE__ */ new Map();
kAttrs.set(nodeID, kkAttrs);
}
kkAttrs.set(key, attr);
}
}
}
};
// src/admin/connection.ts
var import_rxjs5 = require("rxjs");
// src/admin/observables.ts
var import_async_mutex = require("async-mutex");
var import_rxjs4 = require("rxjs");
function subscribeAsync(obs, fn) {
const cancel = new import_rxjs4.Subject();
obs.pipe((0, import_rxjs4.concatMap)(fn), (0, import_rxjs4.takeUntil)(cancel)).subscribe();
return {
closed: false,
unsubscribe() {
if (this.closed) {
warn("closing a closed async observable subscription");
return;
}
this.closed = true;
cancel.next();
cancel.unsubscribe();
}
};
}
// src/admin/connection.ts
var AdminConnection = class {
constructor(taj, tokens, resetToken) {
this.resetToken = resetToken;
this._tajriba = bsu();
this._connected = bs(false);
this._connecting = bs(false);
this._stopped = bs(false);
let token;
let connected = false;
this.sub = subscribeAsync(
(0, import_rxjs5.merge)(taj.connected, tokens),
async (tokenOrConnected) => {
if (typeof tokenOrConnected === "boolean") {
connected = tokenOrConnected;
} else {
token = tokenOrConnected;
}
if (!token || !connected) {
return;
}
if (this._connected.getValue()) {
return;
}
this._connecting.next(true);
try {
const tajAdmin = await taj.sessionAdmin(token);
this._tajriba.next(tajAdmin);
this._connected.next(true);
tajAdmin.on("connected", () => {
if (!this._connected.getValue()) {
this._connected.next(true);
}
});
tajAdmin.on("error", (err) => {
error("connection error", err);
});
tajAdmin.on("disconnected", () => {
if (this._connected.getValue()) {
this._connected.next(false);
}
});
tajAdmin.on("accessDenied", () => {
if (this._connected.getValue()) {
this._connected.next(false);
}
this.resetToken();
});
} catch (error2) {
if (error2 !== ErrNotConnected) {
this.resetToken();
}
}
this._connecting.next(false);
}
);
}
stop() {
if (this._stopped.getValue()) {
return;
}
const taj = this._tajriba.getValue();
if (taj) {
taj.removeAllListeners("connected");
taj.removeAllListeners("disconnected");
taj.stop();
this._tajriba.next(void 0);
}
this.sub.unsubscribe();
this._connecting.next(false);
this._connected.next(false);
this._stopped.next(true);
}
get connecting() {
return this._connecting;
}
get connected() {
return this._connected;
}
get stopped() {
return this._stopped;
}
get admin() {
return this._tajriba;
}
};
// src/admin/events.ts
var TajribaEvent = /* @__PURE__ */ ((TajribaEvent2) => {
TajribaEvent2["TransitionAdd"] = "TRANSITION_ADD";
TajribaEvent2["ParticipantConnect"] = "PARTICIPANT_CONNECT";
TajribaEvent2["ParticipantDisconnect"] = "PARTICIPANT_DISCONNECT";
return TajribaEvent2;
})(TajribaEvent || {});
var placementString = /* @__PURE__ */ new Map();
placementString.set(0 /* Before */, "before");
placementString.set(1 /* None */, "on");
placementString.set(2 /* After */, "after");
function PlacementString(placement) {
return placementString.get(placement);
}
function unique(kind, placement, callback) {
return async (ctx, props) => {
const attr = props.attribute;
const scope = props[kind];
if (!attr.id || scope.get(`ran-${PlacementString(placement)}-${props.attrId}`)) {
return;
}
await callback(ctx, props);
scope.set(`ran-${PlacementString(placement)}-${props.attrId}`, true);
};
}
var ListenersCollector = class {
constructor() {
/** @internal */
this.starts = [];
/** @internal */
this.readys = [];
/** @internal */
this.tajEvents = [];
/** @internal */
this.kindListeners = [];
/** @internal */
this.attributeListeners = [];
}
/** @internal */
setFlusher(flusher) {
this.flusher = flusher;
}
async flush() {
if (!this.flusher) {
return;
}
await this.flusher.flush();
}
flushAfter(cb) {
if (!this.flusher) {
return;
}
return this.flusher.flushAfter(cb);
}
get unique() {
return new ListenersCollectorProxy(this);
}
on(kindOrEvent, keyOrNodeIDOrEventOrCallback, callback) {
this.registerListerner(
1 /* None */,
kindOrEvent,
keyOrNodeIDOrEventOrCallback,
callback
);
}
before(kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall) {
this.registerListerner(
0 /* Before */,
kindOrEvent,
keyOrNodeIDOrEventOrCallback,
callback,
uniqueCall
);
}
after(kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall) {
this.registerListerner(
2 /* After */,
kindOrEvent,
keyOrNodeIDOrEventOrCallback,
callback,
uniqueCall
);
}
registerListerner(placement, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback, uniqueCall = false) {
if (kindOrEvent === "start") {
if (callback) {
throw new Error("start event only accepts 2 arguments");
}
if (typeof keyOrNodeIDOrEventOrCallback !== "function") {
throw new Error("second argument expected to be a callback");
}
this.starts.push({
placement,
callback: keyOrNodeIDOrEventOrCallback
});
return;
}
if (kindOrEvent === "ready") {
if (callback) {
throw new Error("ready event only accepts 2 arguments");
}
if (typeof keyOrNodeIDOrEventOrCallback !== "function") {
throw new Error("second argument expected to be a callback");
}
this.readys.push({
placement,
callback: keyOrNodeIDOrEventOrCallback
});
return;
}
if (Object.values(TajribaEvent).includes(kindOrEvent)) {
if (typeof keyOrNodeIDOrEventOrCallback !== "function") {
throw new Error("second argument expected to be a callback");
}
this.tajEvents.push({
placement,
event: kindOrEvent,
callback: keyOrNodeIDOrEventOrCallback
});
return;
}
if (typeof keyOrNodeIDOrEventOrCallback === "function") {
this.kindListeners.push({
placement,
kind: kindOrEvent,
callback: keyOrNodeIDOrEventOrCallback
});
} else {
if (typeof keyOrNodeIDOrEventOrCallback !== "string") {
throw new Error("second argument expected to be an attribute key");
}
if (typeof callback !== "function") {
throw new Error("third argument expected to be a callback");
}
if (uniqueCall) {
callback = unique(kindOrEvent, placement, callback);
}
this.attributeListeners.push({
placement,
kind: kindOrEvent,
key: keyOrNodeIDOrEventOrCallback,
callback
});
}
}
};
var ListenersCollectorProxy = class extends ListenersCollector {
constructor(coll) {
super();
this.coll = coll;
}
registerListerner(placement, kindOrEvent, keyOrNodeIDOrEventOrCallback, callback) {
if (kindOrEvent === "start" || kindOrEvent === "ready" || Object.values(TajribaEvent).includes(kindOrEvent) || typeof keyOrNodeIDOrEventOrCallback === "function") {
throw new Error("only attribute listeners can be unique");
}
super.registerListerner(
placement,
kindOrEvent,
keyOrNodeIDOrEventOrCallback,
callback,
true
);
while (true) {
const listener = this.attributeListeners.pop();
if (!listener) {
break;
}
this.coll.attributeListeners.push(listener);
}
}
};
var EventContext = class {
constructor(subs, taj, scopes, flusher) {
this.subs = subs;
this.taj = taj;
this.scopes = scopes;
this.flusher = flusher;
}
async flush() {
await this.flusher.flush();
}
flushAfter(cb) {
return this.flusher.flushAfter(cb);
}
scopesByKind(kind) {
return this.scopes.byKind(kind);
}
scopesByKindID(kind, id) {
return this.scopes.byKind(kind).get(id);
}
scopesByKindMatching(kind, key, val) {
const scopes = Array.from(this.scopes.byKind(kind).values());
return scopes.filter((s) => s.get(key) === val);
}
scopeSub(...inputs) {
for (const input of inputs) {
this.subs.scopeSub(input);
}
}
participantsSub() {
this.subs.participantsSub();
}
transitionsSub(stepID) {
this.subs.transitionsSub(stepID);
}
// c8 ignore: the TajribaAdminAccess proxy functions are tested elswhere
/* c8 ignore next 3 */
addScopes(input) {
return this.taj.addScopes(input);
}
/* c8 ignore next 3 */
addGroups(input) {
return this.taj.addGroups(input);
}
/* c8 ignore next 3 */
addLinks(input) {
return this.taj.addLinks(input);
}
/* c8 ignore next 3 */
addSteps(input) {
return this.taj.addSteps(input);
}
/* c8 ignore next 3 */
addTransitions(input) {
return this.taj.addTransitions(input);
}
addFinalizer(cb) {
this.taj.addFinalizer(cb);
}
/* c8 ignore next 3 */
get globals() {
return this.taj.globals;
}
};
// src/admin/participants.ts
var import_tajriba2 = require("@empirica/tajriba");
// src/admin/promises.ts
function promiseHandle() {
let ret = {};
ret.promise = new Promise((r) => {
ret.result = r;
});
return ret;
}
// src/admin/participants.ts
async function participantsSub(taj, connections, participants) {
let handle = promiseHandle();
taj.onEvent({ eventTypes: [import_tajriba2.EventType.ParticipantConnected] }).subscribe({
next({ node, done }) {
if (!node) {
if (done) {
if (handle) {
handle?.result();
connections.next({ done: true });
}
return;
}
error(`received no participant on connected`);
return;
}
if (node.__typename !== "Participant") {
error(`received non-participant on connected`);
return;
}
const part = {
id: node.id,
identifier: node.identifier
};
participants.set(node.id, part);
connections.next({
connection: {
participant: part,
connected: true
},
done
});
if (handle && done) {
handle.result();
}
}
});
taj.onEvent({ eventTypes: [import_tajriba2.EventType.ParticipantDisconnect] }).subscribe({
next({ node }) {
if (!node) {
error(`received no participant on disconnect`);
return;
}
if (node.__typename !== "Participant") {
error(`received non-participant on disconnect`);
return;
}
participants.delete(node.id);
connections.next({
connection: {
participant: {
id: node.id,
identifier: node.identifier
},
connected: false
},
done: true
});
}
});
await handle.promise;
handle = void 0;
}
// src/admin/scopes.ts
var import_rxjs7 = require("rxjs");
// src/shared/scopes.ts
var import_rxjs6 = require("rxjs");
var Scopes = class {
constructor(scopesObs, donesObs, ctx, kinds, attributes) {
this.ctx = ctx;
this.kinds = kinds;
this.attributes = attributes;
this.scopes = /* @__PURE__ */ new Map();
// newScopes is used to track scopes that have appeared for the first time.
this.newScopes = /* @__PURE__ */ new Map();
this.scopesByKind = /* @__PURE__ */ new Map();
this.kindUpdated = /* @__PURE__ */ new Set();
scopesObs.subscribe({
next: ({ scope, removed }) => {
this.update(scope, removed);
}
});
donesObs.subscribe({
next: (scopeIDs) => {
this.next(scopeIDs);
}
});
}
scope(id) {
return this.scopes.get(id)?.getValue();
}
scopeObs(id) {
return this.scopes.get(id);
}
byKind(kind) {
let map = this.scopesByKind.get(kind);
if (!map) {
map = /* @__PURE__ */ new Map();
this.scopesByKind.set(kind, map);
}
return map;
}
kindWasUpdated(kind) {
return this.kindUpdated.has(kind);
}
next(scopeIDs) {
this.kindUpdated.clear();
for (const [_, scopeSubject] of this.scopes) {
const scope = scopeSubject.getValue();
if ((scope._updated || this.attributes.scopeWasUpdated(scope.id)) && scopeIDs.includes(scope.id)) {
scope._updated = false;
scopeSubject.next(scope);
}
}
}
update(scope, removed) {
const existing = this.scopes.get(scope.id)?.getValue();
if (removed) {
if (!existing) {
warn("scopes: missing scope on removal", scope.id, scope.kind);
return;
}
existing._deleted = true;
existing._updated = true;
this.scopes.delete(scope.id);
if (!scope.kind) {
warn("scopes: scope missing kind on scope on removal");
return;
}
const kind2 = scope.kind;
this.scopesByKind.get(kind2).delete(scope.id);
this.kindUpdated.add(kind2);
return;
}
if (existing) {
existing._deleted = false;
return;
}
if (!scope.kind) {
warn("scopes: scope missing kind on scope");
return;
}
const kind = scope.kind;
const scopeClass = this.kinds[kind];
if (!scopeClass) {
warn(`scopes: unknown scope kind: ${scope.kind}`);
return;
}
const obj = this.create(scopeClass, scope);
const subj = new import_rxjs6.BehaviorSubject(obj);
this.scopes.set(scope.id, subj);
this.newScopes.set(scope.id, true);
let skm = this.scopesByKind.get(kind);
if (!skm) {
skm = /* @__PURE__ */ new Map();
this.scopesByKind.set(kind, skm);
}
skm.set(scope.id, obj);
obj._updated = true;
this.kindUpdated.add(kind);
}
create(scopeClass, scope) {
return new scopeClass(this.ctx, scope, this.attributes);
}
};
var Scope = class {
constructor(ctx, scope, attributes) {
this.ctx = ctx;
this.scope = scope;
this.attributes = attributes;
/**
* @internal
*/
this._deleted = false;
/**
* @internal
*/
this._updated = false;
}
get id() {
return this.scope.id;
}
/**
* @internal
*/
get kind() {
return this.scope.kind;
}
get(key) {
return this.attributes.attribute(this.scope.id, key).value;
}
getAttribute(key) {
return this.attributes.attribute(this.scope.id, key);
}
obs(key) {
return this.attributes.attribute(this.scope.id, key).obs;
}
set(keyOrAttributes, value, ao) {
if (typeof keyOrAttributes === "string") {
if (value === void 0) {
value = null;
}
return this.attributes.attribute(this.scope.id, keyOrAttributes).set(value, ao);
}
const nextProps = [];
for (const attr of keyOrAttributes) {
const at = this.attributes.attribute(this.scope.id, attr.key)._prepSet(attr.value, attr.ao);
if (!at) {
continue;
}
nextProps.push(at);
}
if (nextProps.length === 0) {
return;
}
this.attributes.setAttributes(nextProps);
}
append(key, value, ao) {
if (!ao) {
ao = {};
}
ao.append = true;
return this.attributes.attribute(this.scope.id, key).set(value, ao);
}
inspect() {
const attrs = this.attributes.attributes(this.scope.id);
const out = {};
for (const attr of attrs) {
out[attr.key] = attr.value;
}
return out;
}
/**
* @internal
*/
hasUpdated() {
return this._updated || this.attributes.scopeWasUpdated(this.id);
}
};
// src/admin/scopes.ts
var Scopes2 = class extends Scopes {
constructor(scopesObs, donesObs, ctx, kinds, attributes, taj) {
super(scopesObs, donesObs, ctx, kinds, attributes);
this.taj = taj;
this.kindSubs = /* @__PURE__ */ new Map();
}
/** @internal */
subscribeKind(kind) {
let sub = this.kindSubs.get(kind);
if (!sub) {
sub = new import_rxjs7.ReplaySubject();
this.kindSubs.set(kind, sub);
const scopes = this.byKind(kind);
setTimeout(() => {
if (scopes.size === 0) {
sub.next({ done: true });
return;
}
let count = 0;
for (const [_, scope] of scopes) {
count++;
sub.next({ scope, done: scopes.size === count });
}
}, 0);
}
return sub;
}
next(scopeIDs) {
for (const [_, scopeReplaySubject] of this.scopes) {
const scope = scopeReplaySubject.getValue();
if (this.newScopes.get(scope.id) && scopeIDs.includes(scope.id)) {
const kindSub = this.kindSubs.get(scope.kind);
if (kindSub) {
kindSub.next({ scope, done: true });
}
this.newScopes.set(scope.id, false);
}
}
super.next(scopeIDs);
}
create(scopeClass, scope) {
return new scopeClass(this.ctx, scope, this, this.attributes);
}
};
var Scope2 = class extends Scope {
constructor(ctx, scope, scopes, attributes) {
super(ctx, scope, attributes);
this.scopes = scopes;
this.taj = scopes.taj;
}
scopeByID(id) {
return this.scopes.scope(id);
}
scopeByKey(key) {
const id = this.get(key);
if (!id || typeof id !== "string") {
return;
}
return this.scopes.scope(id);
}
scopesByKind(kind) {
return this.scopes.byKind(kind);
}
scopesByKindID(kind, id) {
return this.scopes.byKind(kind).get(id);
}
scopesByKindMatching(kind, key, val) {
const scopes = Array.from(this.scopes.byKind(kind).values());
return scopes.filter((s) => s.get(key) === val);
}
addScopes(input) {
return this.taj.addScopes(input);
}
addGroups(input) {
return this.taj.addGroups(input);
}
addLinks(input) {
return this.taj.addLinks(input);
}
addSteps(input) {
return this.taj.addSteps(input);
}
addTransitions(input) {
return this.taj.addTransitions(input);
}
addFinalizer(cb) {
this.taj.addFinalizer(cb);
}
/**
* @internal
*/
get globals() {
return this.taj.globals;
}
};
// src/admin/subscriptions.ts
function kvstr(kv) {
return kv.key + "-" + kv.val;
}
var Subscriptions = class {
constructor() {
this.scopeKinds = /* @__PURE__ */ new Set();
this.scopeIDs = /* @__PURE__ */ new Set();
this.scopeNames = /* @__PURE__ */ new Set();
this.scopeKeys = /* @__PURE__ */ new Set();
this.scopeKVSet = /* @__PURE__ */ new Set();
this.scopeKVs = [];
this.participantSub = false;
this.transitionsSubs = /* @__PURE__ */ new Set();
this.dirty = false;
this.last = {
participants: false,
scopes: {
ids: [],
kinds: [],
names: [],
keys: [],
kvs: []
},
transitions: []
};
}
get subs() {
return {
participants: this.participantSub,
scopes: {
kinds: Array.from(this.scopeKinds.values()),
ids: Array.from(this.scopeIDs.values()),
names: Array.from(this.scopeNames.values()),
keys: Array.from(this.scopeKeys.values()),
kvs: [...this.scopeKVs]
},
transitions: Array.from(this.transitionsSubs.values())
};
}
// newSubs will return only new subs since the last call.
newSubs() {
if (!this.dirty) {
return;
}
const current = this.subs;
const {
scopes: { ids, kinds, names, keys, kvs },
participants,
transitions
} = this.last;
const kvsstrs = kvs.map((kv) => kvstr(kv));
const next = {
participants: this.participantSub && !participants,
scopes: {
ids: current.scopes.ids.filter((id) => !ids.includes(id)),
kinds: current.scopes.kinds.filter((kind) => !kinds.includes(kind)),
names: current.scopes.names.filter((name) => !names.includes(name)),
keys: current.scopes.keys.filter((key) => !keys.includes(key)),
kvs: current.scopes.kvs.filter((kv) => !kvsstrs.includes(kvstr(kv)))
},
transitions: current.transitions.filter(
(id) => !transitions.includes(id)
)
};
this.last = current;
this.dirty = false;
return next;
}
scopeSub(input) {
if (input.ids) {
for (const id of input.ids) {
if (!this.scopeIDs.has(id)) {
this.scopeIDs.add(id);
this.dirty = true;
}
}
}
if (input.kinds) {
for (const id of input.kinds) {
if (!this.scopeKinds.has(id)) {
this.scopeKinds.add(id);
this.dirty = true;
}
}
}
if (input.names) {
for (const name of input.names) {
if (!this.scopeNames.has(name)) {
this.scopeNames.add(name);
this.dirty = true;
}
}
}
if (input.keys) {
for (const key of input.keys) {
if (!this.scopeKeys.has(key)) {
this.scopeKeys.add(key);
this.dirty = true;
}
}
}
if (input.kvs) {
for (const kv of input.kvs) {
const kvKey = kvstr(kv);
if (!this.scopeKVSet.has(kvKey)) {
this.scopeKVSet.add(kvKey);
this.scopeKVs.push(kv);
this.dirty = true;
}
}
}
}
participantsSub() {
if (!this.participantSub) {
this.dirty = true;
this.participantSub = true;
}
}
transitionsSub(nodeID) {
if (!this.transitionsSubs.has(nodeID)) {
this.transitionsSubs.add(nodeID);
this.dirty = true;
}
}
};
// src/admin/transitions.ts
var import_tajriba3 = require("@empirica/tajriba");
function transitionsSub(taj, transitions, nodeID) {
taj.onEvent({ eventTypes: [import_tajriba3.EventType.TransitionAdd], nodeID }).subscribe({
next({ node }) {
if (!node) {
return;
}
if (node.__typename !== "Transition") {
error(`received non-transition`);
return;
}
if (node.node.__typename !== "Step") {
error(`received non-step transition`, node.node);
return;
}
transitions.next({
id: node.id,
to: node.to,
from: node.from,
step: {
id: node.node.id,
duration: node.node.duration,
state: node.node.state
}
});
}
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AdminConnection,
Attribute,
Attributes,
EventContext,
ListenersCollector,
Scope,
Scopes,
Subscriptions,
TajribaConnection,
TajribaEvent,
participantsSub,
transitionsSub
});
//# sourceMappingURL=user.cjs.map