@memberjunction/ng-conversations 6.2.0-edge.6 → 6.2.0-edge.7

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.
Files changed (131) hide show
  1. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  2. package/dist/lib/components/collection/collection-view.component.js +1 -0
  3. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  4. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +174 -2
  5. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  6. package/dist/lib/components/conversation/conversation-chat-area.component.js +1266 -443
  7. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  8. package/dist/lib/components/conversation/conversation-mode-picker.component.js +2 -2
  9. package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -1
  10. package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts +109 -0
  11. package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts.map +1 -0
  12. package/dist/lib/components/conversation/conversation-remote-browser-controller.js +298 -0
  13. package/dist/lib/components/conversation/conversation-remote-browser-controller.js.map +1 -0
  14. package/dist/lib/components/message/agent-handoff-card.component.d.ts +102 -0
  15. package/dist/lib/components/message/agent-handoff-card.component.d.ts.map +1 -0
  16. package/dist/lib/components/message/agent-handoff-card.component.js +439 -0
  17. package/dist/lib/components/message/agent-handoff-card.component.js.map +1 -0
  18. package/dist/lib/components/message/message-input.component.d.ts +80 -53
  19. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  20. package/dist/lib/components/message/message-input.component.js +381 -277
  21. package/dist/lib/components/message/message-input.component.js.map +1 -1
  22. package/dist/lib/components/message/message-item.component.d.ts +18 -1
  23. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  24. package/dist/lib/components/message/message-item.component.js +548 -426
  25. package/dist/lib/components/message/message-item.component.js.map +1 -1
  26. package/dist/lib/components/message/message-list.component.d.ts +8 -1
  27. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  28. package/dist/lib/components/message/message-list.component.js +40 -2
  29. package/dist/lib/components/message/message-list.component.js.map +1 -1
  30. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +1 -1
  31. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -1
  32. package/dist/lib/components/realtime/media/media-channel-state.d.ts +5 -0
  33. package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -1
  34. package/dist/lib/components/realtime/media/media-channel-state.js +27 -1
  35. package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -1
  36. package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
  37. package/dist/lib/components/realtime/media/media-channel.js +7 -2
  38. package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
  39. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +19 -2
  40. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -1
  41. package/dist/lib/components/realtime/media/realtime-media-surface.component.js +59 -10
  42. package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
  43. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +17 -6
  44. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  45. package/dist/lib/components/realtime/realtime-agent-banner.component.js +61 -27
  46. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  47. package/dist/lib/components/realtime/realtime-composer.component.d.ts +26 -3
  48. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
  49. package/dist/lib/components/realtime/realtime-composer.component.js +196 -102
  50. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  51. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +53 -15
  52. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  53. package/dist/lib/components/realtime/realtime-session-overlay.component.js +125 -51
  54. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  55. package/dist/lib/components/realtime/realtime-ui-config.d.ts +9 -12
  56. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
  57. package/dist/lib/components/realtime/realtime-ui-config.js +38 -18
  58. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
  59. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  60. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +27 -4
  61. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  62. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +8 -2
  63. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
  64. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +51 -10
  65. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
  66. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts +46 -0
  67. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts.map +1 -0
  68. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js +64 -0
  69. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js.map +1 -0
  70. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +131 -9
  71. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
  72. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +347 -38
  73. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
  74. package/dist/lib/conversations.module.d.ts +2 -1
  75. package/dist/lib/conversations.module.d.ts.map +1 -1
  76. package/dist/lib/conversations.module.js +9 -4
  77. package/dist/lib/conversations.module.js.map +1 -1
  78. package/dist/lib/events/chat-events.d.ts +5 -4
  79. package/dist/lib/events/chat-events.d.ts.map +1 -1
  80. package/dist/lib/events/chat-events.js +2 -2
  81. package/dist/lib/events/chat-events.js.map +1 -1
  82. package/dist/lib/models/agent-turn.model.d.ts +11 -7
  83. package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
  84. package/dist/lib/models/agent-turn.model.js.map +1 -1
  85. package/dist/lib/services/active-tasks.service.d.ts +5 -0
  86. package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
  87. package/dist/lib/services/active-tasks.service.js +16 -0
  88. package/dist/lib/services/active-tasks.service.js.map +1 -1
  89. package/dist/lib/services/agent-state.service.d.ts.map +1 -1
  90. package/dist/lib/services/agent-state.service.js +7 -1
  91. package/dist/lib/services/agent-state.service.js.map +1 -1
  92. package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
  93. package/dist/lib/services/artifact-permission.service.js +2 -0
  94. package/dist/lib/services/artifact-permission.service.js.map +1 -1
  95. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  96. package/dist/lib/services/artifact-state.service.js +1 -0
  97. package/dist/lib/services/artifact-state.service.js.map +1 -1
  98. package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
  99. package/dist/lib/services/collection-permission.service.js +1 -0
  100. package/dist/lib/services/collection-permission.service.js.map +1 -1
  101. package/dist/lib/services/conversation-agent.service.d.ts +1 -4
  102. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  103. package/dist/lib/services/conversation-agent.service.js +7 -45
  104. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  105. package/dist/lib/services/realtime-session-review.service.d.ts +2 -1
  106. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
  107. package/dist/lib/services/realtime-session-review.service.js.map +1 -1
  108. package/dist/lib/services/search.service.d.ts.map +1 -1
  109. package/dist/lib/services/search.service.js +1 -0
  110. package/dist/lib/services/search.service.js.map +1 -1
  111. package/dist/lib/utils/agent-handoff-map.d.ts +52 -0
  112. package/dist/lib/utils/agent-handoff-map.d.ts.map +1 -0
  113. package/dist/lib/utils/agent-handoff-map.js +270 -0
  114. package/dist/lib/utils/agent-handoff-map.js.map +1 -0
  115. package/dist/lib/utils/agent-turn-routing.d.ts +62 -7
  116. package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -1
  117. package/dist/lib/utils/agent-turn-routing.js +147 -6
  118. package/dist/lib/utils/agent-turn-routing.js.map +1 -1
  119. package/dist/lib/utils/decision-routing.d.ts +17 -9
  120. package/dist/lib/utils/decision-routing.d.ts.map +1 -1
  121. package/dist/lib/utils/decision-routing.js +23 -14
  122. package/dist/lib/utils/decision-routing.js.map +1 -1
  123. package/dist/lib/utils/thread-replies.d.ts +34 -0
  124. package/dist/lib/utils/thread-replies.d.ts.map +1 -0
  125. package/dist/lib/utils/thread-replies.js +49 -0
  126. package/dist/lib/utils/thread-replies.js.map +1 -0
  127. package/dist/public-api.d.ts +5 -0
  128. package/dist/public-api.d.ts.map +1 -1
  129. package/dist/public-api.js +5 -0
  130. package/dist/public-api.js.map +1 -1
  131. package/package.json +36 -36
