@strapi-community/plugin-io 5.2.0 → 5.3.1

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.
@@ -634,7 +634,9 @@ async function bootstrapIO$1({ strapi: strapi2 }) {
634
634
  });
635
635
  socket.on("get-entity-subscriptions", (callback) => {
636
636
  const rooms = Array.from(socket.rooms).filter((r) => r !== socket.id && r.includes(":")).map((room) => {
637
- const [uid, id] = room.split(":");
637
+ const lastColonIndex = room.lastIndexOf(":");
638
+ const uid = room.substring(0, lastColonIndex);
639
+ const id = room.substring(lastColonIndex + 1);
638
640
  return { uid, id, room };
639
641
  });
640
642
  if (callback) callback({ success: true, subscriptions: rooms });
@@ -1316,7 +1318,7 @@ const sessionTokens = /* @__PURE__ */ new Map();
1316
1318
  const activeSockets = /* @__PURE__ */ new Map();
1317
1319
  const refreshThrottle = /* @__PURE__ */ new Map();
1318
1320
  const SESSION_TTL = 10 * 60 * 1e3;
1319
- const REFRESH_COOLDOWN = 30 * 1e3;
1321
+ const REFRESH_COOLDOWN = 3 * 1e3;
1320
1322
  const CLEANUP_INTERVAL = 2 * 60 * 1e3;
1321
1323
  const hashToken = (token) => {
1322
1324
  return createHash("sha256").update(token).digest("hex");
@@ -1367,7 +1369,7 @@ var presence$3 = ({ strapi: strapi2 }) => ({
1367
1369
  userId: adminUser.id,
1368
1370
  user: {
1369
1371
  id: adminUser.id,
1370
- // Only store minimal user data needed for display
1372
+ email: adminUser.email,
1371
1373
  firstname: adminUser.firstname,
1372
1374
  lastname: adminUser.lastname
1373
1375
  },
@@ -1530,6 +1532,32 @@ var presence$3 = ({ strapi: strapi2 }) => ({
1530
1532
  strapi2.log.error("[plugin-io] Failed to invalidate user sessions:", error2);
1531
1533
  return ctx.internalServerError("Failed to invalidate sessions");
1532
1534
  }
1535
+ },
1536
+ /**
1537
+ * HTTP Handler: Gets all online users with their editing info
1538
+ * Used for the "Who's Online" dashboard widget
1539
+ * @param {object} ctx - Koa context
1540
+ */
1541
+ async getOnlineUsers(ctx) {
1542
+ const adminUser = ctx.state.user;
1543
+ if (!adminUser) {
1544
+ return ctx.unauthorized("Admin authentication required");
1545
+ }
1546
+ try {
1547
+ const presenceService = strapi2.plugin("io").service("presence");
1548
+ const onlineUsers = presenceService.getOnlineUsers();
1549
+ const counts = presenceService.getOnlineCounts();
1550
+ ctx.body = {
1551
+ data: {
1552
+ users: onlineUsers,
1553
+ counts,
1554
+ timestamp: Date.now()
1555
+ }
1556
+ };
1557
+ } catch (error2) {
1558
+ strapi2.log.error("[plugin-io] Failed to get online users:", error2);
1559
+ return ctx.internalServerError("Failed to get online users");
1560
+ }
1533
1561
  }
1534
1562
  });
1535
1563
  const settings$2 = settings$3;
@@ -1639,6 +1667,15 @@ var admin$1 = {
1639
1667
  config: {
1640
1668
  policies: ["admin::isAuthenticatedAdmin"]
1641
1669
  }
1670
+ },
1671
+ // Who's Online: Get all online users with editing info
1672
+ {
1673
+ method: "GET",
1674
+ path: "/online-users",
1675
+ handler: "presence.getOnlineUsers",
1676
+ config: {
1677
+ policies: ["admin::isAuthenticatedAdmin"]
1678
+ }
1642
1679
  }
1643
1680
  ]
1644
1681
  };
@@ -11314,6 +11351,7 @@ const getNonVisibleAttributes = (model) => {
11314
11351
  return ___default.uniq([
11315
11352
  ID_ATTRIBUTE$4,
11316
11353
  DOC_ID_ATTRIBUTE$4,
11354
+ PUBLISHED_AT_ATTRIBUTE$1,
11317
11355
  ...getTimestamps(model),
11318
11356
  ...nonVisibleAttributes
11319
11357
  ]);
@@ -30518,7 +30556,10 @@ var presence$1 = ({ strapi: strapi2 }) => {
30518
30556
  */
30519
30557
  registerConnection(socketId, user = null) {
30520
30558
  const settings2 = getPresenceSettings();
30521
- if (!settings2.enabled) return;
30559
+ if (!settings2.enabled) {
30560
+ strapi2.log.warn(`socket.io: Presence disabled, skipping registration for ${socketId}`);
30561
+ return;
30562
+ }
30522
30563
  activeConnections.set(socketId, {
30523
30564
  user,
30524
30565
  entities: /* @__PURE__ */ new Map(),
@@ -30526,7 +30567,8 @@ var presence$1 = ({ strapi: strapi2 }) => {
30526
30567
  lastSeen: Date.now(),
30527
30568
  connectedAt: Date.now()
30528
30569
  });
30529
- strapi2.log.debug(`socket.io: Presence registered for socket ${socketId}`);
30570
+ const username = user?.username || user?.firstname || "anonymous";
30571
+ strapi2.log.info(`socket.io: Presence registered for ${username} (socket: ${socketId}, total: ${activeConnections.size})`);
30530
30572
  },
30531
30573
  /**
30532
30574
  * Unregisters a socket connection and cleans up all entity presence
@@ -30537,7 +30579,9 @@ var presence$1 = ({ strapi: strapi2 }) => {
30537
30579
  if (!connection) return;
30538
30580
  if (connection.entities) {
30539
30581
  for (const entityKey of connection.entities.keys()) {
30540
- const [uid, documentId] = entityKey.split(":");
30582
+ const lastColonIndex = entityKey.lastIndexOf(":");
30583
+ const uid = entityKey.substring(0, lastColonIndex);
30584
+ const documentId = entityKey.substring(lastColonIndex + 1);
30541
30585
  await this.leaveEntity(socketId, uid, documentId, false);
30542
30586
  }
30543
30587
  }
@@ -30772,6 +30816,90 @@ var presence$1 = ({ strapi: strapi2 }) => {
30772
30816
  timestamp: Date.now()
30773
30817
  });
30774
30818
  }
30819
+ },
30820
+ /**
30821
+ * Gets all online users with their currently editing entities
30822
+ * Used for the "Who's Online" dashboard widget
30823
+ * @returns {Array} List of online users with their editing info
30824
+ */
30825
+ getOnlineUsers() {
30826
+ const users = [];
30827
+ const now = Date.now();
30828
+ for (const [socketId, connection] of activeConnections) {
30829
+ if (!connection.user) continue;
30830
+ const editingEntities = [];
30831
+ if (connection.entities) {
30832
+ for (const [entityKey, joinedAt] of connection.entities) {
30833
+ const lastColonIndex = entityKey.lastIndexOf(":");
30834
+ const uid = entityKey.substring(0, lastColonIndex);
30835
+ const documentId = entityKey.substring(lastColonIndex + 1);
30836
+ let contentTypeName = uid;
30837
+ try {
30838
+ const contentType = strapi2.contentTypes[uid];
30839
+ if (contentType?.info?.displayName) {
30840
+ contentTypeName = contentType.info.displayName;
30841
+ } else if (contentType?.info?.singularName) {
30842
+ contentTypeName = contentType.info.singularName;
30843
+ }
30844
+ } catch (e) {
30845
+ }
30846
+ editingEntities.push({
30847
+ uid,
30848
+ documentId,
30849
+ contentTypeName,
30850
+ joinedAt,
30851
+ editingFor: Math.floor((now - joinedAt) / 1e3)
30852
+ // seconds
30853
+ });
30854
+ }
30855
+ }
30856
+ users.push({
30857
+ socketId,
30858
+ user: {
30859
+ id: connection.user.id,
30860
+ username: connection.user.username,
30861
+ email: connection.user.email,
30862
+ firstname: connection.user.firstname,
30863
+ lastname: connection.user.lastname,
30864
+ isAdmin: connection.user.isAdmin || false
30865
+ },
30866
+ connectedAt: connection.connectedAt,
30867
+ lastSeen: connection.lastSeen,
30868
+ onlineFor: Math.floor((now - connection.connectedAt) / 1e3),
30869
+ // seconds
30870
+ editingEntities,
30871
+ isEditing: editingEntities.length > 0
30872
+ });
30873
+ }
30874
+ users.sort((a, b) => {
30875
+ if (a.isEditing && !b.isEditing) return -1;
30876
+ if (!a.isEditing && b.isEditing) return 1;
30877
+ return b.connectedAt - a.connectedAt;
30878
+ });
30879
+ return users;
30880
+ },
30881
+ /**
30882
+ * Gets count of online users
30883
+ * @returns {object} Online user counts
30884
+ */
30885
+ getOnlineCounts() {
30886
+ let total = 0;
30887
+ let admins = 0;
30888
+ let users = 0;
30889
+ let editing = 0;
30890
+ for (const connection of activeConnections.values()) {
30891
+ if (!connection.user) continue;
30892
+ total++;
30893
+ if (connection.user.isAdmin) {
30894
+ admins++;
30895
+ } else {
30896
+ users++;
30897
+ }
30898
+ if (connection.entities?.size > 0) {
30899
+ editing++;
30900
+ }
30901
+ }
30902
+ return { total, admins, users, editing };
30775
30903
  }
30776
30904
  };
30777
30905
  };
@@ -30988,7 +31116,9 @@ var preview$1 = ({ strapi: strapi2 }) => {
30988
31116
  getActivePreviewEntities() {
30989
31117
  const entities = [];
30990
31118
  for (const [entityKey, subscribers] of previewSubscribers) {
30991
- const [uid, documentId] = entityKey.split(":");
31119
+ const lastColonIndex = entityKey.lastIndexOf(":");
31120
+ const uid = entityKey.substring(0, lastColonIndex);
31121
+ const documentId = entityKey.substring(lastColonIndex + 1);
30992
31122
  entities.push({
30993
31123
  uid,
30994
31124
  documentId,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@strapi-community/plugin-io",
4
- "version": "5.2.0",
4
+ "version": "5.3.1",
5
5
  "description": "A plugin for Strapi CMS that provides the ability for Socket IO integration",
6
6
  "keywords": [
7
7
  "strapi",