@juki-team/commons 0.1.41 → 0.1.43

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.
@@ -582,6 +582,10 @@ exports.WebSocketMessageEvent = void 0;
582
582
  WebSocketMessageEvent["CLIENT_TRACK_DEVICE"] = "CLIENT_TRACK_DEVICE";
583
583
  WebSocketMessageEvent["CHAT_COMPLETIONS"] = "CHAT_COMPLETIONS";
584
584
  })(exports.WebSocketMessageEvent || (exports.WebSocketMessageEvent = {}));
585
+ /**
586
+ * Subscriptions of the client. With the TOPIC channels they are not sent to the server anymore:
587
+ * they only say which channel the client attaches (`getChannelOfWebSocketSubscribeEvent`) and the dispatch key.
588
+ */
585
589
  exports.WebSocketSubscriptionEvent = void 0;
586
590
  (function (WebSocketSubscriptionEvent) {
587
591
  WebSocketSubscriptionEvent["SUBSCRIBE_CODE_RUN_STATUS"] = "SUBSCRIBE_CODE_RUN_STATUS";
@@ -600,6 +604,9 @@ exports.WebSocketSubscriptionEvent = void 0;
600
604
  WebSocketSubscriptionEvent["UNSUBSCRIBE_CLIENT_TRACK"] = "UNSUBSCRIBE_CLIENT_TRACK";
601
605
  WebSocketSubscriptionEvent["SUBSCRIBE_USER_NOTIFICATION"] = "SUBSCRIBE_USER_NOTIFICATION";
602
606
  WebSocketSubscriptionEvent["UNSUBSCRIBE_USER_NOTIFICATION"] = "UNSUBSCRIBE_USER_NOTIFICATION";
607
+ // an administrator watching the data (screenshots, location, device) sent by another client
608
+ WebSocketSubscriptionEvent["SUBSCRIBE_CLIENT_TRACK_DATA"] = "SUBSCRIBE_CLIENT_TRACK_DATA";
609
+ WebSocketSubscriptionEvent["UNSUBSCRIBE_CLIENT_TRACK_DATA"] = "UNSUBSCRIBE_CLIENT_TRACK_DATA";
603
610
  })(exports.WebSocketSubscriptionEvent || (exports.WebSocketSubscriptionEvent = {}));
604
611
  exports.WebSocketResponseEvent = void 0;
605
612
  (function (WebSocketResponseEvent) {
@@ -623,6 +630,20 @@ exports.WebSocketResponseEvent = void 0;
623
630
  WebSocketResponseEvent["USER_NOTIFICATION_SUBMISSION"] = "USER_NOTIFICATION_SUBMISSION";
624
631
  WebSocketResponseEvent["USER_NOTIFICATION_CONTEST_CLARIFICATION"] = "USER_NOTIFICATION_CONTEST_CLARIFICATION";
625
632
  })(exports.WebSocketResponseEvent || (exports.WebSocketResponseEvent = {}));
633
+ /**
634
+ * Roles of a contest, with the same names as the contest members in the DB.
635
+ * Each role has its own Ably channel: `contest:{contestKey}:{role}` (see `CHANNEL_CONTEST`).
636
+ * The inclusions follow `getContestAccessPermissions`:
637
+ * spectators ⊇ managers ⊇ administrators, spectators ⊇ participants, spectators ⊇ guests
638
+ */
639
+ exports.ContestChannelRole = void 0;
640
+ (function (ContestChannelRole) {
641
+ ContestChannelRole["ADMINISTRATORS"] = "administrators";
642
+ ContestChannelRole["MANAGERS"] = "managers";
643
+ ContestChannelRole["PARTICIPANTS"] = "participants";
644
+ ContestChannelRole["GUESTS"] = "guests";
645
+ ContestChannelRole["SPECTATORS"] = "spectators";
646
+ })(exports.ContestChannelRole || (exports.ContestChannelRole = {}));
626
647
 
627
648
  exports.DataViewMode = void 0;