@@ -56,7 +56,7 @@ export class RunViewComponentArtifactSource {
56
56
  /** Loads one row by filter, mapping an empty result to `not_found` and a failed query to `load_failed`. */
57
57
  async queryOne(entityName, filter, what, orderBy) {
58
58
  const rv = RunView.FromMetadataProvider(this.provider);
59
- const result = await rv.RunView({ EntityName: entityName, ExtraFilter: filter, OrderBy: orderBy, MaxRows: 1, ResultType: 'entity_object' }, this.user);
59
+ const result = await rv.RunView({ EntityName: entityName, ExtraFilter: filter, OrderBy: orderBy, MaxRows: 1, SkipTotalRowCount: true, ResultType: 'entity_object' }, this.user);
60
60
  if (!result.Success) {
61
61
  LogError(`[InteractiveComponent] Loading ${what} failed: ${result.ErrorMessage}`);
62
62
  throw new ComponentArtifactError('load_failed', `The ${what} could not be loaded.`);
@@ -1 +1 @@
1
- {"version":3,"file":"run-view-component-artifact-source.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/interactive-component/run-view-component-artifact-source.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview The database-backed {@link IComponentArtifactSource}: loads component artifacts through the\n * SESSION'S OWN provider and the signed-in user's own read access.\n *\n * Nothing here widens access. The channel runs in the user's browser, the provider is the one that\n * authenticates their session, and an artifact is loaded only if the user may read it by the same rules the\n * rest of the conversation UI applies (owner, an explicit grant, or a collection they can read).\n *\n * @module @memberjunction/ng-conversations\n */\n\nimport { IMetadataProvider, LogError, RunView, UserInfo } from '@memberjunction/core';\nimport { MJArtifactEntity, MJArtifactVersionEntity } from '@memberjunction/core-entities';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { ArtifactPermissionService } from '../../../services/artifact-permission.service';\nimport { CollectionPermissionService } from '../../../services/collection-permission.service';\nimport { ParseComponentSpecContent } from './component-spec-content';\nimport {\n ComponentArtifactError,\n type IComponentArtifactSource,\n type ResolvedComponentArtifact,\n} from './interactive-component-types';\n\n/** A UUID in any case, with or without braces. Ids reach SQL filters, so anything else is refused before it is built into one. */\nconst UUID_PATTERN = /^\\{?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}?$/i;\n\n/** The artifact type name that marks a component artifact. */\nconst COMPONENT_ARTIFACT_TYPE = 'Component';\n\n/** Validates an id the model supplied. */\nfunction requireUuid(value: string, what: string): string {\n const trimmed = value.trim();\n if (!UUID_PATTERN.test(trimmed)) {\n throw new ComponentArtifactError('invalid_id', `\"${value}\" is not a valid ${what} id.`);\n }\n return trimmed.replace(/[{}]/g, '');\n}\n\n/** Loads component artifacts with the user's own access. */\nexport class RunViewComponentArtifactSource implements IComponentArtifactSource {\n private readonly permissions: ArtifactPermissionService;\n\n /**\n * @param provider The session's metadata provider (the one that authenticates the user).\n * @param user The signed-in user.\n */\n public constructor(\n private readonly provider: IMetadataProvider,\n private readonly user: UserInfo\n ) {\n this.permissions = new ArtifactPermissionService(new CollectionPermissionService());\n this.permissions.Provider = provider;\n }\n\n /** @inheritdoc */\n public async LoadVersion(versionId: string): Promise<ResolvedComponentArtifact> {\n const id = requireUuid(versionId, 'artifact version');\n const version = await this.queryOne<MJArtifactVersionEntity>('MJ: Artifact Versions', `ID='${id}'`, 'artifact version');\n return this.resolve(version);\n }\n\n /** @inheritdoc */\n public async LoadLatest(artifactId: string): Promise<ResolvedComponentArtifact> {\n const id = requireUuid(artifactId, 'artifact');\n const version = await this.queryOne<MJArtifactVersionEntity>(\n 'MJ: Artifact Versions',\n `ArtifactID='${id}'`,\n 'artifact',\n 'VersionNumber DESC'\n );\n return this.resolve(version);\n }\n\n /** Loads one row by filter, mapping an empty result to `not_found` and a failed query to `load_failed`. */\n private async queryOne<T>(entityName: string, filter: string, what: string, orderBy?: string): Promise<T> {\n const rv = RunView.FromMetadataProvider(this.provider);\n const result = await rv.RunView<T>(\n { EntityName: entityName, ExtraFilter: filter, OrderBy: orderBy, MaxRows: 1, ResultType: 'entity_object' },\n this.user\n );\n if (!result.Success) {\n LogError(`[InteractiveComponent] Loading ${what} failed: ${result.ErrorMessage}`);\n throw new ComponentArtifactError('load_failed', `The ${what} could not be loaded.`);\n }\n if (!result.Results || result.Results.length === 0) {\n throw new ComponentArtifactError('not_found', `No ${what} with that id exists, or you cannot see it.`);\n }\n return result.Results[0];\n }\n\n /** Checks access and type, then parses the content. */\n private async resolve(version: MJArtifactVersionEntity): Promise<ResolvedComponentArtifact> {\n const artifactId = version.ArtifactID;\n const canRead = await this.permissions.CheckPermission(artifactId, this.user.ID, 'read', this.user);\n if (!canRead) {\n throw new ComponentArtifactError('access_denied', 'You do not have access to that artifact.');\n }\n const artifact = await this.queryOne<MJArtifactEntity>('MJ: Artifacts', `ID='${requireUuid(artifactId, 'artifact')}'`, 'artifact');\n if (!UUIDsEqual(artifact.ID, artifactId) || artifact.Type !== COMPONENT_ARTIFACT_TYPE) {\n throw new ComponentArtifactError('not_a_component', `\"${artifact.Name}\" is a ${artifact.Type} artifact, not an interactive component.`);\n }\n const name = version.Name ?? artifact.Name;\n return {\n ArtifactID: artifact.ID,\n VersionID: version.ID,\n VersionNumber: version.VersionNumber,\n Name: name,\n Spec: ParseComponentSpecContent(version.Content, `\"${name}\"`),\n };\n }\n}\n"]}
1
+ {"version":3,"file":"run-view-component-artifact-source.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/interactive-component/run-view-component-artifact-source.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview The database-backed {@link IComponentArtifactSource}: loads component artifacts through the\n * SESSION'S OWN provider and the signed-in user's own read access.\n *\n * Nothing here widens access. The channel runs in the user's browser, the provider is the one that\n * authenticates their session, and an artifact is loaded only if the user may read it by the same rules the\n * rest of the conversation UI applies (owner, an explicit grant, or a collection they can read).\n *\n * @module @memberjunction/ng-conversations\n */\n\nimport { IMetadataProvider, LogError, RunView, UserInfo } from '@memberjunction/core';\nimport { MJArtifactEntity, MJArtifactVersionEntity } from '@memberjunction/core-entities';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { ArtifactPermissionService } from '../../../services/artifact-permission.service';\nimport { CollectionPermissionService } from '../../../services/collection-permission.service';\nimport { ParseComponentSpecContent } from './component-spec-content';\nimport {\n ComponentArtifactError,\n type IComponentArtifactSource,\n type ResolvedComponentArtifact,\n} from './interactive-component-types';\n\n/** A UUID in any case, with or without braces. Ids reach SQL filters, so anything else is refused before it is built into one. */\nconst UUID_PATTERN = /^\\{?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}?$/i;\n\n/** The artifact type name that marks a component artifact. */\nconst COMPONENT_ARTIFACT_TYPE = 'Component';\n\n/** Validates an id the model supplied. */\nfunction requireUuid(value: string, what: string): string {\n const trimmed = value.trim();\n if (!UUID_PATTERN.test(trimmed)) {\n throw new ComponentArtifactError('invalid_id', `\"${value}\" is not a valid ${what} id.`);\n }\n return trimmed.replace(/[{}]/g, '');\n}\n\n/** Loads component artifacts with the user's own access. */\nexport class RunViewComponentArtifactSource implements IComponentArtifactSource {\n private readonly permissions: ArtifactPermissionService;\n\n /**\n * @param provider The session's metadata provider (the one that authenticates the user).\n * @param user The signed-in user.\n */\n public constructor(\n private readonly provider: IMetadataProvider,\n private readonly user: UserInfo\n ) {\n this.permissions = new ArtifactPermissionService(new CollectionPermissionService());\n this.permissions.Provider = provider;\n }\n\n /** @inheritdoc */\n public async LoadVersion(versionId: string): Promise<ResolvedComponentArtifact> {\n const id = requireUuid(versionId, 'artifact version');\n const version = await this.queryOne<MJArtifactVersionEntity>('MJ: Artifact Versions', `ID='${id}'`, 'artifact version');\n return this.resolve(version);\n }\n\n /** @inheritdoc */\n public async LoadLatest(artifactId: string): Promise<ResolvedComponentArtifact> {\n const id = requireUuid(artifactId, 'artifact');\n const version = await this.queryOne<MJArtifactVersionEntity>(\n 'MJ: Artifact Versions',\n `ArtifactID='${id}'`,\n 'artifact',\n 'VersionNumber DESC'\n );\n return this.resolve(version);\n }\n\n /** Loads one row by filter, mapping an empty result to `not_found` and a failed query to `load_failed`. */\n private async queryOne<T>(entityName: string, filter: string, what: string, orderBy?: string): Promise<T> {\n const rv = RunView.FromMetadataProvider(this.provider);\n const result = await rv.RunView<T>(\n { EntityName: entityName, ExtraFilter: filter, OrderBy: orderBy, MaxRows: 1, SkipTotalRowCount: true, ResultType: 'entity_object' },\n this.user\n );\n if (!result.Success) {\n LogError(`[InteractiveComponent] Loading ${what} failed: ${result.ErrorMessage}`);\n throw new ComponentArtifactError('load_failed', `The ${what} could not be loaded.`);\n }\n if (!result.Results || result.Results.length === 0) {\n throw new ComponentArtifactError('not_found', `No ${what} with that id exists, or you cannot see it.`);\n }\n return result.Results[0];\n }\n\n /** Checks access and type, then parses the content. */\n private async resolve(version: MJArtifactVersionEntity): Promise<ResolvedComponentArtifact> {\n const artifactId = version.ArtifactID;\n const canRead = await this.permissions.CheckPermission(artifactId, this.user.ID, 'read', this.user);\n if (!canRead) {\n throw new ComponentArtifactError('access_denied', 'You do not have access to that artifact.');\n }\n const artifact = await this.queryOne<MJArtifactEntity>('MJ: Artifacts', `ID='${requireUuid(artifactId, 'artifact')}'`, 'artifact');\n if (!UUIDsEqual(artifact.ID, artifactId) || artifact.Type !== COMPONENT_ARTIFACT_TYPE) {\n throw new ComponentArtifactError('not_a_component', `\"${artifact.Name}\" is a ${artifact.Type} artifact, not an interactive component.`);\n }\n const name = version.Name ?? artifact.Name;\n return {\n ArtifactID: artifact.ID,\n VersionID: version.ID,\n VersionNumber: version.VersionNumber,\n Name: name,\n Spec: ParseComponentSpecContent(version.Content, `\"${name}\"`),\n };\n }\n}\n"]}
@@ -46,6 +46,11 @@ export interface MediaItem {
46
46
  /** Set transiently to request playback of a video/audio item (consumed by the surface). */
47
47
  PlayRequestedAt?: number;
48
48
  }
49
+ /**
50
+ * Validates that an external/public media URL uses an explicit `http:` or `https:` scheme with a
51
+ * non-empty host (never `javascript:`, `data:`, `blob:`, `file:`, or scheme-relative `//...`).
52
+ */
53
+ export declare function IsAllowedExternalMediaUrl(url: string | null | undefined): boolean;
49
54
  /**
50
55
  * The Media channel's STATE ENGINE — a pure, framework-free model of what's on the shared Media
51
56
  * surface: an ordered list of {@link MediaItem}s plus which one is active. Mutations push the new
@@ -1 +1 @@
1
- {"version":3,"file":"media-channel-state.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAEvC,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,CAAC;AAExE;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,+CAA+C;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,2CAA2C;IAC3C,CAAC,EAAE,MAAM,CAAC;IACV,6CAA6C;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,gGAAgG;AAChG,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,IAAI,EAAE,aAAa,CAAC;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAYD;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,uEAAuE;IAChE,KAAK,EAAE,SAAS,EAAE,CAAM;IAE/B,gFAAgF;IACzE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACH,SAAgB,QAAQ,qCAAgD;IAExE,0DAA0D;IACnD,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAQxE,gHAAgH;IACzG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAatC,oDAAoD;IAC7C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IASrC;;;OAGG;IACI,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAWvC,oGAAoG;IAC7F,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO;IAUvE,oDAAoD;IAC7C,KAAK,IAAI,IAAI;IASpB,qFAAqF;IACrF,IAAW,UAAU,IAAI,SAAS,GAAG,IAAI,CAExC;IAED,iGAAiG;IAC1F,MAAM,IAAI,MAAM;IAKvB;;;;OAIG;IACI,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAiB1C;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAiBnB,6CAA6C;IAC7C,OAAO,CAAC,KAAK;IAKb,+CAA+C;IAC/C,OAAO,CAAC,MAAM;CAGf"}
1
+ {"version":3,"file":"media-channel-state.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAEvC,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,CAAC;AAExE;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,+CAA+C;IAC/C,CAAC,EAAE,MAAM,CAAC;IACV,2CAA2C;IAC3C,CAAC,EAAE,MAAM,CAAC;IACV,6CAA6C;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,gGAAgG;AAChG,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,IAAI,EAAE,aAAa,CAAC;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAYD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAcjF;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,uEAAuE;IAChE,KAAK,EAAE,SAAS,EAAE,CAAM;IAE/B,gFAAgF;IACzE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C;;;OAGG;IACH,SAAgB,QAAQ,qCAAgD;IAExE,0DAA0D;IACnD,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAQxE,gHAAgH;IACzG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAatC,oDAAoD;IAC7C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IASrC;;;OAGG;IACI,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAWvC,oGAAoG;IAC7F,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO;IAUvE,oDAAoD;IAC7C,KAAK,IAAI,IAAI;IASpB,qFAAqF;IACrF,IAAW,UAAU,IAAI,SAAS,GAAG,IAAI,CAExC;IAED,iGAAiG;IAC1F,MAAM,IAAI,MAAM;IAKvB;;;;OAIG;IACI,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAiB1C;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAuBnB,6CAA6C;IAC7C,OAAO,CAAC,KAAK;IAKb,+CAA+C;IAC/C,OAAO,CAAC,MAAM;CAGf"}
@@ -1,6 +1,26 @@
1
1
  import { BehaviorSubject } from 'rxjs';
2
2
  /** Monotonic counter feeding {@link MediaChannelState.newId} so ids stay unique within a session. */
3
3
  let mediaIdCounter = 0;
4
+ /**
5
+ * Validates that an external/public media URL uses an explicit `http:` or `https:` scheme with a
6
+ * non-empty host (never `javascript:`, `data:`, `blob:`, `file:`, or scheme-relative `//...`).
7
+ */
8
+ export function IsAllowedExternalMediaUrl(url) {
9
+ if (typeof url !== 'string') {
10
+ return false;
11
+ }
12
+ const trimmed = url.trim();
13
+ if (!trimmed || trimmed.startsWith('//') || !/^https?:\/\//i.test(trimmed)) {
14
+ return false;
15
+ }
16
+ try {
17
+ const parsed = new URL(trimmed);
18
+ return (parsed.protocol === 'http:' || parsed.protocol === 'https:') && Boolean(parsed.hostname);
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
4
24
  /**
5
25
  * The Media channel's STATE ENGINE — a pure, framework-free model of what's on the shared Media
6
26
  * surface: an ordered list of {@link MediaItem}s plus which one is active. Mutations push the new
@@ -122,7 +142,13 @@ export class MediaChannelState {
122
142
  }
123
143
  const item = value;
124
144
  const validTypes = ['image', 'video', 'audio', 'pdf', 'web'];
125
- const hasUrl = typeof item['Url'] === 'string' && item['Url'].length > 0;
145
+ const rawUrl = item['Url'];
146
+ if (rawUrl !== undefined && rawUrl !== null && rawUrl !== '') {
147
+ if (typeof rawUrl !== 'string' || !IsAllowedExternalMediaUrl(rawUrl)) {
148
+ return false;
149
+ }
150
+ }
151
+ const hasUrl = typeof rawUrl === 'string' && rawUrl.trim().length > 0;
126
152
  const hasFileID = typeof item['FileID'] === 'string' && item['FileID'].length > 0;
127
153
  return (typeof item['Id'] === 'string' &&
128
154
  (hasUrl || hasFileID) &&
@@ -1 +1 @@
1
- {"version":3,"file":"media-channel-state.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { BehaviorSubject } from 'rxjs';\n\n/** The media kinds the Media surface can render — one render path per value. */\nexport type MediaItemType = 'image' | 'video' | 'audio' | 'pdf' | 'web';\n\n/**\n * A fractional highlight rectangle drawn over a media item, in the item's OWN coordinate space\n * (0..1 on both axes). `X`,`Y` is the top-left corner; `W`,`H` are the width/height. Fractional\n * coordinates keep the highlight correct regardless of how the media is scaled to fit the pane.\n */\nexport interface MediaHighlight {\n /** Left edge as a fraction of width (0..1). */\n X: number;\n /** Top edge as a fraction of height (0..1). */\n Y: number;\n /** Width as a fraction of width (0..1). */\n W: number;\n /** Height as a fraction of height (0..1). */\n H: number;\n /** Optional short label rendered on the highlight. */\n Label?: string;\n}\n\n/** One piece of media on the surface — a tab + a render pane keyed by {@link MediaItemType}. */\nexport interface MediaItem {\n /** Stable id (returned to the agent by `Media_ShowMedia`, used by close/play/highlight). */\n Id: string;\n /** Which render path the pane uses. */\n Type: MediaItemType;\n /**\n * Absolute URL of the media — for an EXTERNAL/public asset. Optional/empty when {@link FileID}\n * is set (the surface resolves a secure streaming URL from the file id instead). At least one of\n * `Url` / `FileID` is always present.\n */\n Url?: string;\n /**\n * Id of an \"MJ: Files\" record this item is backed by. When set, the item is streamed securely\n * (permission-gated) from MJStorage rather than from a public {@link Url}; the surface resolves a\n * short-lived streaming URL (via `CreateMediaAccessToken`) or hands the id straight to\n * `mj-storage-media-player` for audio/video.\n */\n FileID?: string;\n /** Short tab label. */\n DisplayName: string;\n /** Optional one-line caption beneath the media. */\n Caption?: string;\n /** Optional fractional highlight overlay. */\n Highlight?: MediaHighlight;\n /** Set transiently to request playback of a video/audio item (consumed by the surface). */\n PlayRequestedAt?: number;\n}\n\n/** The serialized shape persisted as the channel's state of record (versioned for forward-compat). */\ninterface SerializedMediaState {\n V: 1;\n Items: MediaItem[];\n ActiveItemId: string | null;\n}\n\n/** Monotonic counter feeding {@link MediaChannelState.newId} so ids stay unique within a session. */\nlet mediaIdCounter = 0;\n\n/**\n * The Media channel's STATE ENGINE — a pure, framework-free model of what's on the shared Media\n * surface: an ordered list of {@link MediaItem}s plus which one is active. Mutations push the new\n * state through {@link Changed$} so the channel persists it and the surface re-renders. Mirrors\n * `WhiteboardState` (no Angular, tolerant (de)serialization, single `Changed$` notifier).\n */\nexport class MediaChannelState {\n /** The media currently on the surface, in tab order (oldest first). */\n public Items: MediaItem[] = [];\n\n /** The id of the active (visible) item, or `null` when the surface is empty. */\n public ActiveItemId: string | null = null;\n\n /**\n * Fires on every mutation. Replays the current state on subscribe (BehaviorSubject), so a late\n * surface binding immediately renders what's already on the board.\n */\n public readonly Changed$ = new BehaviorSubject<MediaChannelState>(this);\n\n /** Adds a media item, makes it active, and returns it. */\n public AddItem(item: Omit<MediaItem, 'Id'> & { Id?: string }): MediaItem {\n const created: MediaItem = { ...item, Id: item.Id ?? this.newId() };\n this.Items = [...this.Items, created];\n this.ActiveItemId = created.Id;\n this.notify();\n return created;\n }\n\n /** Removes the item with `id` (no-op if absent). When it was active, the last remaining item becomes active. */\n public RemoveItem(id: string): boolean {\n const next = this.Items.filter((i) => i.Id !== id);\n if (next.length === this.Items.length) {\n return false;\n }\n this.Items = next;\n if (this.ActiveItemId === id) {\n this.ActiveItemId = next.length > 0 ? next[next.length - 1].Id : null;\n }\n this.notify();\n return true;\n }\n\n /** Makes `id` the active item (no-op if absent). */\n public SetActive(id: string): boolean {\n if (!this.Items.some((i) => i.Id === id) || this.ActiveItemId === id) {\n return this.ActiveItemId === id;\n }\n this.ActiveItemId = id;\n this.notify();\n return true;\n }\n\n /**\n * Activates `id` and stamps a play request on it (the surface consumes the stamp to start\n * video/audio playback). No-op if the item is absent.\n */\n public RequestPlay(id: string): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.ActiveItemId = id;\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, PlayRequestedAt: Date.now() } : i));\n this.notify();\n return true;\n }\n\n /** Sets (or, with `null`, clears) the fractional highlight on `id`. No-op if the item is absent. */\n public Highlight(id: string, highlight: MediaHighlight | null): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, Highlight: highlight ?? undefined } : i));\n this.notify();\n return true;\n }\n\n /** Removes all media and resets the active item. */\n public Clear(): void {\n if (this.Items.length === 0 && this.ActiveItemId === null) {\n return;\n }\n this.Items = [];\n this.ActiveItemId = null;\n this.notify();\n }\n\n /** The active item, or `null` when the surface is empty / the active id is stale. */\n public get ActiveItem(): MediaItem | null {\n return this.Items.find((i) => i.Id === this.ActiveItemId) ?? null;\n }\n\n /** Serializes the state of record to a JSON string (persisted via the channel's RequestSave). */\n public ToJSON(): string {\n const payload: SerializedMediaState = { V: 1, Items: this.Items, ActiveItemId: this.ActiveItemId };\n return JSON.stringify(payload);\n }\n\n /**\n * Rehydrates a prior session's saved state IN PLACE (preserving this instance + its `Changed$`\n * subscribers). Tolerant by contract: malformed / incompatible JSON is ignored and the method\n * returns `false` without throwing, leaving the current state untouched.\n */\n public LoadFromJSON(json: string): boolean {\n try {\n const parsed = JSON.parse(json) as Partial<SerializedMediaState> | null;\n if (!parsed || !Array.isArray(parsed.Items)) {\n return false;\n }\n const items = parsed.Items.filter((i): i is MediaItem => this.isValidItem(i));\n this.Items = items;\n const active = typeof parsed.ActiveItemId === 'string' ? parsed.ActiveItemId : null;\n this.ActiveItemId = active && items.some((i) => i.Id === active) ? active : items.length > 0 ? items[items.length - 1].Id : null;\n this.notify();\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Validates one parsed item enough to render it safely (defensive against hand-edited / old\n * payloads). An item must carry a SOURCE — a non-empty `Url` OR a non-empty `FileID` — since one\n * of the two backs every render path.\n */\n private isValidItem(value: unknown): value is MediaItem {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const item = value as Record<string, unknown>;\n const validTypes: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n const hasUrl = typeof item['Url'] === 'string' && (item['Url'] as string).length > 0;\n const hasFileID = typeof item['FileID'] === 'string' && (item['FileID'] as string).length > 0;\n return (\n typeof item['Id'] === 'string' &&\n (hasUrl || hasFileID) &&\n typeof item['DisplayName'] === 'string' &&\n typeof item['Type'] === 'string' &&\n validTypes.includes(item['Type'] as MediaItemType)\n );\n }\n\n /** Mints a unique-within-session item id. */\n private newId(): string {\n mediaIdCounter += 1;\n return `media-${Date.now().toString(36)}-${mediaIdCounter}`;\n }\n\n /** Pushes the current state to subscribers. */\n private notify(): void {\n this.Changed$.next(this);\n }\n}\n"]}
1
+ {"version":3,"file":"media-channel-state.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { BehaviorSubject } from 'rxjs';\n\n/** The media kinds the Media surface can render — one render path per value. */\nexport type MediaItemType = 'image' | 'video' | 'audio' | 'pdf' | 'web';\n\n/**\n * A fractional highlight rectangle drawn over a media item, in the item's OWN coordinate space\n * (0..1 on both axes). `X`,`Y` is the top-left corner; `W`,`H` are the width/height. Fractional\n * coordinates keep the highlight correct regardless of how the media is scaled to fit the pane.\n */\nexport interface MediaHighlight {\n /** Left edge as a fraction of width (0..1). */\n X: number;\n /** Top edge as a fraction of height (0..1). */\n Y: number;\n /** Width as a fraction of width (0..1). */\n W: number;\n /** Height as a fraction of height (0..1). */\n H: number;\n /** Optional short label rendered on the highlight. */\n Label?: string;\n}\n\n/** One piece of media on the surface — a tab + a render pane keyed by {@link MediaItemType}. */\nexport interface MediaItem {\n /** Stable id (returned to the agent by `Media_ShowMedia`, used by close/play/highlight). */\n Id: string;\n /** Which render path the pane uses. */\n Type: MediaItemType;\n /**\n * Absolute URL of the media — for an EXTERNAL/public asset. Optional/empty when {@link FileID}\n * is set (the surface resolves a secure streaming URL from the file id instead). At least one of\n * `Url` / `FileID` is always present.\n */\n Url?: string;\n /**\n * Id of an \"MJ: Files\" record this item is backed by. When set, the item is streamed securely\n * (permission-gated) from MJStorage rather than from a public {@link Url}; the surface resolves a\n * short-lived streaming URL (via `CreateMediaAccessToken`) or hands the id straight to\n * `mj-storage-media-player` for audio/video.\n */\n FileID?: string;\n /** Short tab label. */\n DisplayName: string;\n /** Optional one-line caption beneath the media. */\n Caption?: string;\n /** Optional fractional highlight overlay. */\n Highlight?: MediaHighlight;\n /** Set transiently to request playback of a video/audio item (consumed by the surface). */\n PlayRequestedAt?: number;\n}\n\n/** The serialized shape persisted as the channel's state of record (versioned for forward-compat). */\ninterface SerializedMediaState {\n V: 1;\n Items: MediaItem[];\n ActiveItemId: string | null;\n}\n\n/** Monotonic counter feeding {@link MediaChannelState.newId} so ids stay unique within a session. */\nlet mediaIdCounter = 0;\n\n/**\n * Validates that an external/public media URL uses an explicit `http:` or `https:` scheme with a\n * non-empty host (never `javascript:`, `data:`, `blob:`, `file:`, or scheme-relative `//...`).\n */\nexport function IsAllowedExternalMediaUrl(url: string | null | undefined): boolean {\n if (typeof url !== 'string') {\n return false;\n }\n const trimmed = url.trim();\n if (!trimmed || trimmed.startsWith('//') || !/^https?:\\/\\//i.test(trimmed)) {\n return false;\n }\n try {\n const parsed = new URL(trimmed);\n return (parsed.protocol === 'http:' || parsed.protocol === 'https:') && Boolean(parsed.hostname);\n } catch {\n return false;\n }\n}\n\n/**\n * The Media channel's STATE ENGINE — a pure, framework-free model of what's on the shared Media\n * surface: an ordered list of {@link MediaItem}s plus which one is active. Mutations push the new\n * state through {@link Changed$} so the channel persists it and the surface re-renders. Mirrors\n * `WhiteboardState` (no Angular, tolerant (de)serialization, single `Changed$` notifier).\n */\nexport class MediaChannelState {\n /** The media currently on the surface, in tab order (oldest first). */\n public Items: MediaItem[] = [];\n\n /** The id of the active (visible) item, or `null` when the surface is empty. */\n public ActiveItemId: string | null = null;\n\n /**\n * Fires on every mutation. Replays the current state on subscribe (BehaviorSubject), so a late\n * surface binding immediately renders what's already on the board.\n */\n public readonly Changed$ = new BehaviorSubject<MediaChannelState>(this);\n\n /** Adds a media item, makes it active, and returns it. */\n public AddItem(item: Omit<MediaItem, 'Id'> & { Id?: string }): MediaItem {\n const created: MediaItem = { ...item, Id: item.Id ?? this.newId() };\n this.Items = [...this.Items, created];\n this.ActiveItemId = created.Id;\n this.notify();\n return created;\n }\n\n /** Removes the item with `id` (no-op if absent). When it was active, the last remaining item becomes active. */\n public RemoveItem(id: string): boolean {\n const next = this.Items.filter((i) => i.Id !== id);\n if (next.length === this.Items.length) {\n return false;\n }\n this.Items = next;\n if (this.ActiveItemId === id) {\n this.ActiveItemId = next.length > 0 ? next[next.length - 1].Id : null;\n }\n this.notify();\n return true;\n }\n\n /** Makes `id` the active item (no-op if absent). */\n public SetActive(id: string): boolean {\n if (!this.Items.some((i) => i.Id === id) || this.ActiveItemId === id) {\n return this.ActiveItemId === id;\n }\n this.ActiveItemId = id;\n this.notify();\n return true;\n }\n\n /**\n * Activates `id` and stamps a play request on it (the surface consumes the stamp to start\n * video/audio playback). No-op if the item is absent.\n */\n public RequestPlay(id: string): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.ActiveItemId = id;\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, PlayRequestedAt: Date.now() } : i));\n this.notify();\n return true;\n }\n\n /** Sets (or, with `null`, clears) the fractional highlight on `id`. No-op if the item is absent. */\n public Highlight(id: string, highlight: MediaHighlight | null): boolean {\n const item = this.Items.find((i) => i.Id === id);\n if (!item) {\n return false;\n }\n this.Items = this.Items.map((i) => (i.Id === id ? { ...i, Highlight: highlight ?? undefined } : i));\n this.notify();\n return true;\n }\n\n /** Removes all media and resets the active item. */\n public Clear(): void {\n if (this.Items.length === 0 && this.ActiveItemId === null) {\n return;\n }\n this.Items = [];\n this.ActiveItemId = null;\n this.notify();\n }\n\n /** The active item, or `null` when the surface is empty / the active id is stale. */\n public get ActiveItem(): MediaItem | null {\n return this.Items.find((i) => i.Id === this.ActiveItemId) ?? null;\n }\n\n /** Serializes the state of record to a JSON string (persisted via the channel's RequestSave). */\n public ToJSON(): string {\n const payload: SerializedMediaState = { V: 1, Items: this.Items, ActiveItemId: this.ActiveItemId };\n return JSON.stringify(payload);\n }\n\n /**\n * Rehydrates a prior session's saved state IN PLACE (preserving this instance + its `Changed$`\n * subscribers). Tolerant by contract: malformed / incompatible JSON is ignored and the method\n * returns `false` without throwing, leaving the current state untouched.\n */\n public LoadFromJSON(json: string): boolean {\n try {\n const parsed = JSON.parse(json) as Partial<SerializedMediaState> | null;\n if (!parsed || !Array.isArray(parsed.Items)) {\n return false;\n }\n const items = parsed.Items.filter((i): i is MediaItem => this.isValidItem(i));\n this.Items = items;\n const active = typeof parsed.ActiveItemId === 'string' ? parsed.ActiveItemId : null;\n this.ActiveItemId = active && items.some((i) => i.Id === active) ? active : items.length > 0 ? items[items.length - 1].Id : null;\n this.notify();\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Validates one parsed item enough to render it safely (defensive against hand-edited / old\n * payloads). An item must carry a SOURCE — a non-empty `Url` OR a non-empty `FileID` — since one\n * of the two backs every render path.\n */\n private isValidItem(value: unknown): value is MediaItem {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const item = value as Record<string, unknown>;\n const validTypes: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n const rawUrl = item['Url'];\n if (rawUrl !== undefined && rawUrl !== null && rawUrl !== '') {\n if (typeof rawUrl !== 'string' || !IsAllowedExternalMediaUrl(rawUrl)) {\n return false;\n }\n }\n const hasUrl = typeof rawUrl === 'string' && rawUrl.trim().length > 0;\n const hasFileID = typeof item['FileID'] === 'string' && (item['FileID'] as string).length > 0;\n return (\n typeof item['Id'] === 'string' &&\n (hasUrl || hasFileID) &&\n typeof item['DisplayName'] === 'string' &&\n typeof item['Type'] === 'string' &&\n validTypes.includes(item['Type'] as MediaItemType)\n );\n }\n\n /** Mints a unique-within-session item id. */\n private newId(): string {\n mediaIdCounter += 1;\n return `media-${Date.now().toString(36)}-${mediaIdCounter}`;\n }\n\n /** Pushes the current state to subscribers. */\n private notify(): void {\n this.Changed$.next(this);\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"media-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAqC,KAAK,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACjH,OAAO,EAAE,yBAAyB,EAAwB,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,EAAE,iBAAiB,EAAiC,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAkBnF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,oBAAqB,SAAQ,yBAAyB,CAAC,6BAA6B,CAAC;IAChG,mFAAmF;IACnF,SAAgB,KAAK,oBAA2B;IAEhD,2EAA2E;IAC3E,OAAO,CAAC,OAAO,CAA8C;IAC7D,8EAA8E;IAC9E,OAAO,CAAC,eAAe,CAA6B;IAEpD,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,IAAW,cAAc,IAAI,MAAM,CAElC;IAED,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED,IAAW,OAAO,IAAI,MAAM,CAE3B;IAEM,kBAAkB,IAAI,sBAAsB,EAAE;IAIrD;;;;OAIG;IACa,aAAa,IAAI,yBAAyB;IAuB1C,mBAAmB,IAAI,IAAI,CAAC,6BAA6B,CAAC;IAI1E,yFAAyF;IACzE,oBAAoB,IAAI,wBAAwB;IAgBhE,kGAAkG;cAC/E,YAAY,IAAI,IAAI;IAMvC;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,6BAA6B,GAAG,IAAI;IAOjD,aAAa,IAAI,IAAI;IAIrC;;;;;;OAMG;IACI,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IA2BjE,+GAA+G;IAC/G,OAAO,CAAC,cAAc;IAyBtB,2BAA2B;IAC3B,OAAO,CAAC,eAAe;IAavB,qDAAqD;IACrD,OAAO,CAAC,cAAc;IAatB,mFAAmF;IACnF,OAAO,CAAC,cAAc;IAqBtB,wBAAwB;IACxB,OAAO,CAAC,aAAa;IAMrB,4FAA4F;IAC5E,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/C;;;;OAIG;IACa,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxC,OAAO,IAAI,IAAI;IAO/B,sFAAsF;IACtF,OAAO,CAAC,IAAI;IAIZ;;;OAGG;IACH,OAAO,CAAC,IAAI;CAKb;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
1
+ {"version":3,"file":"media-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAqC,KAAK,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AACjH,OAAO,EAAE,yBAAyB,EAAwB,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,EAA6B,iBAAiB,EAAiC,MAAM,uBAAuB,CAAC;AACpH,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAkBnF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,oBAAqB,SAAQ,yBAAyB,CAAC,6BAA6B,CAAC;IAChG,mFAAmF;IACnF,SAAgB,KAAK,oBAA2B;IAEhD,2EAA2E;IAC3E,OAAO,CAAC,OAAO,CAA8C;IAC7D,8EAA8E;IAC9E,OAAO,CAAC,eAAe,CAA6B;IAEpD,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,IAAW,cAAc,IAAI,MAAM,CAElC;IAED,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED,IAAW,OAAO,IAAI,MAAM,CAE3B;IAEM,kBAAkB,IAAI,sBAAsB,EAAE;IAIrD;;;;OAIG;IACa,aAAa,IAAI,yBAAyB;IAuB1C,mBAAmB,IAAI,IAAI,CAAC,6BAA6B,CAAC;IAI1E,yFAAyF;IACzE,oBAAoB,IAAI,wBAAwB;IAgBhE,kGAAkG;cAC/E,YAAY,IAAI,IAAI;IAMvC;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,6BAA6B,GAAG,IAAI;IAOjD,aAAa,IAAI,IAAI;IAIrC;;;;;;OAMG;IACI,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IA2BjE,+GAA+G;IAC/G,OAAO,CAAC,cAAc;IA8BtB,2BAA2B;IAC3B,OAAO,CAAC,eAAe;IAavB,qDAAqD;IACrD,OAAO,CAAC,cAAc;IAatB,mFAAmF;IACnF,OAAO,CAAC,cAAc;IAqBtB,wBAAwB;IACxB,OAAO,CAAC,aAAa;IAMrB,4FAA4F;IAC5E,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/C;;;;OAIG;IACa,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxC,OAAO,IAAI,IAAI;IAO/B,sFAAsF;IACtF,OAAO,CAAC,IAAI;IAIZ;;;OAGG;IACH,OAAO,CAAC,IAAI;CAKb;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
@@ -8,7 +8,7 @@ import { RegisterClass } from '@memberjunction/global';
8
8
  import { REALTIME_CHANNEL_CONTRACT_VERSION } from '@memberjunction/ai-core-plus';
9
9
  import { BaseRealtimeChannelClient, BuildToolBackedVerbs } from '@memberjunction/realtime-runtime';
10
10
  import { MEDIA_TOOL_DEFINITIONS, MEDIA_TOOL_NAMES, MEDIA_TOOL_PREFIX } from './media-channel-tools';
11
- import { MediaChannelState } from './media-channel-state';
11
+ import { IsAllowedExternalMediaUrl, MediaChannelState } from './media-channel-state';
12
12
  import { RealtimeMediaSurfaceComponent } from './realtime-media-surface.component';
13
13
  /** Coerces a parsed arg to a non-empty string, or `undefined` when absent / wrong-typed. */
14
14
  function asString(value) {
@@ -169,12 +169,17 @@ let RealtimeMediaChannel = class RealtimeMediaChannel extends BaseRealtimeChanne
169
169
  if (!url && !fileId) {
170
170
  return this.fail('Media_ShowMedia requires a source — provide a "url" (external/public) OR a "fileId" (an MJ: Files id).');
171
171
  }
172
+ // SECURITY: the url arg is model-supplied and is later rendered in an img/iframe/player, so
173
+ // only well-formed absolute http(s) URLs are accepted — never javascript:, data:, blob:, or scheme-relative.
174
+ if (url && !IsAllowedExternalMediaUrl(url)) {
175
+ return this.fail('Media_ShowMedia "url" must be an absolute http:// or https:// URL.');
176
+ }
172
177
  if (!displayName) {
173
178
  return this.fail('Media_ShowMedia requires a "displayName".');
174
179
  }
175
180
  const item = this.State.AddItem({
176
181
  Type: mediaType,
177
- Url: url,
182
+ Url: url ? url.trim() : undefined,
178
183
  FileID: fileId,
179
184
  DisplayName: displayName,
180
185
  Caption: asString(args['caption']),
@@ -1 +1 @@
1
- {"version":3,"file":"media-channel.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Type } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { RegisterClass } from '@memberjunction/global';\nimport { RealtimeToolDefinition } from '@memberjunction/ai';\nimport { REALTIME_CHANNEL_CONTRACT_VERSION, type RealtimeChannelDescriptor } from '@memberjunction/ai-core-plus';\nimport { BaseRealtimeChannelClient, BuildToolBackedVerbs } from '@memberjunction/realtime-runtime';\nimport { ChannelOnboardingDetails } from '@memberjunction/realtime-runtime';\nimport { MEDIA_TOOL_DEFINITIONS, MEDIA_TOOL_NAMES, MEDIA_TOOL_PREFIX } from './media-channel-tools';\nimport { MediaChannelState, MediaHighlight, MediaItemType } from './media-channel-state';\nimport { RealtimeMediaSurfaceComponent } from './realtime-media-surface.component';\n\n/** A JSON-serializable value (the building block of a parsed tool-args object). */\ntype ToolArgValue = string | number | boolean | null | ToolArgValue[] | { [key: string]: ToolArgValue };\n\n/** Coerces a parsed arg to a non-empty string, or `undefined` when absent / wrong-typed. */\nfunction asString(value: ToolArgValue | undefined): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/** Coerces a parsed arg to a finite number, or `undefined` when absent / wrong-typed. */\nfunction asNumber(value: ToolArgValue | undefined): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** The media types `Media_ShowMedia` accepts (validated before mutating state). */\nconst VALID_MEDIA_TYPES: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n\n/**\n * The shared MEDIA SURFACE as a pluggable interactive channel — a {@link BaseRealtimeChannelClient}\n * resolved from the `MJ: AI Agent Channels` registry row whose `ClientPluginClass` is\n * `'RealtimeMediaChannel'`. One instance per session (created via ClassFactory at session start).\n *\n * It owns a {@link MediaChannelState} engine and contributes the channel's full contract:\n *\n * - **Action**: the `Media_*` client-executed tool set ({@link MEDIA_TOOL_DEFINITIONS});\n * {@link ApplyAgentTool} parses the args, mutates the state engine, returns a short JSON result\n * string, and sends the model a `[media]` background context note (so it stays aware of what's\n * on screen). Works with NO surface bound — the tools mutate the state engine directly.\n * - **Surface**: {@link RealtimeMediaSurfaceComponent}, created dynamically by the overlay's\n * channel tab; {@link BindSurface} hands it the shared state engine + agent name.\n * - **State of record**: every mutation requests a debounced save of {@link MediaChannelState.ToJSON}\n * under channel name `'Media'`; a prior session's media is rehydrated via {@link RestoreState}.\n *\n * Modeled directly on `RealtimeWhiteboardChannel`.\n */\n@RegisterClass(BaseRealtimeChannelClient, 'RealtimeMediaChannel')\nexport class RealtimeMediaChannel extends BaseRealtimeChannelClient<RealtimeMediaSurfaceComponent> {\n /** The media state of record — created fresh with the plugin (one per session). */\n public readonly State = new MediaChannelState();\n\n /** The live bound surface, when the channel tab's pane is instantiated. */\n private surface: RealtimeMediaSurfaceComponent | null = null;\n /** Media-mutation subscription driving the debounced state-of-record save. */\n private stateChangedSub: Subscription | null = null;\n\n public get ChannelName(): string {\n return 'Media';\n }\n\n public get ToolNamePrefix(): string {\n return MEDIA_TOOL_PREFIX;\n }\n\n public get TabTitle(): string {\n return 'Media';\n }\n\n public get TabIcon(): string {\n return 'fa-solid fa-images';\n }\n\n public GetToolDefinitions(): RealtimeToolDefinition[] {\n return MEDIA_TOOL_DEFINITIONS;\n }\n\n /**\n * The media surface's self-description: what it holds, and every `Media_*` tool as a verb (so a tool a\n * subclass adds shows up here too). The model reads its state (what is on screen) but is never shown\n * pixels of it, so its exposure ceiling is `state`.\n */\n public override GetDescriptor(): RealtimeChannelDescriptor {\n return {\n Key: this.ChannelName,\n Version: REALTIME_CHANNEL_CONTRACT_VERSION,\n DisplayName: this.TabTitle,\n OwningPackage: '@memberjunction/ng-conversations',\n Instructions:\n 'A shared surface where you can show the user images, videos, audio clips, PDFs and web pages. ' +\n 'Each item opens in its own tab; you can switch between them and highlight a region to point at it.',\n Nouns: [\n {\n Name: 'items',\n Description: 'The media items currently shared, each with its type, title and source.',\n Schema: { type: 'array', items: { type: 'object' } }\n }\n ],\n Verbs: BuildToolBackedVerbs(this.GetToolDefinitions(), this.ToolNamePrefix),\n DisplayPolicy: 'open-on-start',\n DefaultAvailability: 'all-sessions',\n MaxExposure: 'state'\n };\n }\n\n public override GetSurfaceComponent(): Type<RealtimeMediaSurfaceComponent> {\n return RealtimeMediaSurfaceComponent;\n }\n\n /** First-run intro shown the first time the user opens the Media tab (once per user). */\n public override GetOnboardingDetails(): ChannelOnboardingDetails {\n return {\n Heading: 'Media',\n Description:\n 'A shared surface where the agent can show you visuals during the call — images, videos, ' +\n 'audio clips, PDFs and web pages. Each item opens in its own tab; the agent can switch ' +\n 'between them and highlight regions to point at exactly what it means.',\n Tips: [\n 'Switch between shared items using the tabs along the top.',\n 'The agent can highlight part of an image or page to draw your eye to it.',\n 'Open any item in a new browser tab or download it from the toolbar.',\n ],\n IconClass: 'fa-solid fa-images',\n };\n }\n\n /** Persist the media surface (host-debounced) on EVERY mutation — agent tools and tab changes. */\n protected override OnInitialize(): void {\n this.stateChangedSub = this.State.Changed$.subscribe(() => {\n this.Context?.RequestSave(this.State.ToJSON());\n });\n }\n\n /**\n * Wires the dynamically-created media surface (before first CD): shared state engine + agent name,\n * plus the live session's MJ provider so FileID-backed items stream securely (mj-storage-media-player\n * + CreateMediaAccessToken) under the SAME authenticated, multi-provider-safe provider.\n */\n public BindSurface(instance: RealtimeMediaSurfaceComponent): void {\n this.surface = instance;\n instance.State = this.State;\n instance.AgentName = this.Context?.AgentName ?? 'The agent';\n instance.Provider = this.Context?.Provider ?? null;\n }\n\n public override UnbindSurface(): void {\n this.surface = null;\n }\n\n /**\n * Executes one `Media_*` tool call LOCALLY: parse args, mutate the state engine, push a `[media]`\n * background context note so the agent stays aware of the surface, and return a short JSON result\n * string for the model. Never throws — malformed args resolve to a `{ success: false, error }`\n * payload the model can narrate. Works with or without a bound surface (it mutates the state engine,\n * which the surface observes).\n */\n public ApplyAgentTool(toolName: string, argsJson: string): string {\n let args: Record<string, ToolArgValue> = {};\n try {\n const parsed = argsJson ? (JSON.parse(argsJson) as unknown) : {};\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n args = parsed as Record<string, ToolArgValue>;\n }\n } catch {\n return this.fail(`Could not parse arguments for ${toolName}.`);\n }\n\n switch (toolName) {\n case MEDIA_TOOL_NAMES.ShowMedia:\n return this.applyShowMedia(args);\n case MEDIA_TOOL_NAMES.CloseMedia:\n return this.applyCloseMedia(args);\n case MEDIA_TOOL_NAMES.PlayMedia:\n return this.applyPlayMedia(args);\n case MEDIA_TOOL_NAMES.Highlight:\n return this.applyHighlight(args);\n case MEDIA_TOOL_NAMES.ClearAll:\n return this.applyClearAll();\n default:\n return this.fail(`Unknown Media tool \"${toolName}\".`);\n }\n }\n\n /** Adds a media item and makes it active. Source is a public `url` OR an MJStorage `fileId` (>=1 required). */\n private applyShowMedia(args: Record<string, ToolArgValue>): string {\n const mediaType = asString(args['mediaType']);\n const url = asString(args['url']);\n const fileId = asString(args['fileId']);\n const displayName = asString(args['displayName']);\n if (!mediaType || !VALID_MEDIA_TYPES.includes(mediaType as MediaItemType)) {\n return this.fail('Media_ShowMedia requires a \"mediaType\" of image | video | audio | pdf | web.');\n }\n if (!url && !fileId) {\n return this.fail('Media_ShowMedia requires a source — provide a \"url\" (external/public) OR a \"fileId\" (an MJ: Files id).');\n }\n if (!displayName) {\n return this.fail('Media_ShowMedia requires a \"displayName\".');\n }\n const item = this.State.AddItem({\n Type: mediaType as MediaItemType,\n Url: url,\n FileID: fileId,\n DisplayName: displayName,\n Caption: asString(args['caption']),\n });\n this.note(`now showing ${mediaType} \"${displayName}\"`);\n return JSON.stringify({ success: true, id: item.Id, active: true });\n }\n\n /** Closes a media item. */\n private applyCloseMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_CloseMedia requires an \"id\".');\n }\n const removed = this.State.RemoveItem(id);\n if (!removed) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`closed media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Activates + requests playback of a media item. */\n private applyPlayMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_PlayMedia requires an \"id\".');\n }\n const ok = this.State.RequestPlay(id);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`playing media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Draws (or, with absent dims, clears) a fractional highlight on a media item. */\n private applyHighlight(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_Highlight requires an \"id\".');\n }\n const x = asNumber(args['x']);\n const y = asNumber(args['y']);\n const w = asNumber(args['w']);\n const h = asNumber(args['h']);\n if (x === undefined || y === undefined || w === undefined || h === undefined) {\n return this.fail('Media_Highlight requires numeric fractional x, y, w, h (0..1).');\n }\n const highlight: MediaHighlight = { X: x, Y: y, W: w, H: h, Label: asString(args['label']) };\n const ok = this.State.Highlight(id, highlight);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`highlighted a region of media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Clears all media. */\n private applyClearAll(): string {\n this.State.Clear();\n this.note('cleared all media');\n return JSON.stringify({ success: true });\n }\n\n /** The Media channel's serialized state of record (persisted under {@link ChannelName}). */\n public override SerializeState(): string | null {\n return this.State.ToJSON();\n }\n\n /**\n * Rehydrates a prior session's saved media into THIS session's state engine (in place — the\n * {@link State} instance and its subscriptions are preserved). Tolerant: malformed JSON returns\n * `false` and the surface stays empty.\n */\n public override RestoreState(stateJson: string): boolean {\n return this.State.LoadFromJSON(stateJson);\n }\n\n public override Dispose(): void {\n this.stateChangedSub?.unsubscribe();\n this.stateChangedSub = null;\n this.surface = null;\n super.Dispose();\n }\n\n /** Builds a model-readable failure payload (never thrown — returned to the model). */\n private fail(error: string): string {\n return JSON.stringify({ success: false, error });\n }\n\n /**\n * Pushes a background context note so the agent stays aware of the surface state. Framed as\n * background (like the whiteboard's scene-delta notes) so the model doesn't narrate every change.\n */\n private note(summary: string): void {\n this.Context?.SendContextNote(\n `[media] ${summary} (background context — do NOT narrate routine changes; mention only if relevant).`,\n );\n }\n}\n\n/**\n * Tree-shaking prevention: the Media channel is resolved dynamically through the ClassFactory (by the\n * registry row's `ClientPluginClass` key), so this static call keeps its `@RegisterClass` side effect\n * from being eliminated by the bundler. Called alongside `LoadRealtimeWhiteboardChannel()`.\n */\nexport function LoadRealtimeMediaChannel(): void {\n // intentional no-op — the import side effect performs the registration\n}\n"]}
1
+ {"version":3,"file":"media-channel.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/media-channel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Type } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { RegisterClass } from '@memberjunction/global';\nimport { RealtimeToolDefinition } from '@memberjunction/ai';\nimport { REALTIME_CHANNEL_CONTRACT_VERSION, type RealtimeChannelDescriptor } from '@memberjunction/ai-core-plus';\nimport { BaseRealtimeChannelClient, BuildToolBackedVerbs } from '@memberjunction/realtime-runtime';\nimport { ChannelOnboardingDetails } from '@memberjunction/realtime-runtime';\nimport { MEDIA_TOOL_DEFINITIONS, MEDIA_TOOL_NAMES, MEDIA_TOOL_PREFIX } from './media-channel-tools';\nimport { IsAllowedExternalMediaUrl, MediaChannelState, MediaHighlight, MediaItemType } from './media-channel-state';\nimport { RealtimeMediaSurfaceComponent } from './realtime-media-surface.component';\n\n/** A JSON-serializable value (the building block of a parsed tool-args object). */\ntype ToolArgValue = string | number | boolean | null | ToolArgValue[] | { [key: string]: ToolArgValue };\n\n/** Coerces a parsed arg to a non-empty string, or `undefined` when absent / wrong-typed. */\nfunction asString(value: ToolArgValue | undefined): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/** Coerces a parsed arg to a finite number, or `undefined` when absent / wrong-typed. */\nfunction asNumber(value: ToolArgValue | undefined): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** The media types `Media_ShowMedia` accepts (validated before mutating state). */\nconst VALID_MEDIA_TYPES: MediaItemType[] = ['image', 'video', 'audio', 'pdf', 'web'];\n\n/**\n * The shared MEDIA SURFACE as a pluggable interactive channel — a {@link BaseRealtimeChannelClient}\n * resolved from the `MJ: AI Agent Channels` registry row whose `ClientPluginClass` is\n * `'RealtimeMediaChannel'`. One instance per session (created via ClassFactory at session start).\n *\n * It owns a {@link MediaChannelState} engine and contributes the channel's full contract:\n *\n * - **Action**: the `Media_*` client-executed tool set ({@link MEDIA_TOOL_DEFINITIONS});\n * {@link ApplyAgentTool} parses the args, mutates the state engine, returns a short JSON result\n * string, and sends the model a `[media]` background context note (so it stays aware of what's\n * on screen). Works with NO surface bound — the tools mutate the state engine directly.\n * - **Surface**: {@link RealtimeMediaSurfaceComponent}, created dynamically by the overlay's\n * channel tab; {@link BindSurface} hands it the shared state engine + agent name.\n * - **State of record**: every mutation requests a debounced save of {@link MediaChannelState.ToJSON}\n * under channel name `'Media'`; a prior session's media is rehydrated via {@link RestoreState}.\n *\n * Modeled directly on `RealtimeWhiteboardChannel`.\n */\n@RegisterClass(BaseRealtimeChannelClient, 'RealtimeMediaChannel')\nexport class RealtimeMediaChannel extends BaseRealtimeChannelClient<RealtimeMediaSurfaceComponent> {\n /** The media state of record — created fresh with the plugin (one per session). */\n public readonly State = new MediaChannelState();\n\n /** The live bound surface, when the channel tab's pane is instantiated. */\n private surface: RealtimeMediaSurfaceComponent | null = null;\n /** Media-mutation subscription driving the debounced state-of-record save. */\n private stateChangedSub: Subscription | null = null;\n\n public get ChannelName(): string {\n return 'Media';\n }\n\n public get ToolNamePrefix(): string {\n return MEDIA_TOOL_PREFIX;\n }\n\n public get TabTitle(): string {\n return 'Media';\n }\n\n public get TabIcon(): string {\n return 'fa-solid fa-images';\n }\n\n public GetToolDefinitions(): RealtimeToolDefinition[] {\n return MEDIA_TOOL_DEFINITIONS;\n }\n\n /**\n * The media surface's self-description: what it holds, and every `Media_*` tool as a verb (so a tool a\n * subclass adds shows up here too). The model reads its state (what is on screen) but is never shown\n * pixels of it, so its exposure ceiling is `state`.\n */\n public override GetDescriptor(): RealtimeChannelDescriptor {\n return {\n Key: this.ChannelName,\n Version: REALTIME_CHANNEL_CONTRACT_VERSION,\n DisplayName: this.TabTitle,\n OwningPackage: '@memberjunction/ng-conversations',\n Instructions:\n 'A shared surface where you can show the user images, videos, audio clips, PDFs and web pages. ' +\n 'Each item opens in its own tab; you can switch between them and highlight a region to point at it.',\n Nouns: [\n {\n Name: 'items',\n Description: 'The media items currently shared, each with its type, title and source.',\n Schema: { type: 'array', items: { type: 'object' } }\n }\n ],\n Verbs: BuildToolBackedVerbs(this.GetToolDefinitions(), this.ToolNamePrefix),\n DisplayPolicy: 'open-on-start',\n DefaultAvailability: 'all-sessions',\n MaxExposure: 'state'\n };\n }\n\n public override GetSurfaceComponent(): Type<RealtimeMediaSurfaceComponent> {\n return RealtimeMediaSurfaceComponent;\n }\n\n /** First-run intro shown the first time the user opens the Media tab (once per user). */\n public override GetOnboardingDetails(): ChannelOnboardingDetails {\n return {\n Heading: 'Media',\n Description:\n 'A shared surface where the agent can show you visuals during the call — images, videos, ' +\n 'audio clips, PDFs and web pages. Each item opens in its own tab; the agent can switch ' +\n 'between them and highlight regions to point at exactly what it means.',\n Tips: [\n 'Switch between shared items using the tabs along the top.',\n 'The agent can highlight part of an image or page to draw your eye to it.',\n 'Open any item in a new browser tab or download it from the toolbar.',\n ],\n IconClass: 'fa-solid fa-images',\n };\n }\n\n /** Persist the media surface (host-debounced) on EVERY mutation — agent tools and tab changes. */\n protected override OnInitialize(): void {\n this.stateChangedSub = this.State.Changed$.subscribe(() => {\n this.Context?.RequestSave(this.State.ToJSON());\n });\n }\n\n /**\n * Wires the dynamically-created media surface (before first CD): shared state engine + agent name,\n * plus the live session's MJ provider so FileID-backed items stream securely (mj-storage-media-player\n * + CreateMediaAccessToken) under the SAME authenticated, multi-provider-safe provider.\n */\n public BindSurface(instance: RealtimeMediaSurfaceComponent): void {\n this.surface = instance;\n instance.State = this.State;\n instance.AgentName = this.Context?.AgentName ?? 'The agent';\n instance.Provider = this.Context?.Provider ?? null;\n }\n\n public override UnbindSurface(): void {\n this.surface = null;\n }\n\n /**\n * Executes one `Media_*` tool call LOCALLY: parse args, mutate the state engine, push a `[media]`\n * background context note so the agent stays aware of the surface, and return a short JSON result\n * string for the model. Never throws — malformed args resolve to a `{ success: false, error }`\n * payload the model can narrate. Works with or without a bound surface (it mutates the state engine,\n * which the surface observes).\n */\n public ApplyAgentTool(toolName: string, argsJson: string): string {\n let args: Record<string, ToolArgValue> = {};\n try {\n const parsed = argsJson ? (JSON.parse(argsJson) as unknown) : {};\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n args = parsed as Record<string, ToolArgValue>;\n }\n } catch {\n return this.fail(`Could not parse arguments for ${toolName}.`);\n }\n\n switch (toolName) {\n case MEDIA_TOOL_NAMES.ShowMedia:\n return this.applyShowMedia(args);\n case MEDIA_TOOL_NAMES.CloseMedia:\n return this.applyCloseMedia(args);\n case MEDIA_TOOL_NAMES.PlayMedia:\n return this.applyPlayMedia(args);\n case MEDIA_TOOL_NAMES.Highlight:\n return this.applyHighlight(args);\n case MEDIA_TOOL_NAMES.ClearAll:\n return this.applyClearAll();\n default:\n return this.fail(`Unknown Media tool \"${toolName}\".`);\n }\n }\n\n /** Adds a media item and makes it active. Source is a public `url` OR an MJStorage `fileId` (>=1 required). */\n private applyShowMedia(args: Record<string, ToolArgValue>): string {\n const mediaType = asString(args['mediaType']);\n const url = asString(args['url']);\n const fileId = asString(args['fileId']);\n const displayName = asString(args['displayName']);\n if (!mediaType || !VALID_MEDIA_TYPES.includes(mediaType as MediaItemType)) {\n return this.fail('Media_ShowMedia requires a \"mediaType\" of image | video | audio | pdf | web.');\n }\n if (!url && !fileId) {\n return this.fail('Media_ShowMedia requires a source — provide a \"url\" (external/public) OR a \"fileId\" (an MJ: Files id).');\n }\n // SECURITY: the url arg is model-supplied and is later rendered in an img/iframe/player, so\n // only well-formed absolute http(s) URLs are accepted — never javascript:, data:, blob:, or scheme-relative.\n if (url && !IsAllowedExternalMediaUrl(url)) {\n return this.fail('Media_ShowMedia \"url\" must be an absolute http:// or https:// URL.');\n }\n if (!displayName) {\n return this.fail('Media_ShowMedia requires a \"displayName\".');\n }\n const item = this.State.AddItem({\n Type: mediaType as MediaItemType,\n Url: url ? url.trim() : undefined,\n FileID: fileId,\n DisplayName: displayName,\n Caption: asString(args['caption']),\n });\n this.note(`now showing ${mediaType} \"${displayName}\"`);\n return JSON.stringify({ success: true, id: item.Id, active: true });\n }\n\n /** Closes a media item. */\n private applyCloseMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_CloseMedia requires an \"id\".');\n }\n const removed = this.State.RemoveItem(id);\n if (!removed) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`closed media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Activates + requests playback of a media item. */\n private applyPlayMedia(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_PlayMedia requires an \"id\".');\n }\n const ok = this.State.RequestPlay(id);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`playing media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Draws (or, with absent dims, clears) a fractional highlight on a media item. */\n private applyHighlight(args: Record<string, ToolArgValue>): string {\n const id = asString(args['id']);\n if (!id) {\n return this.fail('Media_Highlight requires an \"id\".');\n }\n const x = asNumber(args['x']);\n const y = asNumber(args['y']);\n const w = asNumber(args['w']);\n const h = asNumber(args['h']);\n if (x === undefined || y === undefined || w === undefined || h === undefined) {\n return this.fail('Media_Highlight requires numeric fractional x, y, w, h (0..1).');\n }\n const highlight: MediaHighlight = { X: x, Y: y, W: w, H: h, Label: asString(args['label']) };\n const ok = this.State.Highlight(id, highlight);\n if (!ok) {\n return this.fail(`No media item with id \"${id}\".`);\n }\n this.note(`highlighted a region of media item \"${id}\"`);\n return JSON.stringify({ success: true, id });\n }\n\n /** Clears all media. */\n private applyClearAll(): string {\n this.State.Clear();\n this.note('cleared all media');\n return JSON.stringify({ success: true });\n }\n\n /** The Media channel's serialized state of record (persisted under {@link ChannelName}). */\n public override SerializeState(): string | null {\n return this.State.ToJSON();\n }\n\n /**\n * Rehydrates a prior session's saved media into THIS session's state engine (in place — the\n * {@link State} instance and its subscriptions are preserved). Tolerant: malformed JSON returns\n * `false` and the surface stays empty.\n */\n public override RestoreState(stateJson: string): boolean {\n return this.State.LoadFromJSON(stateJson);\n }\n\n public override Dispose(): void {\n this.stateChangedSub?.unsubscribe();\n this.stateChangedSub = null;\n this.surface = null;\n super.Dispose();\n }\n\n /** Builds a model-readable failure payload (never thrown — returned to the model). */\n private fail(error: string): string {\n return JSON.stringify({ success: false, error });\n }\n\n /**\n * Pushes a background context note so the agent stays aware of the surface state. Framed as\n * background (like the whiteboard's scene-delta notes) so the model doesn't narrate every change.\n */\n private note(summary: string): void {\n this.Context?.SendContextNote(\n `[media] ${summary} (background context — do NOT narrate routine changes; mention only if relevant).`,\n );\n }\n}\n\n/**\n * Tree-shaking prevention: the Media channel is resolved dynamically through the ClassFactory (by the\n * registry row's `ClientPluginClass` key), so this static call keeps its `@RegisterClass` side effect\n * from being eliminated by the bundler. Called alongside `LoadRealtimeWhiteboardChannel()`.\n */\nexport function LoadRealtimeMediaChannel(): void {\n // intentional no-op — the import side effect performs the registration\n}\n"]}
@@ -18,6 +18,13 @@ export type FileResolveState = {
18
18
  export type MediaRenderRoute = 'storage-player' | 'url-player' | 'iframe-or-img' | 'none';
19
19
  /** Whether an item is rendered by the real media player (audio/video) vs. an img/iframe pane. */
20
20
  export declare function IsPlayerMediaType(item: MediaItem): boolean;
21
+ /**
22
+ * Returns true only when `url` resolves (against `baseOrigin`) to an `http:` or `https:` URL and
23
+ * is not a scheme-relative `//host/...` reference. Used across all surface sinks (`RenderUrl`,
24
+ * `TracksFor`, `SafeUrl`, `OpenInNew`, `Download`) so neither model-supplied URLs nor rehydrated
25
+ * session URLs can inject `javascript:`, `data:`, `blob:`, or scheme-relative targets.
26
+ */
27
+ export declare function IsEmbeddableSurfaceUrl(url: string | null | undefined, baseOrigin: string): boolean;
21
28
  /**
22
29
  * Decides which render path an item uses based ONLY on its source + type — the single source of truth
23
30
  * the surface methods delegate to (kept pure so it's unit-testable without an Angular context):
@@ -86,7 +93,8 @@ export declare class RealtimeMediaSurfaceComponent extends BaseAngularComponent
86
93
  TracksFor(item: MediaItem): MediaTrack[];
87
94
  /**
88
95
  * The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a
89
- * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access).
96
+ * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access /
97
+ * if the URL fails http(s) scheme screening).
90
98
  */
91
99
  RenderUrl(item: MediaItem): string | null;
92
100
  /** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */
@@ -109,8 +117,17 @@ export declare class RealtimeMediaSurfaceComponent extends BaseAngularComponent
109
117
  CycleZoom(): void;
110
118
  /** The Font Awesome icon class for a media type's tab. */
111
119
  IconFor(type: MediaItem['Type']): string;
112
- /** A memoized sanitized resource URL for iframe-rendered media (web / pdf). */
120
+ /**
121
+ * A memoized sanitized resource URL for iframe-rendered media (web / pdf).
122
+ *
123
+ * SECURITY: `bypassSecurityTrustResourceUrl` disables Angular's sanitizer, and the URL here can
124
+ * originate from an agent tool call (`Media_ShowMedia`), so only http(s) URLs — absolute, or
125
+ * relative to this origin (the minted `/media/<fileId>?token=` streaming URLs) — are ever
126
+ * trusted. Anything else (javascript:, data:, blob:, …) renders about:blank instead.
127
+ */
113
128
  SafeUrl(url: string): SafeResourceUrl;
129
+ /** True when the URL parses (relative to this origin, excluding scheme-relative `//`) to http or https. */
130
+ private isEmbeddableUrl;
114
131
  /** Inline style for the fractional highlight rectangle (percentages of the pane box). */
115
132
  HighlightStyle(item: MediaItem): Record<string, string> | null;
116
133
  /** Stable `@for` track for tab/pane diffing. */
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-media-surface.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/realtime-media-surface.component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"realtime-media-surface.component.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/media/realtime-media-surface.component.ts"],"names":[],"mappings":"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"}
@@ -230,6 +230,28 @@ const CREATE_MEDIA_ACCESS_TOKEN_MUTATION = `mutation CreateMediaAccessToken($fil
230
230
  export function IsPlayerMediaType(item) {
231
231
  return item.Type === 'audio' || item.Type === 'video';
232
232
  }
233
+ /**
234
+ * Returns true only when `url` resolves (against `baseOrigin`) to an `http:` or `https:` URL and
235
+ * is not a scheme-relative `//host/...` reference. Used across all surface sinks (`RenderUrl`,
236
+ * `TracksFor`, `SafeUrl`, `OpenInNew`, `Download`) so neither model-supplied URLs nor rehydrated
237
+ * session URLs can inject `javascript:`, `data:`, `blob:`, or scheme-relative targets.
238
+ */
239
+ export function IsEmbeddableSurfaceUrl(url, baseOrigin) {
240
+ if (typeof url !== 'string') {
241
+ return false;
242
+ }
243
+ const trimmed = url.trim();
244
+ if (!trimmed || trimmed.startsWith('//')) {
245
+ return false;
246
+ }
247
+ try {
248
+ const parsed = new URL(trimmed, baseOrigin);
249
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:';
250
+ }
251
+ catch {
252
+ return false;
253
+ }
254
+ }
233
255
  /**
234
256
  * Decides which render path an item uses based ONLY on its source + type — the single source of truth
235
257
  * the surface methods delegate to (kept pure so it's unit-testable without an Angular context):
@@ -335,24 +357,33 @@ export class RealtimeMediaSurfaceComponent extends BaseAngularComponent {
335
357
  }
336
358
  /** The single-track payload for the URL-backed `mj-media-player` (audio/video). */
337
359
  TracksFor(item) {
338
- if (!item.Url) {
360
+ if (!item.Url || !this.isEmbeddableUrl(item.Url)) {
339
361
  return [];
340
362
  }
341
- return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url }];
363
+ return [{ Id: item.Id, Kind: item.Type === 'video' ? 'video' : 'audio', Url: item.Url.trim() }];
342
364
  }
343
365
  /**
344
366
  * The render URL for an IMG/IFRAME (image/pdf/web) item: its public {@link MediaItem.Url}, or — for a
345
- * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access).
367
+ * FileID-backed item — the resolved streaming URL once minted (`null` while loading / on no-access /
368
+ * if the URL fails http(s) scheme screening).
346
369
  */
347
370
  RenderUrl(item) {
371
+ let candidate = null;
348
372
  if (item.Url) {
349
- return item.Url;
373
+ candidate = item.Url;
350
374
  }
351
- if (item.FileID) {
375
+ else if (item.FileID) {
352
376
  const state = this.fileResolveByItem.get(item.Id);
353
- return state?.Status === 'ready' ? state.Url : null;
377
+ candidate = state?.Status === 'ready' ? state.Url : null;
354
378
  }
355
- return null;
379
+ if (!candidate) {
380
+ return null;
381
+ }
382
+ if (!this.isEmbeddableUrl(candidate)) {
383
+ LogError('RealtimeMediaSurface: refused media URL with a non-http(s) or scheme-relative target.');
384
+ return null;
385
+ }
386
+ return candidate.trim();
356
387
  }
357
388
  /** Whether a FileID-backed image/pdf/web item is still resolving its streaming URL. */
358
389
  IsResolving(item) {
@@ -436,15 +467,33 @@ export class RealtimeMediaSurfaceComponent extends BaseAngularComponent {
436
467
  return 'fa-solid fa-file';
437
468
  }
438
469
  }
439
- /** A memoized sanitized resource URL for iframe-rendered media (web / pdf). */
470
+ /**
471
+ * A memoized sanitized resource URL for iframe-rendered media (web / pdf).
472
+ *
473
+ * SECURITY: `bypassSecurityTrustResourceUrl` disables Angular's sanitizer, and the URL here can
474
+ * originate from an agent tool call (`Media_ShowMedia`), so only http(s) URLs — absolute, or
475
+ * relative to this origin (the minted `/media/<fileId>?token=` streaming URLs) — are ever
476
+ * trusted. Anything else (javascript:, data:, blob:, …) renders about:blank instead.
477
+ */
440
478
  SafeUrl(url) {
441
479
  let cached = this.safeUrlCache.get(url);
442
480
  if (!cached) {
443
- cached = this.sanitizer.bypassSecurityTrustResourceUrl(url);
481
+ const target = this.isEmbeddableUrl(url) ? url : 'about:blank';
482
+ if (target !== url) {
483
+ LogError(`RealtimeMediaSurface: refused to embed media URL with a non-http(s) scheme.`);
484
+ }
485
+ cached = this.sanitizer.bypassSecurityTrustResourceUrl(target);
444
486
  this.safeUrlCache.set(url, cached);
445
487
  }
446
488
  return cached;
447
489
  }
490
+ /** True when the URL parses (relative to this origin, excluding scheme-relative `//`) to http or https. */
491
+ isEmbeddableUrl(url) {
492
+ const origin = typeof window !== 'undefined' && window.location?.origin
493
+ ? window.location.origin
494
+ : 'https://localhost';
495
+ return IsEmbeddableSurfaceUrl(url, origin);
496
+ }
448
497
  /** Inline style for the fractional highlight rectangle (percentages of the pane box). */
449
498
  HighlightStyle(item) {
450
499
  const h = item.Highlight;
@@ -570,5 +619,5 @@ export class RealtimeMediaSurfaceComponent extends BaseAngularComponent {
570
619
  type: ViewChild,
571
620
  args: ['mediaPane']
572
621
  }] }); })();
573
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeMediaSurfaceComponent, { className: "RealtimeMediaSurfaceComponent", filePath: "src/lib/components/realtime/media/realtime-media-surface.component.ts", lineNumber: 100 }); })();
622
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeMediaSurfaceComponent, { className: "RealtimeMediaSurfaceComponent", filePath: "src/lib/components/realtime/media/realtime-media-surface.component.ts", lineNumber: 122 }); })();
574
623
  //# sourceMappingURL=realtime-media-surface.component.js.map