628
649
  (function (DataViewMode) {
@@ -2223,11 +2244,56 @@ const EMPTY_USER_PERMISSIONS = {
2223
2244
  },
2224
2245
  };
2225
2246
 
2247
+ // LEGACY channels (fan-out per client through the subscriptions registry in Redis)
2248
+ /** @deprecated the subscriptions registry was replaced by the TOPIC channels granted in the Ably token */
2226
2249
  const CHANNEL_PUBLISH_SUBSCRIPTIONS = 'pub:subscriptions';
2250
+ /** @deprecated not used, the events arrive by the TOPIC channels */
2227
2251
  const CHANNEL_SUBSCRIBE_NOTIFICATIONS = 'sub:notifications';
2252
+ /** @deprecated the events arrive by the TOPIC channels */
2228
2253
  const CHANNEL_SUBSCRIBE_CLIENT = (clientId) => `sub:client:${clientId}`;
2254
+ /** messages from the clients to the backend (chat completions, client track screenshots) */
2229
2255
  const CHANNEL_PUBLISH_MESSAGES = 'pub:messages';
2256
+ /** @deprecated global presence channel (limited to 200 members by Ably), replaced by CHANNEL_COMPANY_PRESENCE */
2230
2257
  const CHANNEL_PRESENCE_CLIENT = 'pre:client';
2258
+ // TOPIC channels
2259
+ // The backend publishes each event once on the channel of its topic and Ably does the fan-out.
2260
+ // Who can subscribe to each channel is decided by the API in the Ably token (capability), never by the client.
2261
+ // Dynamic segments are encoded so a key can never inject a `:` (the Ably segment separator used by the wildcards).
2262
+ const segment = (value) => encodeURIComponent(value);
2263
+ /** private: events of one session (code runs, requested data, client track commands) */
2264
+ const CHANNEL_SESSION = (sessionId) => `session:${segment(sessionId)}`;
2265
+ /** private: events of one user (own submissions while the contest is frozen, notifications) */
2266
+ const CHANNEL_USER = (userId) => `user:${segment(userId)}`;
2267
+ /** public: status of a submission when it is visible to everyone */
2268
+ const CHANNEL_SUBMISSION = (submitId) => `submission:${segment(submitId)}`;
2269
+ /** public: changes of a problem */
2270
+ const CHANNEL_PROBLEM = (problemKey) => `problem:${segment(problemKey)}`;
2271
+ /** per role: events of a contest for one role (and the roles above it, see `ContestChannelRole`) */
2272
+ const CHANNEL_CONTEST = (contestKey, role) => `contest:${segment(contestKey)}:${role}`;
2273
+ /** system administrators only: data sent by a tracked client (screenshots, location, device) */
2274
+ const CHANNEL_CLIENT_TRACK = (trackedClientId) => `client-track:${segment(trackedClientId)}`;
2275
+ /**
2276
+ * presence of the clients (tabs) of the logged users of a company (by its key), data: `UserPresenceData`.
2277
+ * The clients only enter (capability `presence`), only the administrators of the company receive it
2278
+ * (capability `presence-subscribe`). Ably limits the presence members per channel (200 by default,
2279
+ * more with server-side batching enabled on the `presence` namespace).
2280
+ * The identity of a member is its `clientId` (`sessionId|uiId`, verified by Ably), never its data.
2281
+ */
2282
+ const CHANNEL_COMPANY_PRESENCE = (companyKey) => `presence:company:${segment(companyKey)}`;
2283
+ // capability resources (wildcards) of the public channels
2284
+ const CHANNEL_SUBMISSION_ALL = 'submission:*';
2285
+ const CHANNEL_PROBLEM_ALL = 'problem:*';
2286
+ const CHANNEL_CLIENT_TRACK_ALL = 'client-track:*';
2287
+ /**
2288
+ * Returns the contest and role of a `CHANNEL_CONTEST` channel name, or null if it is not one.
2289
+ */
2290
+ const getContestChannelParams = (channel) => {
2291
+ const [namespace, contestKey, role, ...rest] = channel.split(':');
2292
+ if (namespace !== 'contest' || !contestKey || rest.length || !Object.values(exports.ContestChannelRole).includes(role)) {
2293
+ return null;
2294
+ }
2295
+ return { contestKey: decodeURIComponent(contestKey), role: role };
2296
+ };
2231
2297
 
2232
2298
  const NEW_PAGE_SHEET = () => ({
2233
2299
  id: uuid.v4(),
@@ -3010,6 +3076,16 @@ const isUnsubscribeUserNotificationWebSocketEventDTO = (event) => {
3010
3076
  return isWebsocketSubscription(event)
3011
3077
  && event?.event === exports.WebSocketSubscriptionEvent.UNSUBSCRIBE_USER_NOTIFICATION;
3012
3078
  };
3079
+ const isSubscribeClientTrackDataWebSocketEventDTO = (event) => {
3080
+ return isWebsocketSubscription(event)
3081
+ && event?.event === exports.WebSocketSubscriptionEvent.SUBSCRIBE_CLIENT_TRACK_DATA
3082
+ && typeof event?.trackedClientId === 'string' && !!event.trackedClientId;
3083
+ };
3084
+ const isUnsubscribeClientTrackDataWebSocketEventDTO = (event) => {
3085
+ return isWebsocketSubscription(event)
3086
+ && event?.event === exports.WebSocketSubscriptionEvent.UNSUBSCRIBE_CLIENT_TRACK_DATA
3087
+ && typeof event?.trackedClientId === 'string' && !!event.trackedClientId;
3088
+ };
3013
3089
  // is WebSocketResponseEventDTO, CHANNEL_SUBSCRIBE_CLIENT
3014
3090
  const isWebSocketResponseEventDTO = (event) => {
3015
3091
  return Object.values(exports.WebSocketResponseEvent).includes(event?.event)
@@ -3123,6 +3199,73 @@ const isUserNotificationContestClarificationWebSocketResponseEventDTO = (event)
3123
3199
  const getWebSocketResponseEventKey = (event, clientId, id) => {
3124
3200
  return `${event}-${clientId}-${id}`;
3125
3201
  };
3202
+ const isWebSocketChannelsMessageDTO = (message) => {
3203
+ return Array.isArray(message?.channels)
3204
+ && message.channels.every((channel) => typeof channel === 'string' && !!channel)
3205
+ && Object.values(exports.WebSocketResponseEvent).includes(message?.event?.event);
3206
+ };
3207
+ /**
3208
+ * Key used by the client to dispatch a response received on a TOPIC channel to its subscribers.
3209
+ * It is the same key that `getKeyWebSocketEventDTO` (base-ui) computes for the subscription.
3210
+ * Returns null when the response is not addressed to this client.
3211
+ */
3212
+ const getWebSocketResponseEventKeyOfResponse = (response, clientId) => {
3213
+ if (isCodeRunStatusMessageWebSocketResponseEventDTO(response)) {
3214
+ return getWebSocketResponseEventKey(exports.WebSocketResponseEvent.CODE_RUN_STATUS, clientId, response.runId);
3215
+ }
3216
+ if (isSubmissionRunStatusMessageWebSocketResponseEventDTO(response)) {
3217
+ return getWebSocketResponseEventKey(exports.WebSocketResponseEvent.SUBMISSION_RUN_STATUS, clientId, response.submitId);
3218
+ }
3219
+ if (isSendDataWebSocketResponseEventDTO(response)) {
3220
+ return getWebSocketResponseEventKey(exports.WebSocketResponseEvent.SEND_DATA_, clientId, response.dataId);
3221
+ }
3222
+ if (isContestChangesWebSocketResponseEventDTO(response)) {
3223
+ return getWebSocketResponseEventKey(exports.WebSocketResponseEvent.CONTEST_CHANGES, clientId, response.content.contestKey);
3224
+ }
3225
+ if (isProblemCrawledWebSocketResponseEventDTO(response)) {
3226
+ return getWebSocketResponseEventKey(exports.WebSocketResponseEvent.PROBLEM_CRAWLED, clientId, response.content.problemKey);
3227
+ }
3228
+ if (isSubmissionsCrawlWebSocketResponseEventDTO(response)) {
3229
+ return getWebSocketResponseEventKey(exports.WebSocketResponseEvent.SUBMISSIONS_CRAWL, clientId, join([response.content.contestKey, response.content.problemKeys]));
3230
+ }
3231
+ if (isClientTrackWebSocketResponseEventDTO(response)) {
3232
+ // the command is sent to the session channel, `key` holds the targeted client
3233
+ return response.key === clientId ? getWebSocketResponseEventKey(exports.WebSocketResponseEvent.CLIENT_TRACK, clientId, '*') : null;
3234
+ }
3235
+ if (isUserNotificationWebSocketResponseEventDTO(response)) {
3236
+ return getWebSocketResponseEventKey(exports.WebSocketResponseEvent.USER_NOTIFICATION_, clientId, '*');
3237
+ }
3238
+ return null;
3239
+ };
3240
+ /**
3241
+ * TOPIC channel that the client has to attach to receive the events of a subscription,
3242
+ * or null when the events arrive by the private channels (session / user) the client is always attached to.
3243
+ * Ably rejects the attach if the token has no capability for the channel.
3244
+ */
3245
+ const getChannelOfWebSocketSubscribeEvent = (event) => {
3246
+ if (isSubscribeSubmissionRunStatusWebSocketEventDTO(event)) {
3247
+ return CHANNEL_SUBMISSION(event.submitId);
3248
+ }
3249
+ if (isSubscribeProblemCrawledWebSocketEventDTO(event)) {
3250
+ return CHANNEL_PROBLEM(event.problemKey);
3251
+ }
3252
+ if (isSubscribeContestChangesWebSocketEventDTO(event) || isSubscribeSubmissionsCrawlWebSocketEventDTO(event)) {
3253
+ return CHANNEL_CONTEST(event.contestKey, exports.ContestChannelRole.SPECTATORS);
3254
+ }
3255
+ if (isSubscribeClientTrackDataWebSocketEventDTO(event)) {
3256
+ return CHANNEL_CLIENT_TRACK(event.trackedClientId);
3257
+ }
3258
+ return null;
3259
+ };
3260
+ /**
3261
+ * Contest whose role channels the client needs (the API grants them when the token is requested with this contest).
3262
+ */
3263
+ const getContestKeyOfWebSocketSubscribeEvent = (event) => {
3264
+ if (isSubscribeContestChangesWebSocketEventDTO(event) || isSubscribeSubmissionsCrawlWebSocketEventDTO(event)) {
3265
+ return event.contestKey;
3266
+ }
3267
+ return null;
3268
+ };
3126
3269
 
3127
3270
  function splitTime(timeRemaining) {
3128
3271
  const remaining = [];
@@ -3350,11 +3493,21 @@ exports.ACCEPTED_PROGRAMMING_LANGUAGES = ACCEPTED_PROGRAMMING_LANGUAGES;
3350
3493
  exports.ASPECT_RATIO = ASPECT_RATIO;
3351
3494
  exports.AssignmentState = AssignmentState;
3352
3495
  exports.AssignmentType = AssignmentType;
3496
+ exports.CHANNEL_CLIENT_TRACK = CHANNEL_CLIENT_TRACK;
3497
+ exports.CHANNEL_CLIENT_TRACK_ALL = CHANNEL_CLIENT_TRACK_ALL;
3498
+ exports.CHANNEL_COMPANY_PRESENCE = CHANNEL_COMPANY_PRESENCE;
3499
+ exports.CHANNEL_CONTEST = CHANNEL_CONTEST;
3353
3500
  exports.CHANNEL_PRESENCE_CLIENT = CHANNEL_PRESENCE_CLIENT;
3501
+ exports.CHANNEL_PROBLEM = CHANNEL_PROBLEM;
3502
+ exports.CHANNEL_PROBLEM_ALL = CHANNEL_PROBLEM_ALL;
3354
3503
  exports.CHANNEL_PUBLISH_MESSAGES = CHANNEL_PUBLISH_MESSAGES;
3355
3504
  exports.CHANNEL_PUBLISH_SUBSCRIPTIONS = CHANNEL_PUBLISH_SUBSCRIPTIONS;
3505
+ exports.CHANNEL_SESSION = CHANNEL_SESSION;
3506
+ exports.CHANNEL_SUBMISSION = CHANNEL_SUBMISSION;
3507
+ exports.CHANNEL_SUBMISSION_ALL = CHANNEL_SUBMISSION_ALL;
3356
3508
  exports.CHANNEL_SUBSCRIBE_CLIENT = CHANNEL_SUBSCRIBE_CLIENT;
3357
3509
  exports.CHANNEL_SUBSCRIBE_NOTIFICATIONS = CHANNEL_SUBSCRIBE_NOTIFICATIONS;
3510
+ exports.CHANNEL_USER = CHANNEL_USER;
3358
3511
  exports.CODE_LANGUAGE = CODE_LANGUAGE;
3359
3512
  exports.COMPANY_PLAN = COMPANY_PLAN;
3360
3513
  exports.CONTEST_ROLE = CONTEST_ROLE;
@@ -3459,8 +3612,11 @@ exports.contentsResponse = contentsResponse;
3459
3612
  exports.errorsResponse = errorsResponse;
3460
3613
  exports.getAssignmentCommentKey = getAssignmentCommentKey;
3461
3614
  exports.getAssignmentUserCommentKey = getAssignmentUserCommentKey;
3615
+ exports.getChannelOfWebSocketSubscribeEvent = getChannelOfWebSocketSubscribeEvent;
3462
3616
  exports.getClientId = getClientId;
3617
+ exports.getContestChannelParams = getContestChannelParams;
3463
3618
  exports.getContestCommentKey = getContestCommentKey;
3619
+ exports.getContestKeyOfWebSocketSubscribeEvent = getContestKeyOfWebSocketSubscribeEvent;
3464
3620
  exports.getDataLog = getDataLog;
3465
3621
  exports.getDataOfTestCase = getDataOfTestCase;
3466
3622
  exports.getDays = getDays;
@@ -3482,6 +3638,7 @@ exports.getTotalExercisesOfSummaryWorksheetsInPages = getTotalExercisesOfSummary
3482
3638
  exports.getUserKey = getUserKey;
3483
3639
  exports.getVerdictFromTestCase = getVerdictFromTestCase;
3484
3640
  exports.getWebSocketResponseEventKey = getWebSocketResponseEventKey;
3641
+ exports.getWebSocketResponseEventKeyOfResponse = getWebSocketResponseEventKeyOfResponse;
3485
3642
  exports.getWorksheetsInPages = getWorksheetsInPages;
3486
3643
  exports.getYears = getYears;
3487
3644
  exports.humanFileSize = humanFileSize;
@@ -3520,6 +3677,7 @@ exports.isSendDataWebSocketResponseEventDTO = isSendDataWebSocketResponseEventDT
3520
3677
  exports.isStringJson = isStringJson;
3521
3678
  exports.isSubmissionRunStatusMessageWebSocketResponseEventDTO = isSubmissionRunStatusMessageWebSocketResponseEventDTO;
3522
3679
  exports.isSubmissionsCrawlWebSocketResponseEventDTO = isSubmissionsCrawlWebSocketResponseEventDTO;
3680
+ exports.isSubscribeClientTrackDataWebSocketEventDTO = isSubscribeClientTrackDataWebSocketEventDTO;
3523
3681
  exports.isSubscribeClientTrackWebSocketEventDTO = isSubscribeClientTrackWebSocketEventDTO;
3524
3682
  exports.isSubscribeCodeRunStatusWebSocketEventDTO = isSubscribeCodeRunStatusWebSocketEventDTO;
3525
3683
  exports.isSubscribeContestChangesWebSocketEventDTO = isSubscribeContestChangesWebSocketEventDTO;
@@ -3529,6 +3687,7 @@ exports.isSubscribeSubmissionRunStatusWebSocketEventDTO = isSubscribeSubmissionR
3529
3687
  exports.isSubscribeSubmissionsCrawlWebSocketEventDTO = isSubscribeSubmissionsCrawlWebSocketEventDTO;
3530
3688
  exports.isSubscribeUserNotificationWebSocketEventDTO = isSubscribeUserNotificationWebSocketEventDTO;
3531
3689
  exports.isTeamMember = isTeamMember;
3690
+ exports.isUnsubscribeClientTrackDataWebSocketEventDTO = isUnsubscribeClientTrackDataWebSocketEventDTO;
3532
3691
  exports.isUnsubscribeClientTrackWebSocketEventDTO = isUnsubscribeClientTrackWebSocketEventDTO;
3533
3692
  exports.isUnsubscribeCodeRunStatusWebSocketEventDTO = isUnsubscribeCodeRunStatusWebSocketEventDTO;
3534
3693
  exports.isUnsubscribeContestChangesWebSocketEventDTO = isUnsubscribeContestChangesWebSocketEventDTO;
@@ -3542,6 +3701,7 @@ exports.isUserMessageWebSocketResponseEventDTO = isUserMessageWebSocketResponseE
3542
3701
  exports.isUserNotificationContestClarificationWebSocketResponseEventDTO = isUserNotificationContestClarificationWebSocketResponseEventDTO;
3543
3702
  exports.isUserNotificationSubmissionWebSocketResponseEventDTO = isUserNotificationSubmissionWebSocketResponseEventDTO;
3544
3703
  exports.isUserNotificationWebSocketResponseEventDTO = isUserNotificationWebSocketResponseEventDTO;
3704
+ exports.isWebSocketChannelsMessageDTO = isWebSocketChannelsMessageDTO;
3545
3705
  exports.isWebSocketResponseEventDTO = isWebSocketResponseEventDTO;
3546
3706
  exports.isWebsocketSubscription = isWebsocketSubscription;
3547
3707
  exports.isWorksheetAssignment = isWorksheetAssignment;
package/dist/esm/index.js CHANGED
@@ -580,6 +580,10 @@ var WebSocketMessageEvent;
580
580
  WebSocketMessageEvent["CLIENT_TRACK_DEVICE"] = "CLIENT_TRACK_DEVICE";
581
581
  WebSocketMessageEvent["CHAT_COMPLETIONS"] = "CHAT_COMPLETIONS";
582
582
  })(WebSocketMessageEvent || (WebSocketMessageEvent = {}));
583
+ /**
584
+ * Subscriptions of the client. With the TOPIC channels they are not sent to the server anymore:
585
+ * they only say which channel the client attaches (`getChannelOfWebSocketSubscribeEvent`) and the dispatch key.
586
+ */
583
587
  var WebSocketSubscriptionEvent;
584
588
  (function (WebSocketSubscriptionEvent) {
585
589
  WebSocketSubscriptionEvent["SUBSCRIBE_CODE_RUN_STATUS"] = "SUBSCRIBE_CODE_RUN_STATUS";
@@ -598,6 +602,9 @@ var WebSocketSubscriptionEvent;
598
602
  WebSocketSubscriptionEvent["UNSUBSCRIBE_CLIENT_TRACK"] = "UNSUBSCRIBE_CLIENT_TRACK";
599
603
  WebSocketSubscriptionEvent["SUBSCRIBE_USER_NOTIFICATION"] = "SUBSCRIBE_USER_NOTIFICATION";
600
604
  WebSocketSubscriptionEvent["UNSUBSCRIBE_USER_NOTIFICATION"] = "UNSUBSCRIBE_USER_NOTIFICATION";
605
+ // an administrator watching the data (screenshots, location, device) sent by another client
606
+ WebSocketSubscriptionEvent["SUBSCRIBE_CLIENT_TRACK_DATA"] = "SUBSCRIBE_CLIENT_TRACK_DATA";
607
+ WebSocketSubscriptionEvent["UNSUBSCRIBE_CLIENT_TRACK_DATA"] = "UNSUBSCRIBE_CLIENT_TRACK_DATA";
601
608
  })(WebSocketSubscriptionEvent || (WebSocketSubscriptionEvent = {}));
602
609
  var WebSocketResponseEvent;
603
610
  (function (WebSocketResponseEvent) {
@@ -621,6 +628,20 @@ var WebSocketResponseEvent;
621
628
  WebSocketResponseEvent["USER_NOTIFICATION_SUBMISSION"] = "USER_NOTIFICATION_SUBMISSION";
622
629
  WebSocketResponseEvent["USER_NOTIFICATION_CONTEST_CLARIFICATION"] = "USER_NOTIFICATION_CONTEST_CLARIFICATION";
623
630
  })(WebSocketResponseEvent || (WebSocketResponseEvent = {}));
631
+ /**
632
+ * Roles of a contest, with the same names as the contest members in the DB.
633
+ * Each role has its own Ably channel: `contest:{contestKey}:{role}` (see `CHANNEL_CONTEST`).
634
+ * The inclusions follow `getContestAccessPermissions`:
635
+ * spectators ⊇ managers ⊇ administrators, spectators ⊇ participants, spectators ⊇ guests
636
+ */
637
+ var ContestChannelRole;
638
+ (function (ContestChannelRole) {
639
+ ContestChannelRole["ADMINISTRATORS"] = "administrators";
640
+ ContestChannelRole["MANAGERS"] = "managers";
641
+ ContestChannelRole["PARTICIPANTS"] = "participants";
642
+ ContestChannelRole["GUESTS"] = "guests";
643
+ ContestChannelRole["SPECTATORS"] = "spectators";
644
+ })(ContestChannelRole || (ContestChannelRole = {}));
624
645
 
625
646
  var DataViewMode;
626
647
  (function (DataViewMode) {
@@ -2221,11 +2242,56 @@ const EMPTY_USER_PERMISSIONS = {
2221
2242
  },
2222
2243
  };
2223
2244
 
2245
+ // LEGACY channels (fan-out per client through the subscriptions registry in Redis)
2246
+ /** @deprecated the subscriptions registry was replaced by the TOPIC channels granted in the Ably token */
2224
2247
  const CHANNEL_PUBLISH_SUBSCRIPTIONS = 'pub:subscriptions';
2248
+ /** @deprecated not used, the events arrive by the TOPIC channels */
2225
2249
  const CHANNEL_SUBSCRIBE_NOTIFICATIONS = 'sub:notifications';
2250
+ /** @deprecated the events arrive by the TOPIC channels */
2226
2251
  const CHANNEL_SUBSCRIBE_CLIENT = (clientId) => `sub:client:${clientId}`;
2252
+ /** messages from the clients to the backend (chat completions, client track screenshots) */
2227
2253
  const CHANNEL_PUBLISH_MESSAGES = 'pub:messages';
2254
+ /** @deprecated global presence channel (limited to 200 members by Ably), replaced by CHANNEL_COMPANY_PRESENCE */
2228
2255
  const CHANNEL_PRESENCE_CLIENT = 'pre:client';
2256
+ // TOPIC channels
2257
+ // The backend publishes each event once on the channel of its topic and Ably does the fan-out.
2258
+ // Who can subscribe to each channel is decided by the API in the Ably token (capability), never by the client.
2259
+ // Dynamic segments are encoded so a key can never inject a `:` (the Ably segment separator used by the wildcards).
2260
+ const segment = (value) => encodeURIComponent(value);
2261
+ /** private: events of one session (code runs, requested data, client track commands) */
2262
+ const CHANNEL_SESSION = (sessionId) => `session:${segment(sessionId)}`;
2263
+ /** private: events of one user (own submissions while the contest is frozen, notifications) */
2264
+ const CHANNEL_USER = (userId) => `user:${segment(userId)}`;
2265
+ /** public: status of a submission when it is visible to everyone */
2266
+ const CHANNEL_SUBMISSION = (submitId) => `submission:${segment(submitId)}`;
2267
+ /** public: changes of a problem */
2268
+ const CHANNEL_PROBLEM = (problemKey) => `problem:${segment(problemKey)}`;
2269
+ /** per role: events of a contest for one role (and the roles above it, see `ContestChannelRole`) */
2270
+ const CHANNEL_CONTEST = (contestKey, role) => `contest:${segment(contestKey)}:${role}`;
2271
+ /** system administrators only: data sent by a tracked client (screenshots, location, device) */
2272
+ const CHANNEL_CLIENT_TRACK = (trackedClientId) => `client-track:${segment(trackedClientId)}`;
2273
+ /**
2274
+ * presence of the clients (tabs) of the logged users of a company (by its key), data: `UserPresenceData`.
2275
+ * The clients only enter (capability `presence`), only the administrators of the company receive it
2276
+ * (capability `presence-subscribe`). Ably limits the presence members per channel (200 by default,
2277
+ * more with server-side batching enabled on the `presence` namespace).
2278
+ * The identity of a member is its `clientId` (`sessionId|uiId`, verified by Ably), never its data.
2279
+ */
2280
+ const CHANNEL_COMPANY_PRESENCE = (companyKey) => `presence:company:${segment(companyKey)}`;
2281
+ // capability resources (wildcards) of the public channels
2282
+ const CHANNEL_SUBMISSION_ALL = 'submission:*';
2283
+ const CHANNEL_PROBLEM_ALL = 'problem:*';
2284
+ const CHANNEL_CLIENT_TRACK_ALL = 'client-track:*';
2285
+ /**
2286
+ * Returns the contest and role of a `CHANNEL_CONTEST` channel name, or null if it is not one.
2287
+ */
2288
+ const getContestChannelParams = (channel) => {
2289
+ const [namespace, contestKey, role, ...rest] = channel.split(':');
2290
+ if (namespace !== 'contest' || !contestKey || rest.length || !Object.values(ContestChannelRole).includes(role)) {
2291
+ return null;
2292
+ }
2293
+ return { contestKey: decodeURIComponent(contestKey), role: role };
2294
+ };
2229
2295
 
2230
2296
  const NEW_PAGE_SHEET = () => ({
2231
2297
  id: v4(),
@@ -3008,6 +3074,16 @@ const isUnsubscribeUserNotificationWebSocketEventDTO = (event) => {
3008
3074
  return isWebsocketSubscription(event)
3009
3075
  && event?.event === WebSocketSubscriptionEvent.UNSUBSCRIBE_USER_NOTIFICATION;
3010
3076
  };
3077
+ const isSubscribeClientTrackDataWebSocketEventDTO = (event) => {
3078
+ return isWebsocketSubscription(event)
3079
+ && event?.event === WebSocketSubscriptionEvent.SUBSCRIBE_CLIENT_TRACK_DATA
3080
+ && typeof event?.trackedClientId === 'string' && !!event.trackedClientId;
3081
+ };
3082
+ const isUnsubscribeClientTrackDataWebSocketEventDTO = (event) => {
3083
+ return isWebsocketSubscription(event)
3084
+ && event?.event === WebSocketSubscriptionEvent.UNSUBSCRIBE_CLIENT_TRACK_DATA
3085
+ && typeof event?.trackedClientId === 'string' && !!event.trackedClientId;
3086
+ };
3011
3087
  // is WebSocketResponseEventDTO, CHANNEL_SUBSCRIBE_CLIENT
3012
3088
  const isWebSocketResponseEventDTO = (event) => {
3013
3089
  return Object.values(WebSocketResponseEvent).includes(event?.event)
@@ -3121,6 +3197,73 @@ const isUserNotificationContestClarificationWebSocketResponseEventDTO = (event)
3121
3197
  const getWebSocketResponseEventKey = (event, clientId, id) => {
3122
3198
  return `${event}-${clientId}-${id}`;
3123
3199
  };
3200
+ const isWebSocketChannelsMessageDTO = (message) => {
3201
+ return Array.isArray(message?.channels)
3202
+ && message.channels.every((channel) => typeof channel === 'string' && !!channel)
3203
+ && Object.values(WebSocketResponseEvent).includes(message?.event?.event);
3204
+ };
3205
+ /**
3206
+ * Key used by the client to dispatch a response received on a TOPIC channel to its subscribers.
3207
+ * It is the same key that `getKeyWebSocketEventDTO` (base-ui) computes for the subscription.
3208
+ * Returns null when the response is not addressed to this client.
3209
+ */
3210
+ const getWebSocketResponseEventKeyOfResponse = (response, clientId) => {
3211
+ if (isCodeRunStatusMessageWebSocketResponseEventDTO(response)) {
3212
+ return getWebSocketResponseEventKey(WebSocketResponseEvent.CODE_RUN_STATUS, clientId, response.runId);
3213
+ }
3214
+ if (isSubmissionRunStatusMessageWebSocketResponseEventDTO(response)) {
3215
+ return getWebSocketResponseEventKey(WebSocketResponseEvent.SUBMISSION_RUN_STATUS, clientId, response.submitId);
3216
+ }
3217
+ if (isSendDataWebSocketResponseEventDTO(response)) {
3218
+ return getWebSocketResponseEventKey(WebSocketResponseEvent.SEND_DATA_, clientId, response.dataId);
3219
+ }
3220
+ if (isContestChangesWebSocketResponseEventDTO(response)) {
3221
+ return getWebSocketResponseEventKey(WebSocketResponseEvent.CONTEST_CHANGES, clientId, response.content.contestKey);
3222
+ }
3223
+ if (isProblemCrawledWebSocketResponseEventDTO(response)) {
3224
+ return getWebSocketResponseEventKey(WebSocketResponseEvent.PROBLEM_CRAWLED, clientId, response.content.problemKey);
3225
+ }
3226
+ if (isSubmissionsCrawlWebSocketResponseEventDTO(response)) {
3227
+ return getWebSocketResponseEventKey(WebSocketResponseEvent.SUBMISSIONS_CRAWL, clientId, join([response.content.contestKey, response.content.problemKeys]));
3228
+ }
3229
+ if (isClientTrackWebSocketResponseEventDTO(response)) {
3230
+ // the command is sent to the session channel, `key` holds the targeted client
3231
+ return response.key === clientId ? getWebSocketResponseEventKey(WebSocketResponseEvent.CLIENT_TRACK, clientId, '*') : null;
3232
+ }
3233
+ if (isUserNotificationWebSocketResponseEventDTO(response)) {
3234
+ return getWebSocketResponseEventKey(WebSocketResponseEvent.USER_NOTIFICATION_, clientId, '*');
3235
+ }
3236
+ return null;
3237
+ };
3238
+ /**
3239
+ * TOPIC channel that the client has to attach to receive the events of a subscription,
3240
+ * or null when the events arrive by the private channels (session / user) the client is always attached to.
3241
+ * Ably rejects the attach if the token has no capability for the channel.
3242
+ */
3243
+ const getChannelOfWebSocketSubscribeEvent = (event) => {
3244
+ if (isSubscribeSubmissionRunStatusWebSocketEventDTO(event)) {
3245
+ return CHANNEL_SUBMISSION(event.submitId);
3246
+ }
3247
+ if (isSubscribeProblemCrawledWebSocketEventDTO(event)) {
3248
+ return CHANNEL_PROBLEM(event.problemKey);
3249
+ }
3250
+ if (isSubscribeContestChangesWebSocketEventDTO(event) || isSubscribeSubmissionsCrawlWebSocketEventDTO(event)) {
3251
+ return CHANNEL_CONTEST(event.contestKey, ContestChannelRole.SPECTATORS);
3252
+ }
3253
+ if (isSubscribeClientTrackDataWebSocketEventDTO(event)) {
3254
+ return CHANNEL_CLIENT_TRACK(event.trackedClientId);
3255
+ }
3256
+ return null;
3257
+ };
3258
+ /**
3259
+ * Contest whose role channels the client needs (the API grants them when the token is requested with this contest).
3260
+ */
3261
+ const getContestKeyOfWebSocketSubscribeEvent = (event) => {
3262
+ if (isSubscribeContestChangesWebSocketEventDTO(event) || isSubscribeSubmissionsCrawlWebSocketEventDTO(event)) {
3263
+ return event.contestKey;
3264
+ }
3265
+ return null;
3266
+ };
3124
3267
 
3125
3268
  function splitTime(timeRemaining) {
3126
3269
  const remaining = [];
@@ -3344,4 +3487,4 @@ const isQuizTextSheet = (value) => {
3344
3487
  && ['text', 'number', 'textarea'].includes(value?.inputType);
3345
3488
  };
3346
3489
 
3347
- export { ACCEPTED_PROGRAMMING_LANGUAGES, ASPECT_RATIO, AspectRatio, AssignmentClass, AssignmentState, AssignmentType, CHANNEL_PRESENCE_CLIENT, CHANNEL_PUBLISH_MESSAGES, CHANNEL_PUBLISH_SUBSCRIPTIONS, CHANNEL_SUBSCRIBE_CLIENT, CHANNEL_SUBSCRIBE_NOTIFICATIONS, CODE_LANGUAGE, COMPANY_PLAN, CONTEST_ROLE, CONTEST_STATUS, COOKIE_JUKI_SESSION_ID, COURSE_ROLE, CodeLanguage, CollectionKey, CompanyLogoType, CompanyPlan, ContestEventAction, ContestProblemBlockedByType, ContestProblemPrerequisiteType, ContestStatus, ContestTimeStatus, CourseRole, CourseStatus, CourseType, DAY_NAMES, DEFAULT_JUKI_PROFILE_IMAGE, DataViewMode, EMPTY_DOCUMENT_MEMBERS, EMPTY_ENTITY_MEMBERS, EMPTY_USER_PERMISSIONS, ENTITY_ACCESS, ENTITY_STATE, ERROR, EmailSubscription, EmailValidationState, EntityAccess, EntityCollection, EntityMembersRank, EntityState, EntityStatus, ErrorCode, FILE_ROLE, FILE_TYPE, FileMemberRole, FileType, FilesJukiPub, GroupType, HEADER_JUKI_FORWARDED_HOST, HEADER_JUKI_INTERNAL_API_KEY, HEADER_JUKI_METADATA, HEADER_JUKI_SECRET_TOKEN, HEADER_JUKI_VISITOR_SESSION_ID, HOURS, HTTPMethod, JUDGE, JUKI_APP_COMPANY_KEY, JUKI_APP_EMAIL_TEMPLATE, JkError, Judge, LANGUAGE, LOWERCASE_LETTERS, Language, LogLevel, LogOperation, MAX_DATE, MEMBER_ROLE, MILLISECONDS, MINUTES, MIN_DATE, MONTH_NAMES, MemberRole, MemberType, MenuViewMode, NEW_PAGE_SHEET, NUMBERS, NotificationType, ONE_DAY, ONE_HOUR, ONE_MINUTE, ONE_SECOND, ONE_WEEK, PAGE, PAGE_SIZE, PAGE_SIZES, PALLETE, PROBLEM_INPUT, PROBLEM_MODE, PROBLEM_MODES, PROBLEM_OUTPUT, PROBLEM_ROLE, PROBLEM_TYPE, PROBLEM_TYPES, PROBLEM_VERDICT, PROBLEM_VERDICTS, ProblemInput, ProblemOutput, ProblemScoringMode, ProblemType, ProblemVerdict, ProfileSetting, QuizScoringMode, RUNNER_ACCEPTED_PROBLEM_MODES, RUNNER_ACCEPTED_PROBLEM_TYPES, RUNNER_ACCEPTED_PROGRAMMING_LANGUAGES, RUNNER_TYPE, Role, RunnerType, SECONDS, SEC_120, SEC_30, SEC_60, SEC_90, SEPARATOR_TOKEN, SUBMISSION_RUN_STATUS, SUPPORT_EMAIL, SYSTEM_ROLE, ScopeData, Status, SubmissionRunStatus, SystemRole, TEAM_ROLE, THEME, TeamRole, TeamStatus, Theme, TypeTestCase, UPPERCASE_LETTERS, USER_ROLE, USER_STATUS, UserRole, UserStatus, UserTeamStatus, WebSocketMessageEvent, WebSocketResponseEvent, WebSocketSubscriptionEvent, WorkingIn, WorksheetType, YEARS, _KEY, _SUB_SUB_TAB, _SUB_TAB, _TAB, _TYPE, areArraysDifferent, chunkString, cleanRequest, clearAsyncInterval, consoleError, consoleInfo, consoleWarn, contentResponse, contentsResponse, errorsResponse, getAssignmentCommentKey, getAssignmentUserCommentKey, getClientId, getContestCommentKey, getDataLog, getDataOfTestCase, getDays, getDefaultMeta, getDocumentAccess, getHours, getMilliseconds, getMinutes, getMonths, getParamsOfClientId, getParamsOfUserKey, getPlainText, getProblemCommentKey, getProblemJudgeKey, getRandomString, getSeconds, getSummaryWorksheetsInPages, getTotalExercisesOfSummaryWorksheetsInPages, getUserKey, getVerdictFromTestCase, getWebSocketResponseEventKey, getWorksheetsInPages, getYears, humanFileSize, indexToLetters, isBasicWorksheet, isChatCompletionsWebSocketEventDTO, isClientTrackDeviceWebSocketEventDTO, isClientTrackLocationWebSocketEventDTO, isClientTrackScreenshotWebSocketEventDTO, isClientTrackWebSocketResponseEventDTO, isCodeEditorSheet, isCodeRunStatusMessageWebSocketResponseEventDTO, isContestAssignment, isContestChangesWebSocketResponseEventDTO, isCourseAssignment, isEndlessContest, isFutureContest, isGlobalContest, isJkmdSheet, isObjectJson, isPastContest, isPingWebSocketEventDTO, isPongWebSocketResponseEventDTO, isProblemCrawledWebSocketResponseEventDTO, isQuizOptionsSheet, isQuizProblemSheet, isQuizTextSheet, isSenDataChatCompletionsWebSocketResponseEventDTO, isSendDataClientTrackWebSocketResponseEventDTO, isSendDataEc2InstancesListWebSocketResponseEventDTO, isSendDataEcsTaskDefinitionListWebSocketResponseEventDTO, isSendDataEcsTasksListWebSocketResponseEventDTO, isSendDataRunCommandWebSocketResponseEventDTO, isSendDataSsmSessionsListWebSocketResponseEventDTO, isSendDataWebSocketResponseEventDTO, isStringJson, isSubmissionRunStatusMessageWebSocketResponseEventDTO, isSubmissionsCrawlWebSocketResponseEventDTO, isSubscribeClientTrackWebSocketEventDTO, isSubscribeCodeRunStatusWebSocketEventDTO, isSubscribeContestChangesWebSocketEventDTO, isSubscribeGetDataWebSocketEventDTO, isSubscribeProblemCrawledWebSocketEventDTO, isSubscribeSubmissionRunStatusWebSocketEventDTO, isSubscribeSubmissionsCrawlWebSocketEventDTO, isSubscribeUserNotificationWebSocketEventDTO, isTeamMember, isUnsubscribeClientTrackWebSocketEventDTO, isUnsubscribeCodeRunStatusWebSocketEventDTO, isUnsubscribeContestChangesWebSocketEventDTO, isUnsubscribeGetDataWebSocketEventDTO, isUnsubscribeProblemCrawledWebSocketEventDTO, isUnsubscribeSubmissionRunStatusWebSocketEventDTO, isUnsubscribeSubmissionsCrawlWebSocketEventDTO, isUnsubscribeUserNotificationWebSocketEventDTO, isUserMember, isUserMessageWebSocketResponseEventDTO, isUserNotificationContestClarificationWebSocketResponseEventDTO, isUserNotificationSubmissionWebSocketResponseEventDTO, isUserNotificationWebSocketResponseEventDTO, isWebSocketResponseEventDTO, isWebsocketSubscription, isWorksheetAssignment, join, lettersToIndex, memorySizeOf, mergeVerdicts, mex, normalizeToLocalStartOfDay, normalizeToLocalStartOfHour, normalizeToLocalStartOfMinutes, normalizeToLocalStartOfSeconds, objectUpdate, objectsUpdate, range, removeExtension, safeJsonParse, setAsyncInterval, split, splitTime, stringToArrayBuffer, toEntityMembersDTO, toJkError };
3490
+ export { ACCEPTED_PROGRAMMING_LANGUAGES, ASPECT_RATIO, AspectRatio, AssignmentClass, AssignmentState, AssignmentType, CHANNEL_CLIENT_TRACK, CHANNEL_CLIENT_TRACK_ALL, CHANNEL_COMPANY_PRESENCE, CHANNEL_CONTEST, CHANNEL_PRESENCE_CLIENT, CHANNEL_PROBLEM, CHANNEL_PROBLEM_ALL, CHANNEL_PUBLISH_MESSAGES, CHANNEL_PUBLISH_SUBSCRIPTIONS, CHANNEL_SESSION, CHANNEL_SUBMISSION, CHANNEL_SUBMISSION_ALL, CHANNEL_SUBSCRIBE_CLIENT, CHANNEL_SUBSCRIBE_NOTIFICATIONS, CHANNEL_USER, CODE_LANGUAGE, COMPANY_PLAN, CONTEST_ROLE, CONTEST_STATUS, COOKIE_JUKI_SESSION_ID, COURSE_ROLE, CodeLanguage, CollectionKey, CompanyLogoType, CompanyPlan, ContestChannelRole, ContestEventAction, ContestProblemBlockedByType, ContestProblemPrerequisiteType, ContestStatus, ContestTimeStatus, CourseRole, CourseStatus, CourseType, DAY_NAMES, DEFAULT_JUKI_PROFILE_IMAGE, DataViewMode, EMPTY_DOCUMENT_MEMBERS, EMPTY_ENTITY_MEMBERS, EMPTY_USER_PERMISSIONS, ENTITY_ACCESS, ENTITY_STATE, ERROR, EmailSubscription, EmailValidationState, EntityAccess, EntityCollection, EntityMembersRank, EntityState, EntityStatus, ErrorCode, FILE_ROLE, FILE_TYPE, FileMemberRole, FileType, FilesJukiPub, GroupType, HEADER_JUKI_FORWARDED_HOST, HEADER_JUKI_INTERNAL_API_KEY, HEADER_JUKI_METADATA, HEADER_JUKI_SECRET_TOKEN, HEADER_JUKI_VISITOR_SESSION_ID, HOURS, HTTPMethod, JUDGE, JUKI_APP_COMPANY_KEY, JUKI_APP_EMAIL_TEMPLATE, JkError, Judge, LANGUAGE, LOWERCASE_LETTERS, Language, LogLevel, LogOperation, MAX_DATE, MEMBER_ROLE, MILLISECONDS, MINUTES, MIN_DATE, MONTH_NAMES, MemberRole, MemberType, MenuViewMode, NEW_PAGE_SHEET, NUMBERS, NotificationType, ONE_DAY, ONE_HOUR, ONE_MINUTE, ONE_SECOND, ONE_WEEK, PAGE, PAGE_SIZE, PAGE_SIZES, PALLETE, PROBLEM_INPUT, PROBLEM_MODE, PROBLEM_MODES, PROBLEM_OUTPUT, PROBLEM_ROLE, PROBLEM_TYPE, PROBLEM_TYPES, PROBLEM_VERDICT, PROBLEM_VERDICTS, ProblemInput, ProblemOutput, ProblemScoringMode, ProblemType, ProblemVerdict, ProfileSetting, QuizScoringMode, RUNNER_ACCEPTED_PROBLEM_MODES, RUNNER_ACCEPTED_PROBLEM_TYPES, RUNNER_ACCEPTED_PROGRAMMING_LANGUAGES, RUNNER_TYPE, Role, RunnerType, SECONDS, SEC_120, SEC_30, SEC_60, SEC_90, SEPARATOR_TOKEN, SUBMISSION_RUN_STATUS, SUPPORT_EMAIL, SYSTEM_ROLE, ScopeData, Status, SubmissionRunStatus, SystemRole, TEAM_ROLE, THEME, TeamRole, TeamStatus, Theme, TypeTestCase, UPPERCASE_LETTERS, USER_ROLE, USER_STATUS, UserRole, UserStatus, UserTeamStatus, WebSocketMessageEvent, WebSocketResponseEvent, WebSocketSubscriptionEvent, WorkingIn, WorksheetType, YEARS, _KEY, _SUB_SUB_TAB, _SUB_TAB, _TAB, _TYPE, areArraysDifferent, chunkString, cleanRequest, clearAsyncInterval, consoleError, consoleInfo, consoleWarn, contentResponse, contentsResponse, errorsResponse, getAssignmentCommentKey, getAssignmentUserCommentKey, getChannelOfWebSocketSubscribeEvent, getClientId, getContestChannelParams, getContestCommentKey, getContestKeyOfWebSocketSubscribeEvent, getDataLog, getDataOfTestCase, getDays, getDefaultMeta, getDocumentAccess, getHours, getMilliseconds, getMinutes, getMonths, getParamsOfClientId, getParamsOfUserKey, getPlainText, getProblemCommentKey, getProblemJudgeKey, getRandomString, getSeconds, getSummaryWorksheetsInPages, getTotalExercisesOfSummaryWorksheetsInPages, getUserKey, getVerdictFromTestCase, getWebSocketResponseEventKey, getWebSocketResponseEventKeyOfResponse, getWorksheetsInPages, getYears, humanFileSize, indexToLetters, isBasicWorksheet, isChatCompletionsWebSocketEventDTO, isClientTrackDeviceWebSocketEventDTO, isClientTrackLocationWebSocketEventDTO, isClientTrackScreenshotWebSocketEventDTO, isClientTrackWebSocketResponseEventDTO, isCodeEditorSheet, isCodeRunStatusMessageWebSocketResponseEventDTO, isContestAssignment, isContestChangesWebSocketResponseEventDTO, isCourseAssignment, isEndlessContest, isFutureContest, isGlobalContest, isJkmdSheet, isObjectJson, isPastContest, isPingWebSocketEventDTO, isPongWebSocketResponseEventDTO, isProblemCrawledWebSocketResponseEventDTO, isQuizOptionsSheet, isQuizProblemSheet, isQuizTextSheet, isSenDataChatCompletionsWebSocketResponseEventDTO, isSendDataClientTrackWebSocketResponseEventDTO, isSendDataEc2InstancesListWebSocketResponseEventDTO, isSendDataEcsTaskDefinitionListWebSocketResponseEventDTO, isSendDataEcsTasksListWebSocketResponseEventDTO, isSendDataRunCommandWebSocketResponseEventDTO, isSendDataSsmSessionsListWebSocketResponseEventDTO, isSendDataWebSocketResponseEventDTO, isStringJson, isSubmissionRunStatusMessageWebSocketResponseEventDTO, isSubmissionsCrawlWebSocketResponseEventDTO, isSubscribeClientTrackDataWebSocketEventDTO, isSubscribeClientTrackWebSocketEventDTO, isSubscribeCodeRunStatusWebSocketEventDTO, isSubscribeContestChangesWebSocketEventDTO, isSubscribeGetDataWebSocketEventDTO, isSubscribeProblemCrawledWebSocketEventDTO, isSubscribeSubmissionRunStatusWebSocketEventDTO, isSubscribeSubmissionsCrawlWebSocketEventDTO, isSubscribeUserNotificationWebSocketEventDTO, isTeamMember, isUnsubscribeClientTrackDataWebSocketEventDTO, isUnsubscribeClientTrackWebSocketEventDTO, isUnsubscribeCodeRunStatusWebSocketEventDTO, isUnsubscribeContestChangesWebSocketEventDTO, isUnsubscribeGetDataWebSocketEventDTO, isUnsubscribeProblemCrawledWebSocketEventDTO, isUnsubscribeSubmissionRunStatusWebSocketEventDTO, isUnsubscribeSubmissionsCrawlWebSocketEventDTO, isUnsubscribeUserNotificationWebSocketEventDTO, isUserMember, isUserMessageWebSocketResponseEventDTO, isUserNotificationContestClarificationWebSocketResponseEventDTO, isUserNotificationSubmissionWebSocketResponseEventDTO, isUserNotificationWebSocketResponseEventDTO, isWebSocketChannelsMessageDTO, isWebSocketResponseEventDTO, isWebsocketSubscription, isWorksheetAssignment, join, lettersToIndex, memorySizeOf, mergeVerdicts, mex, normalizeToLocalStartOfDay, normalizeToLocalStartOfHour, normalizeToLocalStartOfMinutes, normalizeToLocalStartOfSeconds, objectUpdate, objectsUpdate, range, removeExtension, safeJsonParse, setAsyncInterval, split, splitTime, stringToArrayBuffer, toEntityMembersDTO, toJkError };
@@ -1,6 +1,41 @@
1
- import { ClientId } from '../types';
1
+ import { ClientId, ContestChannelRole } from '../types';
2
+ /** @deprecated the subscriptions registry was replaced by the TOPIC channels granted in the Ably token */
2
3
  export declare const CHANNEL_PUBLISH_SUBSCRIPTIONS = "pub:subscriptions";
4
+ /** @deprecated not used, the events arrive by the TOPIC channels */
3
5
  export declare const CHANNEL_SUBSCRIBE_NOTIFICATIONS = "sub:notifications";
6
+ /** @deprecated the events arrive by the TOPIC channels */
4
7
  export declare const CHANNEL_SUBSCRIBE_CLIENT: (clientId: ClientId) => string;
8
+ /** messages from the clients to the backend (chat completions, client track screenshots) */
5
9
  export declare const CHANNEL_PUBLISH_MESSAGES = "pub:messages";
10
+ /** @deprecated global presence channel (limited to 200 members by Ably), replaced by CHANNEL_COMPANY_PRESENCE */
6
11
  export declare const CHANNEL_PRESENCE_CLIENT = "pre:client";
12
+ /** private: events of one session (code runs, requested data, client track commands) */
13
+ export declare const CHANNEL_SESSION: (sessionId: string) => string;
14
+ /** private: events of one user (own submissions while the contest is frozen, notifications) */
15
+ export declare const CHANNEL_USER: (userId: string) => string;
16
+ /** public: status of a submission when it is visible to everyone */
17
+ export declare const CHANNEL_SUBMISSION: (submitId: string) => string;
18
+ /** public: changes of a problem */
19
+ export declare const CHANNEL_PROBLEM: (problemKey: string) => string;
20
+ /** per role: events of a contest for one role (and the roles above it, see `ContestChannelRole`) */
21
+ export declare const CHANNEL_CONTEST: (contestKey: string, role: ContestChannelRole) => string;
22
+ /** system administrators only: data sent by a tracked client (screenshots, location, device) */
23
+ export declare const CHANNEL_CLIENT_TRACK: (trackedClientId: ClientId) => string;
24
+ /**
25
+ * presence of the clients (tabs) of the logged users of a company (by its key), data: `UserPresenceData`.
26
+ * The clients only enter (capability `presence`), only the administrators of the company receive it
27
+ * (capability `presence-subscribe`). Ably limits the presence members per channel (200 by default,
28
+ * more with server-side batching enabled on the `presence` namespace).
29
+ * The identity of a member is its `clientId` (`sessionId|uiId`, verified by Ably), never its data.
30
+ */
31
+ export declare const CHANNEL_COMPANY_PRESENCE: (companyKey: string) => string;
32
+ export declare const CHANNEL_SUBMISSION_ALL = "submission:*";
33
+ export declare const CHANNEL_PROBLEM_ALL = "problem:*";
34
+ export declare const CHANNEL_CLIENT_TRACK_ALL = "client-track:*";
35
+ /**
36
+ * Returns the contest and role of a `CHANNEL_CONTEST` channel name, or null if it is not one.
37
+ */
38
+ export declare const getContestChannelParams: (channel: string) => {
39
+ contestKey: string;
40
+ role: ContestChannelRole;
41
+ } | null;
@@ -8,6 +8,7 @@ interface WebsocketMessage {
8
8
  export interface PingWebSocketEventDTO extends WebsocketMessage {
9
9
  event: WebSocketMessageEvent.PING;
10
10
  }
11
+ /** @deprecated the location is the `href` of the company presence (`UserPresenceData`) */
11
12
  export interface ClientTrackLocationWebSocketEventDTO extends WebsocketMessage {
12
13
  event: WebSocketMessageEvent.CLIENT_TRACK_LOCATION;
13
14
  href: string;
@@ -94,8 +95,16 @@ export interface UnsubscribeUserNotificationWebSocketEventDTO extends WebsocketS
94
95
  event: WebSocketSubscriptionEvent.UNSUBSCRIBE_USER_NOTIFICATION;
95
96
  userKey: string;
96
97
  }
97
- export type WebSocketSubscribeEventDTO = SubscribeCodeRunStatusWebSocketEventDTO | SubscribeSubmissionRunStatusWebSocketEventDTO | SubscribeGetDataWebSocketEventDTO | SubscribeProblemCrawledWebSocketEventDTO | SubscribeSubmissionsCrawlWebSocketEventDTO | SubscribeContestChangesWebSocketEventDTO | SubscribeClientTrackWebSocketEventDTO | SubscribeUserNotificationWebsocketEventDTO;
98
- export type WebSocketUnsubscribeEventDTO = UnsubscribeCodeRunStatusWebSocketEventDTO | UnsubscribeSubmissionRunStatusWebSocketEventDTO | UnsubscribeGetDataWebSocketEventDTO | UnsubscribeProblemCrawledWebSocketEventDTO | UnsubscribeSubmissionsCrawlWebSocketEventDTO | UnsubscribeContestChangesWebSocketEventDTO | UnsubscribeClientTrackWebSocketEventDTO | UnsubscribeUserNotificationWebSocketEventDTO;
98
+ export interface SubscribeClientTrackDataWebSocketEventDTO extends WebsocketSubscription {
99
+ event: WebSocketSubscriptionEvent.SUBSCRIBE_CLIENT_TRACK_DATA;
100
+ trackedClientId: ClientId;
101
+ }
102
+ export interface UnsubscribeClientTrackDataWebSocketEventDTO extends WebsocketSubscription {
103
+ event: WebSocketSubscriptionEvent.UNSUBSCRIBE_CLIENT_TRACK_DATA;
104
+ trackedClientId: ClientId;
105
+ }
106
+ export type WebSocketSubscribeEventDTO = SubscribeCodeRunStatusWebSocketEventDTO | SubscribeSubmissionRunStatusWebSocketEventDTO | SubscribeGetDataWebSocketEventDTO | SubscribeProblemCrawledWebSocketEventDTO | SubscribeSubmissionsCrawlWebSocketEventDTO | SubscribeContestChangesWebSocketEventDTO | SubscribeClientTrackWebSocketEventDTO | SubscribeUserNotificationWebsocketEventDTO | SubscribeClientTrackDataWebSocketEventDTO;
107
+ export type WebSocketUnsubscribeEventDTO = UnsubscribeCodeRunStatusWebSocketEventDTO | UnsubscribeSubmissionRunStatusWebSocketEventDTO | UnsubscribeGetDataWebSocketEventDTO | UnsubscribeProblemCrawledWebSocketEventDTO | UnsubscribeSubmissionsCrawlWebSocketEventDTO | UnsubscribeContestChangesWebSocketEventDTO | UnsubscribeClientTrackWebSocketEventDTO | UnsubscribeUserNotificationWebSocketEventDTO | UnsubscribeClientTrackDataWebSocketEventDTO;
99
108
  export interface WebSocketResponse {
100
109
  event: WebSocketResponseEvent;
101
110
  key: WebSocketResponseEventKey;
@@ -253,4 +262,20 @@ export interface UserNotificationContestClarificationWebSocketResponseEventDTO e
253
262
  };
254
263
  }
255
264
  export type WebSocketResponseEventDTO = PongWebSocketResponseEventDTO | CodeRunStatusWebSocketResponseEventDTO | SubmissionRunStatusWebSocketResponseEventDTO | UserMessageWebSocketResponseEventDTO | SendDataEcsTaskDefinitionListWebSocketResponseEventDTO | SendDataEcsTasksListWebSocketResponseEventDTO | SendDataEc2InstancesListWebSocketResponseEventDTO | SendDataSsmSessionsListWebSocketResponseEventDTO | ProblemCrawledWebSocketResponseEventDTO | SendDataChatCompletionsWebSocketResponseEventDTO | SubmissionsCrawlWebSocketResponseEventDTO | ContestChangesWebSocketResponseEventDTO | SendDataRunCommandWebSocketResponseEventDTO | SendDataClientTrackWebSocketResponseEventDTO | ClientTrackWebSocketResponseEventDTO | UserNotificationSubmissionWebSocketResponseEventDTO | UserNotificationContestClarificationWebSocketResponseEventDTO;
265
+ /**
266
+ * Message sent by the backend producers to the websocket SQS queue:
267
+ * the producer (trusted, with access to the DB) decides the Ably channels where the event is published.
268
+ */
269
+ export interface WebSocketChannelsMessageDTO {
270
+ channels: string[];
271
+ event: WebSocketResponseEventDTO;
272
+ }
273
+ /**
274
+ * Data of a client (tab) in its company presence channel (`CHANNEL_COMPANY_PRESENCE`).
275
+ * Sent by the client: informative only, the identity is the presence member `clientId`.
276
+ */
277
+ export interface UserPresenceData {
278
+ href: string;
279
+ uiId: string;
280
+ }
256
281
  export {};
@@ -1,4 +1,4 @@
1
- import { ChatCompletionsWebSocketEventDTO, ClientTrackDeviceWebSocketEventDTO, ClientTrackLocationWebSocketEventDTO, ClientTrackScreenshotWebSocketEventDTO, ClientTrackWebSocketResponseEventDTO, CodeRunStatusWebSocketResponseEventDTO, ContestChangesWebSocketResponseEventDTO, PingWebSocketEventDTO, PongWebSocketResponseEventDTO, ProblemCrawledWebSocketResponseEventDTO, SendDataChatCompletionsWebSocketResponseEventDTO, SendDataClientTrackWebSocketResponseEventDTO, SendDataEc2InstancesListWebSocketResponseEventDTO, SendDataEcsTaskDefinitionListWebSocketResponseEventDTO, SendDataEcsTasksListWebSocketResponseEventDTO, SendDataRunCommandWebSocketResponseEventDTO, SendDataSsmSessionsListWebSocketResponseEventDTO, SendDataWebSocketResponseEventDTO, SubmissionRunStatusWebSocketResponseEventDTO, SubmissionsCrawlWebSocketResponseEventDTO, SubscribeClientTrackWebSocketEventDTO, SubscribeCodeRunStatusWebSocketEventDTO, SubscribeContestChangesWebSocketEventDTO, SubscribeGetDataWebSocketEventDTO, SubscribeProblemCrawledWebSocketEventDTO, SubscribeSubmissionRunStatusWebSocketEventDTO, SubscribeSubmissionsCrawlWebSocketEventDTO, SubscribeUserNotificationWebsocketEventDTO, UnsubscribeClientTrackWebSocketEventDTO, UnsubscribeCodeRunStatusWebSocketEventDTO, UnsubscribeContestChangesWebSocketEventDTO, UnsubscribeGetDataWebSocketEventDTO, UnsubscribeProblemCrawledWebSocketEventDTO, UnsubscribeSubmissionRunStatusWebSocketEventDTO, UnsubscribeSubmissionsCrawlWebSocketEventDTO, UnsubscribeUserNotificationWebSocketEventDTO, UserMessageWebSocketResponseEventDTO, UserNotificationContestClarificationWebSocketResponseEventDTO, UserNotificationSubmissionWebSocketResponseEventDTO, UserNotificationWebSocketResponseEventDTO, WebSocketMessageEventDTO, WebSocketResponseEventDTO, WebSocketSubscribeEventDTO, WebSocketUnsubscribeEventDTO } from '../dto';
1
+ import { ChatCompletionsWebSocketEventDTO, ClientTrackDeviceWebSocketEventDTO, ClientTrackLocationWebSocketEventDTO, ClientTrackScreenshotWebSocketEventDTO, ClientTrackWebSocketResponseEventDTO, CodeRunStatusWebSocketResponseEventDTO, ContestChangesWebSocketResponseEventDTO, PingWebSocketEventDTO, PongWebSocketResponseEventDTO, ProblemCrawledWebSocketResponseEventDTO, SendDataChatCompletionsWebSocketResponseEventDTO, SendDataClientTrackWebSocketResponseEventDTO, SendDataEc2InstancesListWebSocketResponseEventDTO, SendDataEcsTaskDefinitionListWebSocketResponseEventDTO, SendDataEcsTasksListWebSocketResponseEventDTO, SendDataRunCommandWebSocketResponseEventDTO, SendDataSsmSessionsListWebSocketResponseEventDTO, SendDataWebSocketResponseEventDTO, SubmissionRunStatusWebSocketResponseEventDTO, SubmissionsCrawlWebSocketResponseEventDTO, SubscribeClientTrackDataWebSocketEventDTO, SubscribeClientTrackWebSocketEventDTO, SubscribeCodeRunStatusWebSocketEventDTO, SubscribeContestChangesWebSocketEventDTO, SubscribeGetDataWebSocketEventDTO, SubscribeProblemCrawledWebSocketEventDTO, SubscribeSubmissionRunStatusWebSocketEventDTO, SubscribeSubmissionsCrawlWebSocketEventDTO, SubscribeUserNotificationWebsocketEventDTO, UnsubscribeClientTrackDataWebSocketEventDTO, UnsubscribeClientTrackWebSocketEventDTO, UnsubscribeCodeRunStatusWebSocketEventDTO, UnsubscribeContestChangesWebSocketEventDTO, UnsubscribeGetDataWebSocketEventDTO, UnsubscribeProblemCrawledWebSocketEventDTO, UnsubscribeSubmissionRunStatusWebSocketEventDTO, UnsubscribeSubmissionsCrawlWebSocketEventDTO, UnsubscribeUserNotificationWebSocketEventDTO, UserMessageWebSocketResponseEventDTO, UserNotificationContestClarificationWebSocketResponseEventDTO, UserNotificationSubmissionWebSocketResponseEventDTO, UserNotificationWebSocketResponseEventDTO, WebSocketChannelsMessageDTO, WebSocketMessageEventDTO, WebSocketResponseEventDTO, WebSocketSubscribeEventDTO, WebSocketUnsubscribeEventDTO } from '../dto';
2
2
  import { ClientId, WebSocketResponseEvent, WebSocketResponseEventKey } from '../types';
3
3
  export declare const isWebsocketSubscription: (event: any) => event is WebSocketSubscribeEventDTO | WebSocketUnsubscribeEventDTO | WebSocketMessageEventDTO;
4
4
  export declare const isPingWebSocketEventDTO: (event: any) => event is PingWebSocketEventDTO;
@@ -22,6 +22,8 @@ export declare const isSubscribeClientTrackWebSocketEventDTO: (event: any) => ev
22
22
  export declare const isUnsubscribeClientTrackWebSocketEventDTO: (event: any) => event is UnsubscribeClientTrackWebSocketEventDTO;
23
23
  export declare const isSubscribeUserNotificationWebSocketEventDTO: (event: any) => event is SubscribeUserNotificationWebsocketEventDTO;
24
24
  export declare const isUnsubscribeUserNotificationWebSocketEventDTO: (event: any) => event is UnsubscribeUserNotificationWebSocketEventDTO;
25
+ export declare const isSubscribeClientTrackDataWebSocketEventDTO: (event: any) => event is SubscribeClientTrackDataWebSocketEventDTO;
26
+ export declare const isUnsubscribeClientTrackDataWebSocketEventDTO: (event: any) => event is UnsubscribeClientTrackDataWebSocketEventDTO;
25
27
  export declare const isWebSocketResponseEventDTO: (event: any) => event is WebSocketResponseEventDTO;
26
28
  export declare const isPongWebSocketResponseEventDTO: (event: any) => event is PongWebSocketResponseEventDTO;
27
29
  export declare const isCodeRunStatusMessageWebSocketResponseEventDTO: (event: any) => event is CodeRunStatusWebSocketResponseEventDTO;
@@ -43,3 +45,20 @@ export declare const isUserNotificationWebSocketResponseEventDTO: (event: any) =
43
45
  export declare const isUserNotificationSubmissionWebSocketResponseEventDTO: (event: any) => event is UserNotificationSubmissionWebSocketResponseEventDTO;
44
46
  export declare const isUserNotificationContestClarificationWebSocketResponseEventDTO: (event: any) => event is UserNotificationContestClarificationWebSocketResponseEventDTO;
45
47
  export declare const getWebSocketResponseEventKey: (event: WebSocketResponseEvent, clientId: ClientId, id: string) => WebSocketResponseEventKey;
48
+ export declare const isWebSocketChannelsMessageDTO: (message: any) => message is WebSocketChannelsMessageDTO;
49
+ /**
50
+ * Key used by the client to dispatch a response received on a TOPIC channel to its subscribers.
51
+ * It is the same key that `getKeyWebSocketEventDTO` (base-ui) computes for the subscription.
52
+ * Returns null when the response is not addressed to this client.
53
+ */
54
+ export declare const getWebSocketResponseEventKeyOfResponse: (response: WebSocketResponseEventDTO, clientId: ClientId) => WebSocketResponseEventKey | null;
55
+ /**
56
+ * TOPIC channel that the client has to attach to receive the events of a subscription,
57
+ * or null when the events arrive by the private channels (session / user) the client is always attached to.
58
+ * Ably rejects the attach if the token has no capability for the channel.
59
+ */
60
+ export declare const getChannelOfWebSocketSubscribeEvent: (event: WebSocketSubscribeEventDTO) => string | null;
61
+ /**
62
+ * Contest whose role channels the client needs (the API grants them when the token is requested with this contest).
63
+ */
64
+ export declare const getContestKeyOfWebSocketSubscribeEvent: (event: WebSocketSubscribeEventDTO) => string | null;
@@ -6,6 +6,10 @@ export declare enum WebSocketMessageEvent {
6
6
  CLIENT_TRACK_DEVICE = "CLIENT_TRACK_DEVICE",
7
7
  CHAT_COMPLETIONS = "CHAT_COMPLETIONS"
8
8
  }
9
+ /**
10
+ * Subscriptions of the client. With the TOPIC channels they are not sent to the server anymore:
11
+ * they only say which channel the client attaches (`getChannelOfWebSocketSubscribeEvent`) and the dispatch key.
12
+ */
9
13
  export declare enum WebSocketSubscriptionEvent {
10
14
  SUBSCRIBE_CODE_RUN_STATUS = "SUBSCRIBE_CODE_RUN_STATUS",
11
15
  UNSUBSCRIBE_CODE_RUN_STATUS = "UNSUBSCRIBE_CODE_RUN_STATUS",
@@ -22,7 +26,9 @@ export declare enum WebSocketSubscriptionEvent {
22
26
  SUBSCRIBE_CLIENT_TRACK = "SUBSCRIBE_CLIENT_TRACK",
23
27
  UNSUBSCRIBE_CLIENT_TRACK = "UNSUBSCRIBE_CLIENT_TRACK",
24
28
  SUBSCRIBE_USER_NOTIFICATION = "SUBSCRIBE_USER_NOTIFICATION",
25
- UNSUBSCRIBE_USER_NOTIFICATION = "UNSUBSCRIBE_USER_NOTIFICATION"
29
+ UNSUBSCRIBE_USER_NOTIFICATION = "UNSUBSCRIBE_USER_NOTIFICATION",
30
+ SUBSCRIBE_CLIENT_TRACK_DATA = "SUBSCRIBE_CLIENT_TRACK_DATA",
31
+ UNSUBSCRIBE_CLIENT_TRACK_DATA = "UNSUBSCRIBE_CLIENT_TRACK_DATA"
26
32
  }
27
33
  export declare enum WebSocketResponseEvent {
28
34
  PONG = "PONG",
@@ -47,3 +53,16 @@ export declare enum WebSocketResponseEvent {
47
53
  }
48
54
  export type ClientId = `${RecordId}|${string}`;
49
55
  export type WebSocketResponseEventKey = `${WebSocketResponseEvent}-${ClientId}-${string}`;
56
+ /**
57
+ * Roles of a contest, with the same names as the contest members in the DB.
58
+ * Each role has its own Ably channel: `contest:{contestKey}:{role}` (see `CHANNEL_CONTEST`).
59
+ * The inclusions follow `getContestAccessPermissions`:
60
+ * spectators ⊇ managers ⊇ administrators, spectators ⊇ participants, spectators ⊇ guests
61
+ */
62
+ export declare enum ContestChannelRole {
63
+ ADMINISTRATORS = "administrators",
64
+ MANAGERS = "managers",
65
+ PARTICIPANTS = "participants",
66
+ GUESTS = "guests",
67
+ SPECTATORS = "spectators"
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juki-team/commons",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "author": "juki-team",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "rollup": "^4.57.1",
31
31
  "rollup-plugin-typescript2": "^0.36.0",
32
32
  "typedoc": "^0.28.16",
33
- "typescript": "^5.9.3"
33
+ "typescript": "^6.0.3"
34
34
  },
35
35
  "dependencies": {
36
36
  "uuid": "^13.0.0 || ^14.0.0"
@@ -40,6 +40,6 @@
40
40
  "watch": "rollup -cw",
41
41
  "build": "rm -rf docs && rm -rf dist && rollup -c && pnpm run docs",
42
42
  "push-commit": "f() { git add -A && git commit -m \"${1:-chore}\" && pnpm push; }; f",
43
- "push": "f() { npm version patch --no-git-tag-version && pnpm build && pnpm publish --tag release-0.1 --no-git-checks && version=\"v$(node -p \"require('./package.json').version\")\" && git add -A && git commit -m $version && git tag -a $version -m $version && git push origin v0.1.x --follow-tags; }; f"
43
+ "push": "f() { pnpm version patch --no-git-tag-version --no-git-checks && pnpm build && pnpm publish --tag release-0.1 --no-git-checks && version=\"v$(node -p \"require('./package.json').version\")\" && git add -A && git commit -m $version && git tag -a $version -m $version && git push origin v0.1.x --follow-tags; }; f"
44
44
  }
45
45
  }