synapse-react-client 4.0.9 → 4.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SWC.index.d.ts +1 -0
- package/dist/SWC.index.d.ts.map +1 -1
- package/dist/SWC.index.js +2 -1
- package/dist/SWC.index.js.map +1 -1
- package/dist/aridhia-queries/aridhiaTokenExchange.js.map +1 -1
- package/dist/aridhia-queries/useGetAridhiaRequests.js.map +1 -1
- package/dist/assets/icons/TasksIcon.d.ts.map +1 -1
- package/dist/assets/icons/TasksIcon.js +6 -10
- package/dist/assets/icons/TasksIcon.js.map +1 -1
- package/dist/components/AccessRequirementAclEditor/AccessRequirementAclEditor.d.ts.map +1 -1
- package/dist/components/AccessRequirementAclEditor/AccessRequirementAclEditor.js +69 -63
- package/dist/components/AccessRequirementAclEditor/AccessRequirementAclEditor.js.map +1 -1
- package/dist/components/AccessRequirementList/AccessApprovalCheckMark.js.map +1 -1
- package/dist/components/AccessRequirementList/AccessRequirementList.js.map +1 -1
- package/dist/components/AccessRequirementList/AccessRequirementListUtils.js.map +1 -1
- package/dist/components/AccessRequirementList/ManagedACTAccessRequirementRequestFlow/DataAccessRequestAccessorsEditor.js.map +1 -1
- package/dist/components/AccessRequirementList/RequirementItem/SelfSignAccessRequirementItem.js.map +1 -1
- package/dist/components/AccessRequirementRelatedProjectsList/AccessRequirementRelatedProjectsList.js.map +1 -1
- package/dist/components/AccessTokenPage/AccessTokenCard/AccessTokenCard.js.map +1 -1
- package/dist/components/AcknowledgementsPage/StudyAcknowledgements.js.map +1 -1
- package/dist/components/AclEditor/PermissionLevelMenu.js.map +1 -1
- package/dist/components/AclEditor/ResourceAccessAndUserGroupHeader.js.map +1 -1
- package/dist/components/AclEditor/useSortResourceAccessList.js.map +1 -1
- package/dist/components/AclEditor/useUpdateAcl.js.map +1 -1
- package/dist/components/Aridhia/AridhiaAccessStatus.js.map +1 -1
- package/dist/components/Authentication/AuthenticationMethodSelection.d.ts.map +1 -1
- package/dist/components/Authentication/AuthenticationMethodSelection.js +38 -37
- package/dist/components/Authentication/AuthenticationMethodSelection.js.map +1 -1
- package/dist/components/Authentication/Constants.d.ts +1 -0
- package/dist/components/Authentication/Constants.d.ts.map +1 -1
- package/dist/components/Authentication/Constants.js +2 -2
- package/dist/components/Authentication/Constants.js.map +1 -1
- package/dist/components/Authentication/LastLoginInfo.js.map +1 -1
- package/dist/components/Authentication/RecoveryCodeForm.js.map +1 -1
- package/dist/components/Authentication/RecoveryCodeGrid.js.map +1 -1
- package/dist/components/Authentication/RegenerateBackupCodesWarning.js.map +1 -1
- package/dist/components/Authentication/Reset2FAWarning.js.map +1 -1
- package/dist/components/Authentication/StandaloneLoginForm.js +1 -1
- package/dist/components/Authentication/TwoFactorBackupCodes.js.map +1 -1
- package/dist/components/Authentication/TwoFactorEnrollmentForm.d.ts.map +1 -1
- package/dist/components/Authentication/TwoFactorEnrollmentForm.js +2 -1
- package/dist/components/Authentication/TwoFactorEnrollmentForm.js.map +1 -1
- package/dist/components/BasePortalCard/ColorfulPortalCardWithChips/ColorfulPortalCardWithChips.js.map +1 -1
- package/dist/components/CardContainer/CardContainer.js.map +1 -1
- package/dist/components/CardDeck/CardDeck.Mobile.js.map +1 -1
- package/dist/components/CardDeck/TableQueryCardDeck.js.map +1 -1
- package/dist/components/CertificationQuiz/CertificationQuiz.js.map +1 -1
- package/dist/components/ChallengeDataDownload/ChallengeDataDownload.js.map +1 -1
- package/dist/components/ChallengeSubmission/ChallengeSubmission.js.map +1 -1
- package/dist/components/ChallengeSubmission/ChallengeSubmissionStepper.js.map +1 -1
- package/dist/components/ChallengeSubmission/EvaluationQueueCurrentRoundInfo.js.map +1 -1
- package/dist/components/ChallengeSubmission/EvaluationQueueList.js.map +1 -1
- package/dist/components/ChallengeSubmission/SubmissionDirectoryList.js.map +1 -1
- package/dist/components/ChallengeTeamWizard/ChallengeTeamWizard.js.map +1 -1
- package/dist/components/ChallengeTeamWizard/CreateChallengeTeam.js.map +1 -1
- package/dist/components/ChangePassword/ChangePassword.js.map +1 -1
- package/dist/components/ChangePassword/ChangePasswordWithToken.js.map +1 -1
- package/dist/components/ChangePassword/useChangePasswordFormState.js +1 -1
- package/dist/components/ChangePassword/useChangePasswordFormState.js.map +1 -1
- package/dist/components/CitationPopover/CitationPopoverContent.js.map +1 -1
- package/dist/components/ColumnFilter/ColumnFilter.js.map +1 -1
- package/dist/components/ComponentCollapse.js.map +1 -1
- package/dist/components/CookiesNotification/CookiesNotification.js.map +1 -1
- package/dist/components/CreateProjectModal/CreateProjectModal.js.map +1 -1
- package/dist/components/CreateTableViewWizard/CreateTableViewWizardUtils.js.map +1 -1
- package/dist/components/DataGrid/DataGrid.d.ts +0 -1
- package/dist/components/DataGrid/DataGrid.d.ts.map +1 -1
- package/dist/components/DataGrid/DataGrid.js +72 -72
- package/dist/components/DataGrid/DataGrid.js.map +1 -1
- package/dist/components/DataGrid/DataGridWebSocket.d.ts +4 -0
- package/dist/components/DataGrid/DataGridWebSocket.d.ts.map +1 -1
- package/dist/components/DataGrid/DataGridWebSocket.js +9 -8
- package/dist/components/DataGrid/DataGridWebSocket.js.map +1 -1
- package/dist/components/DataGrid/SynapseGrid.d.ts.map +1 -1
- package/dist/components/DataGrid/SynapseGrid.js +326 -268
- package/dist/components/DataGrid/SynapseGrid.js.map +1 -1
- package/dist/components/DataGrid/columns/AutocompleteColumn.d.ts +2 -0
- package/dist/components/DataGrid/columns/AutocompleteColumn.d.ts.map +1 -1
- package/dist/components/DataGrid/columns/AutocompleteColumn.js +113 -67
- package/dist/components/DataGrid/columns/AutocompleteColumn.js.map +1 -1
- package/dist/components/DataGrid/columns/AutocompleteMultipleEnumColumn.d.ts +2 -1
- package/dist/components/DataGrid/columns/AutocompleteMultipleEnumColumn.d.ts.map +1 -1
- package/dist/components/DataGrid/columns/AutocompleteMultipleEnumColumn.js +126 -122
- package/dist/components/DataGrid/columns/AutocompleteMultipleEnumColumn.js.map +1 -1
- package/dist/components/DataGrid/columns/useGridAutocompleteState.d.ts +58 -0
- package/dist/components/DataGrid/columns/useGridAutocompleteState.d.ts.map +1 -0
- package/dist/components/DataGrid/columns/useGridAutocompleteState.js +52 -0
- package/dist/components/DataGrid/columns/useGridAutocompleteState.js.map +1 -0
- package/dist/components/DataGrid/components/ValidationAlert.d.ts +5 -2
- package/dist/components/DataGrid/components/ValidationAlert.d.ts.map +1 -1
- package/dist/components/DataGrid/components/ValidationAlert.js +429 -24
- package/dist/components/DataGrid/components/ValidationAlert.js.map +1 -1
- package/dist/components/DataGrid/hooks/useColumnResizeHandles.js.map +1 -1
- package/dist/components/DataGrid/hooks/useGetSchemaForGrid.js.map +1 -1
- package/dist/components/DataGrid/hooks/useGridUndoRedo.js.map +1 -1
- package/dist/components/DataGrid/hooks/useStack.js.map +1 -1
- package/dist/components/DataGrid/useCRDTModelView.js.map +1 -1
- package/dist/components/DataGrid/useDataGridWebsocket.d.ts +7 -0
- package/dist/components/DataGrid/useDataGridWebsocket.d.ts.map +1 -1
- package/dist/components/DataGrid/useDataGridWebsocket.js +16 -2
- package/dist/components/DataGrid/useDataGridWebsocket.js.map +1 -1
- package/dist/components/DataGrid/useInitializeGridConnection.js.map +1 -1
- package/dist/components/DataGrid/useMergeGridWithRecordSet.js.map +1 -1
- package/dist/components/DataGrid/useMergeGridWithSource.js.map +1 -1
- package/dist/components/DataGrid/useMergeGridWithTable.js.map +1 -1
- package/dist/components/DataGrid/utils/DataGridUtils.js.map +1 -1
- package/dist/components/DataGrid/utils/applyModelChange.js.map +1 -1
- package/dist/components/DataGrid/utils/columnFactory.js.map +1 -1
- package/dist/components/DataGrid/utils/computeReplicaSelectionModel.js.map +1 -1
- package/dist/components/DataGrid/utils/extractColumnValidationMessages.js.map +1 -1
- package/dist/components/DataGrid/utils/getCellClassName.d.ts.map +1 -1
- package/dist/components/DataGrid/utils/getCellClassName.js +8 -8
- package/dist/components/DataGrid/utils/getCellClassName.js.map +1 -1
- package/dist/components/DataGrid/utils/json-rx/JsonRx.js.map +1 -1
- package/dist/components/DataGrid/utils/modelRowsToGrid.js.map +1 -1
- package/dist/components/DataGrid/utils/parseFreeTextUsingJsonSchemaType.js.map +1 -1
- package/dist/components/DataGrid/utils/splitPatch.js.map +1 -1
- package/dist/components/DateTimePicker/DateTimePicker.js.map +1 -1
- package/dist/components/DirectDownload/DirectDownload.js.map +1 -1
- package/dist/components/DirectDownloadButton.js.map +1 -1
- package/dist/components/DownloadCart/CreatePackageV2.js.map +1 -1
- package/dist/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.js.map +1 -1
- package/dist/components/DownloadCart/DownloadListActionsRequired.js.map +1 -1
- package/dist/components/DownloadCart/DownloadListTable.js.map +1 -1
- package/dist/components/DownloadCart/fileNameUtils.js.map +1 -1
- package/dist/components/DraggableDialog/DraggableDialog.js.map +1 -1
- package/dist/components/DynamicForm/DynamicFormModal.js.map +1 -1
- package/dist/components/Ecosystem/TableQueryEcosystem.js.map +1 -1
- package/dist/components/EntityAclEditor/EntityAclEditor.d.ts.map +1 -1
- package/dist/components/EntityAclEditor/EntityAclEditor.js +103 -103
- package/dist/components/EntityAclEditor/EntityAclEditor.js.map +1 -1
- package/dist/components/EntityAclEditor/useNotifyNewACLUsers.js.map +1 -1
- package/dist/components/EntityBadgeIcons/EntityBadgeIcons.js.map +1 -1
- package/dist/components/EntityCitation/EntityCitation.js.map +1 -1
- package/dist/components/EntityDownloadButton/EntityDownloadButton.js.map +1 -1
- package/dist/components/EntityDownloadConfirmation/EntityDownloadConfirmation.d.ts.map +1 -1
- package/dist/components/EntityDownloadConfirmation/EntityDownloadConfirmation.js +36 -30
- package/dist/components/EntityDownloadConfirmation/EntityDownloadConfirmation.js.map +1 -1
- package/dist/components/EntityFinder/EntityFinder.js.map +1 -1
- package/dist/components/EntityFinder/VersionSelectionType.js.map +1 -1
- package/dist/components/EntityFinder/details/configurations/EntityChildrenDetails.js.map +1 -1
- package/dist/components/EntityFinder/details/configurations/FavoritesDetails.js.map +1 -1
- package/dist/components/EntityFinder/details/configurations/ProjectListDetails.js.map +1 -1
- package/dist/components/EntityFinder/details/view/DetailsView.js.map +1 -1
- package/dist/components/EntityFinder/tree/EntityTree.js.map +1 -1
- package/dist/components/EntityFinder/tree/VirtualizedTree.js.map +1 -1
- package/dist/components/EntityFinder/useEntitySelection.js.map +1 -1
- package/dist/components/EntityForm/EntityForm.js.map +1 -1
- package/dist/components/EntityHeaderTable/EntityHeaderTable.js.map +1 -1
- package/dist/components/EntityHeaderTable/Filter.js.map +1 -1
- package/dist/components/EntityHeaderTable/useEntityHeaderTableState.js.map +1 -1
- package/dist/components/EntitySubjectsSelector/EntitySubjectsSelector.js.map +1 -1
- package/dist/components/EntityTreeTable/components/IdColumnHeader.js.map +1 -1
- package/dist/components/EntityTreeTable/hooks/useEntityTreeState.js.map +1 -1
- package/dist/components/EntityTreeTable/hooks/useTableColumns.js.map +1 -1
- package/dist/components/EntityTreeTable/hooks/useTableData.js.map +1 -1
- package/dist/components/EntityTreeTable/hooks/useTreeOperationsWithDirectFetch.js.map +1 -1
- package/dist/components/EntityUpload/EntityUpload.js.map +1 -1
- package/dist/components/ExperimentalMode/ExperimentalMode.js.map +1 -1
- package/dist/components/ExternalFileHandleLink/ExternalFileHandleLink.js.map +1 -1
- package/dist/components/FeaturedDataTabs/FacetPlotsCard.js.map +1 -1
- package/dist/components/FeaturedDataTabs/QueryPerFacetPlotsCard.js.map +1 -1
- package/dist/components/FeaturedDataTabs/SingleQueryFacetPlotsCards.js.map +1 -1
- package/dist/components/FeaturedResearch/FeaturedResearch.js.map +1 -1
- package/dist/components/FeaturedToolsList/FeaturedToolsList.js.map +1 -1
- package/dist/components/FilePreview/FileHandleContentRenderer.js.map +1 -1
- package/dist/components/FilePreview/HtmlPreview/HtmlPreview.js.map +1 -1
- package/dist/components/FilePreview/PreviewRendererType.js.map +1 -1
- package/dist/components/Forum/DiscussionReply.js.map +1 -1
- package/dist/components/Forum/DiscussionSearchResult.js.map +1 -1
- package/dist/components/Forum/ForumTable.js.map +1 -1
- package/dist/components/Forum/ForumThreadEditor.js.map +1 -1
- package/dist/components/FullTextSearch/FullTextSearchUtils.js.map +1 -1
- package/dist/components/GenericCard/GenericCard.d.ts.map +1 -1
- package/dist/components/GenericCard/GenericCard.js +12 -7
- package/dist/components/GenericCard/GenericCard.js.map +1 -1
- package/dist/components/GenericCard/Linkify.js.map +1 -1
- package/dist/components/GenericCard/SynapseCardLabel.js.map +1 -1
- package/dist/components/GenericCard/TableRowGenericCard.js +105 -105
- package/dist/components/GenericCard/TableRowGenericCard.js.map +1 -1
- package/dist/components/Goals/Goals.Mobile.js.map +1 -1
- package/dist/components/Goals/Goals.js.map +1 -1
- package/dist/components/GoalsV2/GoalsV2.Mobile.js.map +1 -1
- package/dist/components/GoalsV2/GoalsV2.js.map +1 -1
- package/dist/components/GoalsV3/GoalsV3.Mobile.js.map +1 -1
- package/dist/components/GoalsV3/GoalsV3.js.map +1 -1
- package/dist/components/GoogleMap/SynapseUserMarker.js.map +1 -1
- package/dist/components/HasAccess/AccessIcon.js.map +1 -1
- package/dist/components/HasAccess/useHasAccess.js.map +1 -1
- package/dist/components/HeaderCard/HeaderCardV2.js.map +1 -1
- package/dist/components/HeaderCard.d.ts +6 -1
- package/dist/components/HeaderCard.d.ts.map +1 -1
- package/dist/components/HeaderCard.js +107 -76
- package/dist/components/HeaderCard.js.map +1 -1
- package/dist/components/HexGrid/HexGrid.js.map +1 -1
- package/dist/components/IconList.js.map +1 -1
- package/dist/components/ImageCardGridWithLinks/ImageCardGridWithLinks.js.map +1 -1
- package/dist/components/ImageFromSynapseTable.js.map +1 -1
- package/dist/components/JSONArrayEditor/useParseCsv.js.map +1 -1
- package/dist/components/JsonSchemaForm/templates/ArrayFieldDescriptionTemplate.js.map +1 -1
- package/dist/components/JsonSchemaForm/templates/ArrayFieldItemTemplate.js.map +1 -1
- package/dist/components/JsonSchemaForm/templates/BaseInputTemplate.js.map +1 -1
- package/dist/components/JsonSchemaForm/templates/FieldTemplate.js.map +1 -1
- package/dist/components/JsonSchemaForm/templates/RJSFInputLabel.js.map +1 -1
- package/dist/components/Markdown/MarkdownGithub.js.map +1 -1
- package/dist/components/Markdown/MarkdownSynapse.js.map +1 -1
- package/dist/components/Markdown/MarkdownUtils.js.map +1 -1
- package/dist/components/Markdown/SynapseWikiContext.js.map +1 -1
- package/dist/components/Markdown/UserMentionModal.js.map +1 -1
- package/dist/components/Markdown/widget/MarkdownProvenanceGraph.js.map +1 -1
- package/dist/components/MissingQueryResultsWarning/MissingQueryResultsWarning.js.map +1 -1
- package/dist/components/ModalDownload/ModalDownload.js.map +1 -1
- package/dist/components/OAuthClientAclEditor/OAuthClientAclEditor.d.ts.map +1 -1
- package/dist/components/OAuthClientAclEditor/OAuthClientAclEditor.js +45 -39
- package/dist/components/OAuthClientAclEditor/OAuthClientAclEditor.js.map +1 -1
- package/dist/components/OAuthClientManagement/OAuthManagement.js.map +1 -1
- package/dist/components/PageProgress/PageProgress.js.map +1 -1
- package/dist/components/Plot/DotPlot.js.map +1 -1
- package/dist/components/Plot/Plot.js.map +1 -1
- package/dist/components/Plot/SynapsePlot.js.map +1 -1
- package/dist/components/Plot/ThemesPlot.js.map +1 -1
- package/dist/components/Plot/UpsetPlot.js.map +1 -1
- package/dist/components/PortalAclEditor/PortalAclEditor.d.ts.map +1 -1
- package/dist/components/PortalAclEditor/PortalAclEditor.js +43 -41
- package/dist/components/PortalAclEditor/PortalAclEditor.js.map +1 -1
- package/dist/components/PortalFeaturedPartners/PortalFeaturedPartners.js.map +1 -1
- package/dist/components/PortalList/CreatePortalModal.js.map +1 -1
- package/dist/components/ProgrammaticInstructionsModal/ProgrammaticInstructionsModal.js.map +1 -1
- package/dist/components/ProgrammaticTableDownload/ProgrammaticTableDownload.js.map +1 -1
- package/dist/components/Programs/Programs.Mobile.js.map +1 -1
- package/dist/components/Programs/Programs.js.map +1 -1
- package/dist/components/ProvenanceGraph/ProvenanceExternalIcon.js.map +1 -1
- package/dist/components/ProvenanceGraph/ProvenanceGraph.js.map +1 -1
- package/dist/components/ProvenanceGraph/ProvenanceGraphUtils.js.map +1 -1
- package/dist/components/ProvenanceGraph/ProvenanceUtils.js.map +1 -1
- package/dist/components/QueryCount/QueryCount.js.map +1 -1
- package/dist/components/QueryCountButton/QueryCountButton.js.map +1 -1
- package/dist/components/QueryVisualizationWrapper/QueryVisualizationWrapper.js.map +1 -1
- package/dist/components/QueryWrapper/QueryWrapper.js.map +1 -1
- package/dist/components/QueryWrapper/TableQueryUseQueryOptions.js.map +1 -1
- package/dist/components/QueryWrapper/TableRowSelectionState.js.map +1 -1
- package/dist/components/QueryWrapper/generateEncodedPathAndQueryForSelectedFacetURL.js.map +1 -1
- package/dist/components/QueryWrapper/useGetQueryMetadata.js.map +1 -1
- package/dist/components/QueryWrapperErrorBoundary.js.map +1 -1
- package/dist/components/QueryWrapperPlotNav/QueryWrapperPlotNav.js.map +1 -1
- package/dist/components/QueryWrapperPlotNav/UseRowSet.js.map +1 -1
- package/dist/components/RecentPublicationsGrid/RecentPublicationsGrid.js.map +1 -1
- package/dist/components/ReleaseCard/ReleaseCardUtils.js.map +1 -1
- package/dist/components/ResizableContainer/hooks/useResizable.js.map +1 -1
- package/dist/components/Resources/Resources.Mobile.js.map +1 -1
- package/dist/components/Resources/Resources.js.map +1 -1
- package/dist/components/RowDataTable/RowDataTableWithQuery.js.map +1 -1
- package/dist/components/SageResourcesPopover/SageResourcesPopover.js.map +1 -1
- package/dist/components/SchemaDrivenAnnotationEditor/AnnotationEditorUtils.js.map +1 -1
- package/dist/components/SetAccessRequirementCommonFields/SetAccessRequirementCommonFields.js.map +1 -1
- package/dist/components/SetManagedAccessRequirementFields/SetManagedAccessRequirementFields.js.map +1 -1
- package/dist/components/SmartLink/SmartButton.js.map +1 -1
- package/dist/components/SmartLink/SmartLink.js.map +1 -1
- package/dist/components/SourceAppImage.js.map +1 -1
- package/dist/components/StandaloneQueryWrapper/StandaloneQueryWrapper.js.map +1 -1
- package/dist/components/StatisticsPlot.js.map +1 -1
- package/dist/components/StorybookComponentWrapper.js.map +1 -1
- package/dist/components/SubsectionRowRenderer/SubsectionRowRenderer.js.map +1 -1
- package/dist/components/SustainabilityScorecard/SustainabilityScorecard.js.map +1 -1
- package/dist/components/SynapseChat/GridAgentChat.js.map +1 -1
- package/dist/components/SynapseChat/SynapseChatInteraction.js.map +1 -1
- package/dist/components/SynapseChat/SynapseChatMessage.js.map +1 -1
- package/dist/components/SynapseChat/extractMessageFromTraceEvent.js.map +1 -1
- package/dist/components/SynapseForm/StepsSideNav.js.map +1 -1
- package/dist/components/SynapseForm/SummaryTable.js.map +1 -1
- package/dist/components/SynapseForm/SynapseForm.js +4 -2
- package/dist/components/SynapseForm/SynapseForm.js.map +1 -1
- package/dist/components/SynapseForm/SynapseFormWrapper.js.map +1 -1
- package/dist/components/SynapseHomepageV2/SynapseByTheNumbersItem.js.map +1 -1
- package/dist/components/SynapseHomepageV2/SynapseFeatureItem.js.map +1 -1
- package/dist/components/SynapseHomepageV2/SynapseHomepageChatSearch.js.map +1 -1
- package/dist/components/SynapseHomepageV2/SynapseHomepageSearch.js.map +1 -1
- package/dist/components/SynapseHomepageV2/SynapseInActionItem.js.map +1 -1
- package/dist/components/SynapseHomepageV2/SynapsePlans.js.map +1 -1
- package/dist/components/SynapseHomepageV2/SynapseTrendingProjects.js.map +1 -1
- package/dist/components/SynapseNavDrawer/SynapseNavDrawer.d.ts +8 -7
- package/dist/components/SynapseNavDrawer/SynapseNavDrawer.d.ts.map +1 -1
- package/dist/components/SynapseNavDrawer/SynapseNavDrawer.js +173 -164
- package/dist/components/SynapseNavDrawer/SynapseNavDrawer.js.map +1 -1
- package/dist/components/SynapsePortalBanners/SynapsePortalBanners.js.map +1 -1
- package/dist/components/SynapseSearchPageResults/SearchFacetPanel/SearchFacetPanel.js.map +1 -1
- package/dist/components/SynapseSearchPageResults/SearchFacetPanel/SearchFacetPanelUtils.js.map +1 -1
- package/dist/components/SynapseSearchPageResults/SynapseSearchPageResults.js.map +1 -1
- package/dist/components/SynapseTable/EntityIDColumnCopyIcon.js.map +1 -1
- package/dist/components/SynapseTable/NoContentPlaceholderType.js.map +1 -1
- package/dist/components/SynapseTable/RowSelection/RowSelectionControls.js.map +1 -1
- package/dist/components/SynapseTable/SynapseTableCell/SynapseTableCell.js.map +1 -1
- package/dist/components/SynapseTable/SynapseTableRenderers.js.map +1 -1
- package/dist/components/SynapseTable/datasets/DatasetItemsEditor.js.map +1 -1
- package/dist/components/SynapseTable/table-top/ColumnSelection.js.map +1 -1
- package/dist/components/SynapseTable/table-top/DownloadOptions.js.map +1 -1
- package/dist/components/SynapseTable/usePrefetchTableData.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/ColumnModelForm.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/ColumnModelFormFields/DefaultValueField.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/ImportTableColumnsButton.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/TableColumnSchemaEditorUtils.d.ts +1 -1
- package/dist/components/TableColumnSchemaEditor/TableColumnSchemaEditorUtils.d.ts.map +1 -1
- package/dist/components/TableColumnSchemaEditor/TableColumnSchemaEditorUtils.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/TableColumnSchemaForm.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/TableColumnSchemaFormReducer.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/Validators/ColumnModelValidator.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/Validators/DatetimeSchema.js.map +1 -1
- package/dist/components/TanStackTable/ColumnHeader.d.ts +1 -0
- package/dist/components/TanStackTable/ColumnHeader.d.ts.map +1 -1
- package/dist/components/TanStackTable/ColumnHeader.js +8 -8
- package/dist/components/TanStackTable/ColumnHeader.js.map +1 -1
- package/dist/components/TanStackTable/ColumnHeaderEnumFilter.js.map +1 -1
- package/dist/components/TanStackTable/TableBody.js.map +1 -1
- package/dist/components/TeamSubjectsSelector/TeamSubjectsSelector.js.map +1 -1
- package/dist/components/TextField/TextField.js.map +1 -1
- package/dist/components/TimelinePlot/TimelinePhase.js.map +1 -1
- package/dist/components/TimelinePlot/TimelinePlot.js.map +1 -1
- package/dist/components/TimelinePlot/TimelinePlotSpeciesSelector.js.map +1 -1
- package/dist/components/UserCard/Avatar.js.map +1 -1
- package/dist/components/UserCardList/UserCardList.js.map +1 -1
- package/dist/components/UserCardList/UserCardListGroups/UserCardListGroups.Mobile.js.map +1 -1
- package/dist/components/UserCardList/UserCardListRotate.js.map +1 -1
- package/dist/components/UserOrTeamBadge/useUserOrTeam.js.map +1 -1
- package/dist/components/UserProfileLinks/UserProjects.js.map +1 -1
- package/dist/components/UserSearchBox/UserSearchBox.js.map +1 -1
- package/dist/components/Webhook/WebhookDashboard.js.map +1 -1
- package/dist/components/WikiMarkdownEditor/WikiMarkdownEditor.js.map +1 -1
- package/dist/components/WikiMarkdownEditorButton/WikiMarkdownEditorButton.js.map +1 -1
- package/dist/components/dataaccess/AccessApprovalsTable.js.map +1 -1
- package/dist/components/dataaccess/AccessRequestSubmissionTable.js.map +1 -1
- package/dist/components/dataaccess/SubmissionPage/SubmissionPage.js.map +1 -1
- package/dist/components/dataaccess/UseAccessRequirementTable.js.map +1 -1
- package/dist/components/dataaccess/UserAccessRequestHistory/UserAccessRequestHistoryTable.js.map +1 -1
- package/dist/components/doi/CreateOrUpdateDoiModal.js.map +1 -1
- package/dist/components/entity/page/CreatedByModifiedBy.js.map +1 -1
- package/dist/components/entity/page/action_menu/EntityActionMenu.js.map +1 -1
- package/dist/components/entity/page/title_bar/useDataCiteUsage.js.map +1 -1
- package/dist/components/entity/page/title_bar/useGetMentions.js.map +1 -1
- package/dist/components/error/ErrorPage.js.map +1 -1
- package/dist/components/favorites/FavoritesPage.js.map +1 -1
- package/dist/components/file/upload/BasicFileHandleUpload.js.map +1 -1
- package/dist/components/layout/SWCHeader.d.ts +9 -0
- package/dist/components/layout/SWCHeader.d.ts.map +1 -0
- package/dist/components/layout/SWCHeader.js +19 -0
- package/dist/components/layout/SWCHeader.js.map +1 -0
- package/dist/components/layout/SWCPageLayout.d.ts +9 -0
- package/dist/components/layout/SWCPageLayout.d.ts.map +1 -0
- package/dist/components/layout/SWCPageLayout.js +14 -0
- package/dist/components/layout/SWCPageLayout.js.map +1 -0
- package/dist/components/menu/ComplexMenu.js.map +1 -1
- package/dist/components/row_renderers/utils/ChipContainer.js.map +1 -1
- package/dist/components/styled/StyledPopover.js.map +1 -1
- package/dist/components/table/CsvPreview/CsvPreview.js +2 -1
- package/dist/components/table/CsvPreview/CsvPreview.js.map +1 -1
- package/dist/components/table/CsvPreview/CsvPreviewDialog.js.map +1 -1
- package/dist/components/trash/TrashCanList.js.map +1 -1
- package/dist/components/widgets/FileHandleLink.js.map +1 -1
- package/dist/components/widgets/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/widgets/SynapseVideo.js.map +1 -1
- package/dist/components/widgets/facet-nav/FacetNavPanel.js.map +1 -1
- package/dist/components/widgets/facet-nav/PlotsContainer.js.map +1 -1
- package/dist/components/widgets/facet-nav/SelectionCriteriaPills.js.map +1 -1
- package/dist/components/widgets/facet-nav/useFacetPlots.js.map +1 -1
- package/dist/components/widgets/query-filter/CombinedRangeFacetFilter.js.map +1 -1
- package/dist/components/widgets/query-filter/EnumFacetFilter/EnumFacetFilter.js.map +1 -1
- package/dist/components/widgets/query-filter/FacetFilterControls.js.map +1 -1
- package/dist/components/widgets/query-filter/RangeFacetFilter.js.map +1 -1
- package/dist/components/widgets/query-filter/RangeFacetFilterUI.js.map +1 -1
- package/dist/features/curator/GridPage/components/GridPageTitle.d.ts.map +1 -1
- package/dist/features/curator/GridPage/components/GridPageTitle.js +23 -30
- package/dist/features/curator/GridPage/components/GridPageTitle.js.map +1 -1
- package/dist/features/curator/dashboard/CuratorDashboard.d.ts +2 -0
- package/dist/features/curator/dashboard/CuratorDashboard.d.ts.map +1 -0
- package/dist/features/curator/dashboard/CuratorDashboard.js +45 -0
- package/dist/features/curator/dashboard/CuratorDashboard.js.map +1 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.css +1 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.d.ts +9 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.d.ts.map +1 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.js +106 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.js.map +1 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.module.js +12 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.module.js.map +1 -0
- package/dist/features/curator/dashboard/components/CurationTaskCard.module.scss +52 -0
- package/dist/features/curator/dashboard/components/NextStepButton.css +1 -0
- package/dist/features/curator/dashboard/components/NextStepButton.d.ts +14 -0
- package/dist/features/curator/dashboard/components/NextStepButton.d.ts.map +1 -0
- package/dist/features/curator/dashboard/components/NextStepButton.js +35 -0
- package/dist/features/curator/dashboard/components/NextStepButton.js.map +1 -0
- package/dist/features/curator/dashboard/components/NextStepButton.module.js +11 -0
- package/dist/features/curator/dashboard/components/NextStepButton.module.js.map +1 -0
- package/dist/features/curator/dashboard/components/NextStepButton.module.scss +57 -0
- package/dist/features/curator/dashboard/components/UserOrTeamChip.css +1 -1
- package/dist/features/curator/dashboard/components/UserOrTeamChip.module.js +1 -1
- package/dist/features/curator/dashboard/components/UserOrTeamChip.module.js.map +1 -1
- package/dist/features/curator/dashboard/components/UserOrTeamChip.module.scss +5 -5
- package/dist/features/curator/dashboard/components/shared.css +1 -0
- package/dist/features/curator/dashboard/components/shared.module.js +5 -0
- package/dist/features/curator/dashboard/components/shared.module.js.map +1 -0
- package/dist/features/curator/dashboard/components/shared.module.scss +8 -0
- package/dist/features/entity/metadata-task/components/MetadataTaskTableActionCell.d.ts +0 -2
- package/dist/features/entity/metadata-task/components/MetadataTaskTableActionCell.d.ts.map +1 -1
- package/dist/features/entity/metadata-task/components/MetadataTaskTableActionCell.js +16 -34
- package/dist/features/entity/metadata-task/components/MetadataTaskTableActionCell.js.map +1 -1
- package/dist/features/entity/metadata-task/components/MetadataTasksTableAssigneeCell.js.map +1 -1
- package/dist/features/entity/metadata-task/hooks/useGetOrCreateGridSessionForSource.js.map +1 -1
- package/dist/features/entity/metadata-task/hooks/useGridSessionForCurationTask.js.map +1 -1
- package/dist/features/entity/metadata-task/hooks/useGridSessionForCurationTask_legacy.js.map +1 -1
- package/dist/features/entity/metadata-task/hooks/useMetadataTaskTable.js +1 -1
- package/dist/features/entity/metadata-task/hooks/useMetadataTaskTable.js.map +1 -1
- package/dist/features/entity/metadata-task/hooks/useOpenCuratorButton.d.ts +10 -0
- package/dist/features/entity/metadata-task/hooks/useOpenCuratorButton.d.ts.map +1 -0
- package/dist/features/entity/metadata-task/hooks/useOpenCuratorButton.js +37 -0
- package/dist/features/entity/metadata-task/hooks/useOpenCuratorButton.js.map +1 -0
- package/dist/features/entity/metadata-task/utils/constants.d.ts +5 -0
- package/dist/features/entity/metadata-task/utils/constants.d.ts.map +1 -0
- package/dist/features/entity/metadata-task/utils/constants.js +6 -0
- package/dist/features/entity/metadata-task/utils/constants.js.map +1 -0
- package/dist/mocks/challenge/mockChallenge.js.map +1 -1
- package/dist/mocks/entity/mockDataset.js.map +1 -1
- package/dist/mocks/entity/mockDatasetCollection.js.map +1 -1
- package/dist/mocks/entity/mockFileEntity.js.map +1 -1
- package/dist/mocks/entity/mockFileView.js.map +1 -1
- package/dist/mocks/entity/mockGeneratedEntityData.js.map +1 -1
- package/dist/mocks/entity/mockProject.js.map +1 -1
- package/dist/mocks/entity/mockProjectView.js.map +1 -1
- package/dist/mocks/entity/mockRootEntity.js.map +1 -1
- package/dist/mocks/entity/mockTableEntity.js.map +1 -1
- package/dist/mocks/mockWiki.js.map +1 -1
- package/dist/mocks/msw/handlers/asyncJobHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/challengeHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/changePasswordHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/discussionHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/entityHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/fileHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/gridHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/personalAccessTokenHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/subscriptionHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/teamHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/userProfileHandlers.js.map +1 -1
- package/dist/mocks/msw/handlers/wikiHandlers.js.map +1 -1
- package/dist/mocks/provenance/mockActivity.js.map +1 -1
- package/dist/mocks/query/mockReleaseCardsTableQueryResultBundle.js.map +1 -1
- package/dist/ror-client/index.js.map +1 -1
- package/dist/style/components/_cards.scss +4 -0
- package/dist/style/components/_data-grid-extra.css +1 -1
- package/dist/style/components/_data-grid-extra.scss +2 -0
- package/dist/style/main.css +1 -1
- package/dist/synapse-client/HttpClient.js.map +1 -1
- package/dist/synapse-client/SynapseClient.js.map +1 -1
- package/dist/synapse-queries/QueryMatching.test-utils.js.map +1 -1
- package/dist/synapse-queries/auth/useTwoFactorEnrollment.js.map +1 -1
- package/dist/synapse-queries/curation/task/useCurationTask.d.ts +1 -1
- package/dist/synapse-queries/curation/task/useCurationTask.d.ts.map +1 -1
- package/dist/synapse-queries/curation/task/useCurationTask.js +1 -1
- package/dist/synapse-queries/curation/task/useCurationTask.js.map +1 -1
- package/dist/synapse-queries/dataaccess/useRestrictionInformation.js.map +1 -1
- package/dist/synapse-queries/doi/useDOI.js.map +1 -1
- package/dist/synapse-queries/download/useDownloadList.js.map +1 -1
- package/dist/synapse-queries/entity/useEntity.js.map +1 -1
- package/dist/synapse-queries/entity/useEntityBundle.js.map +1 -1
- package/dist/synapse-queries/entity/useExportTableQueryToAnalysisPlatform.js.map +1 -1
- package/dist/synapse-queries/entity/useExportToTerra.js.map +1 -1
- package/dist/synapse-queries/entity/useGetQueryResultBundle.js.map +1 -1
- package/dist/synapse-queries/entity/useSchema.js.map +1 -1
- package/dist/synapse-queries/file/UploadToS3.js.map +1 -1
- package/dist/synapse-queries/file/useDirectUploadToS3.js.map +1 -1
- package/dist/synapse-queries/file/useFiles.js.map +1 -1
- package/dist/synapse-queries/forum/useReply.js.map +1 -1
- package/dist/synapse-queries/forum/useThread.js.map +1 -1
- package/dist/synapse-queries/grid/useEstablishWebsocketConnection.d.ts +2 -0
- package/dist/synapse-queries/grid/useEstablishWebsocketConnection.d.ts.map +1 -1
- package/dist/synapse-queries/grid/useEstablishWebsocketConnection.js.map +1 -1
- package/dist/synapse-queries/grid/useExportGrid.js.map +1 -1
- package/dist/synapse-queries/grid/useGridSession.js.map +1 -1
- package/dist/synapse-queries/grid/useImportCsvIntoGrid.js.map +1 -1
- package/dist/synapse-queries/subscription/useSubscription.js.map +1 -1
- package/dist/synapse-queries/table/useGetCsvPreview.js.map +1 -1
- package/dist/synapse-queries/table/useTableUpdateTransaction.js.map +1 -1
- package/dist/synapse-queries/team/useTeamMembers.js.map +1 -1
- package/dist/synapse-queries/user/useGetUserChallenges.js.map +1 -1
- package/dist/synapse-queries/user/useUserBundle.js.map +1 -1
- package/dist/synapse-queries/user/useUserGroupHeader.js.map +1 -1
- package/dist/testutils/ReactQueryMockUtils.js.map +1 -1
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/AppUtils/session/SynapseSessionManager.js.map +1 -1
- package/dist/utils/AppUtils/session/useSessionManager.js.map +1 -1
- package/dist/utils/PermissionLevelToAccessType.js.map +1 -1
- package/dist/utils/challenge/evaluation/EvaluationUtils.js.map +1 -1
- package/dist/utils/context/SynapseContext.js.map +1 -1
- package/dist/utils/functions/AccessControlListUtils.d.ts +4 -0
- package/dist/utils/functions/AccessControlListUtils.d.ts.map +1 -1
- package/dist/utils/functions/AccessControlListUtils.js +12 -1
- package/dist/utils/functions/AccessControlListUtils.js.map +1 -1
- package/dist/utils/functions/GridApiUtils.js.map +1 -1
- package/dist/utils/functions/QueryFilterUtils.js.map +1 -1
- package/dist/utils/functions/RealmUtils.d.ts +4 -0
- package/dist/utils/functions/RealmUtils.d.ts.map +1 -1
- package/dist/utils/functions/RealmUtils.js +9 -3
- package/dist/utils/functions/RealmUtils.js.map +1 -1
- package/dist/utils/functions/SanitizeHtmlUtils.js.map +1 -1
- package/dist/utils/functions/SanitizeHtmlUtils.test-utils.js.map +1 -1
- package/dist/utils/functions/SqlFunctions.js.map +1 -1
- package/dist/utils/functions/StringUtils.js.map +1 -1
- package/dist/utils/functions/deepLinkingUtils.js.map +1 -1
- package/dist/utils/functions/getDataFromFromStorage.js.map +1 -1
- package/dist/utils/functions/getEndpoint.js.map +1 -1
- package/dist/utils/functions/getUserData.js.map +1 -1
- package/dist/utils/functions/queryUtils.js.map +1 -1
- package/dist/utils/functions/testDownloadSpeed.js.map +1 -1
- package/dist/utils/hooks/useConfirmItems.js.map +1 -1
- package/dist/utils/hooks/useCookiePreferences.js.map +1 -1
- package/dist/utils/hooks/useCreateShortUrl.js.map +1 -1
- package/dist/utils/hooks/useDetectSSOCode.js.map +1 -1
- package/dist/utils/hooks/useDirectDownloadHandler.js.map +1 -1
- package/dist/utils/hooks/useGetGoalData.js.map +1 -1
- package/dist/utils/hooks/useGetInfoFromIds.js.map +1 -1
- package/dist/utils/hooks/useImageUrlUtils.js.map +1 -1
- package/dist/utils/hooks/useImmutableTableQuery/useImmutableTableQuery.js.map +1 -1
- package/dist/utils/hooks/useImmutableTableQuery/useTableQueryReducer.js.map +1 -1
- package/dist/utils/hooks/useIsBot.js.map +1 -1
- package/dist/utils/hooks/useListState.js.map +1 -1
- package/dist/utils/hooks/useLogin.d.ts.map +1 -1
- package/dist/utils/hooks/useLogin.js +53 -52
- package/dist/utils/hooks/useLogin.js.map +1 -1
- package/dist/utils/hooks/useMutuallyExclusiveState.js.map +1 -1
- package/dist/utils/hooks/useOverlay.js.map +1 -1
- package/dist/utils/hooks/usePreFetchResource.js.map +1 -1
- package/dist/utils/hooks/useQuerySearchParam.js.map +1 -1
- package/dist/utils/hooks/useScrollFadeTransition.js.map +1 -1
- package/dist/utils/hooks/useSet.js.map +1 -1
- package/dist/utils/hooks/useSourceAppConfigs.js.map +1 -1
- package/dist/utils/hooks/useTableImageUrl.js.map +1 -1
- package/dist/utils/hooks/useUploadFileEntity/useCreatePathsAndGetParentId.js.map +1 -1
- package/dist/utils/hooks/useUploadFileEntity/useLinkFileEntityToURL.js.map +1 -1
- package/dist/utils/hooks/useUploadFileEntity/usePrepareFileEntityUpload.js.map +1 -1
- package/dist/utils/hooks/useUploadFileEntity/useTrackFileUploads.js.map +1 -1
- package/dist/utils/hooks/useUploadFileEntity/useUploadFileEntities.js.map +1 -1
- package/dist/utils/hooks/useUploadFileEntity/useUploadFiles.js.map +1 -1
- package/dist/utils/hooks/useUploadFileEntity/willUploadsExceedStorageLimit.js.map +1 -1
- package/dist/utils/html/TargetEnum.js.map +1 -1
- package/dist/utils/jsonschema/SchemaAnnotationUtils.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SynapseSessionManager.js","names":[],"sources":["../../../../src/utils/AppUtils/session/SynapseSessionManager.ts"],"sourcesContent":["import SynapseClient from '@/synapse-client'\nimport {\n BackendDestinationEnum,\n getEndpoint,\n} from '@/utils/functions/getEndpoint'\nimport {\n OAuthTokenIntrospectionResponse,\n RealmPrincipal,\n SynapseClient as SynapseOpenApiClient,\n SynapseClientError,\n} from '@sage-bionetworks/synapse-client'\n\nconst REFRESH_INTERVAL_MS = 60_000\n\nexport type SessionState = {\n token: string | undefined\n realmId: string | undefined\n userId: string | undefined\n isAuthenticated: boolean\n hasInitializedSession: boolean\n}\n\nexport type SynapseSessionManagerOptions = {\n /** The realm that an unauthenticated user should be signed in to. Defaults to \"0\", the public Synapse realm */\n defaultRealm?: string\n /** If defined, specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated.\n * If the elapsed time is greater than this value, the session will be cleared and the user will have to re-authenticate. */\n maxAge?: number\n /** Called if the user is not authenticated when the session is initialized. */\n onMissingExpectedAuthentication?: () => void\n /** Called when a stored token fails validation (e.g. expired). Called after the session manager signs out.\n * If not provided, the session manager will call `window.location.reload()` after signing out. */\n onSessionInvalid?: () => void\n}\n\nconst INITIAL_STATE: SessionState = {\n token: undefined,\n realmId: undefined,\n userId: undefined,\n isAuthenticated: false,\n hasInitializedSession: false,\n}\n\n/**\n * Framework-agnostic session manager for Synapse applications.\n *\n * Handles the full session lifecycle: token retrieval from browser cookies,\n * token validation via OAuth2 introspection, periodic refresh, and sign-out.\n *\n * Can be used directly from any JavaScript context (GWT/JsInterop, vanilla JS, etc.)\n * or wrapped by a framework-specific adapter (e.g. a React hook via `useSyncExternalStore`).\n *\n * Implements the external store contract: use {@link subscribe} and {@link getSnapshot}\n * to integrate with React's `useSyncExternalStore`, or any other subscription-based framework.\n */\nexport class SynapseSessionManager {\n private state: SessionState = INITIAL_STATE\n private listeners = new Set<() => void>()\n private intervalId: ReturnType<typeof setInterval> | null = null\n private options: Required<\n Pick<SynapseSessionManagerOptions, 'defaultRealm'>\n > &\n SynapseSessionManagerOptions\n\n constructor(options: SynapseSessionManagerOptions = {}) {\n this.options = {\n ...options,\n defaultRealm: options.defaultRealm ?? '0',\n }\n // Bind methods so they can be passed directly to useSyncExternalStore\n this.subscribe = this.subscribe.bind(this)\n this.getSnapshot = this.getSnapshot.bind(this)\n this.getServerSnapshot = this.getServerSnapshot.bind(this)\n }\n\n /**\n * Subscribe to state changes. The listener is called whenever session state is updated.\n * @returns an unsubscribe function.\n *\n * Compatible with React's `useSyncExternalStore(manager.subscribe, manager.getSnapshot)`.\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Get the current session state snapshot. Returns a stable reference that only\n * changes when the state is updated.\n *\n * Compatible with React's `useSyncExternalStore(manager.subscribe, manager.getSnapshot)`.\n */\n getSnapshot(): SessionState {\n return this.state\n }\n\n /**\n * Get the server-side (SSR) snapshot: always returns the unauthenticated initial state.\n * Required by React's `useSyncExternalStore` when rendering on the server.\n */\n getServerSnapshot(): SessionState {\n return INITIAL_STATE\n }\n\n /**\n * Update mutable options. Values are read at `refreshSession`/`clearSession` call time,\n * so changes take effect on the next refresh cycle.\n */\n setOptions(\n options: Pick<SynapseSessionManagerOptions, 'defaultRealm' | 'maxAge'>,\n ): void {\n if (options.defaultRealm !== undefined) {\n this.options.defaultRealm = options.defaultRealm\n }\n if (options.maxAge !== undefined) {\n this.options.maxAge = options.maxAge\n }\n }\n\n /**\n * Start the session manager: performs an initial refresh and sets up the periodic refresh interval.\n */\n start(): void {\n this.refreshSession()\n this.intervalId = setInterval(() => {\n this.refreshSession()\n }, REFRESH_INTERVAL_MS)\n }\n\n /**\n * Stop the periodic refresh and clean up resources.\n */\n dispose(): void {\n if (this.intervalId !== null) {\n clearInterval(this.intervalId)\n this.intervalId = null\n }\n }\n\n /**\n * Refresh the session by reading the stored token, validating it, and updating state.\n * If no token is stored, initializes an anonymous session.\n * If the token is invalid, signs out and triggers onSessionInvalid (or reloads the page).\n */\n async refreshSession(): Promise<void> {\n let token = await SynapseSessionManager.getStoredToken()\n\n if (!token) {\n token = await this.initAnonymousUserState()\n }\n\n const introspectionResponse = await SynapseSessionManager.validateToken(\n token,\n this.options.maxAge,\n )\n if (introspectionResponse === null) {\n // Token was invalid (e.g. expired). Clear from browser storage and get a fresh anonymous token.\n await SynapseClient.signOut(this.options.defaultRealm)\n if (this.options.onSessionInvalid) {\n this.options.onSessionInvalid()\n } else {\n window.location.reload()\n }\n return\n }\n\n const currentUserId = introspectionResponse.sub\n\n let realmPrincipals: RealmPrincipal | undefined\n try {\n realmPrincipals = await SynapseSessionManager.getCurrentRealmPrincipals(\n token,\n )\n } catch (error) {\n // Special handling for Terms of Service and 2FA errors - allow initialization to continue\n // so the app can prompt the user to complete the required action\n const isTosError =\n error instanceof SynapseClientError &&\n error.reason?.toLowerCase().includes('terms of service')\n const isTwoFaError =\n error instanceof SynapseClientError &&\n error.errorResponse &&\n 'errorCode' in error.errorResponse &&\n error.errorResponse.errorCode === 'TWO_FA_ENABLED_REQUIRED'\n\n if (isTosError || isTwoFaError) {\n const errorType = isTosError\n ? 'Terms of Service not accepted'\n : 'Two factor authentication required'\n console.warn(\n `${errorType}. Session will be initialized without realm principals.`,\n )\n // Continue initialization with partial data - the app's flow will handle this\n this.updateState({\n token,\n hasInitializedSession: true,\n realmId: this.options.defaultRealm,\n userId: currentUserId,\n isAuthenticated: true, // User has a valid token, even if ToS/2FA not complete\n })\n return\n }\n // For any other error, re-throw to be handled by caller\n console.error('Error fetching realm principals: ', error)\n throw error\n }\n\n const isAuthenticated = currentUserId !== realmPrincipals.anonymousUser\n\n /*\n NOTE: It's possible for a user to possess an anonymous token from a different realm (e.g. if they started on an app with a default realm, \n then navigated to a different app with a different default realm). Currently, we will do nothing, so the user will stay in the realm in which they started.\n We may consider different approaches in the future as new requirements arise, such as\n - Replacing the anonymous token with a new one from the default realm (could cause issues if two apps with different realms are used in different tabs)\n - Storing anonymous tokens per-realm\n - Adding UI to choose a realm or account to use\n */\n\n this.updateState({\n token,\n hasInitializedSession: true,\n realmId: realmPrincipals.realmId,\n userId: currentUserId,\n isAuthenticated,\n })\n }\n\n /**\n * Clear the current session by signing out and initializing an anonymous session.\n * Does NOT handle navigation/reload — the caller is responsible for that.\n */\n async clearSession(): Promise<void> {\n await this.initAnonymousUserState()\n await this.refreshSession()\n }\n\n /**\n * Attempt to get the stored access token from the browser cookie.\n * Returns the token string, or undefined if not found or on error.\n */\n static async getStoredToken(): Promise<string | undefined> {\n try {\n return await SynapseClient.getAccessTokenFromCookie()\n } catch (e) {\n console.error('Unable to get the access token: ', e)\n return undefined\n }\n }\n\n /**\n * Validate the token by calling the token introspection service.\n * @return the introspection response if valid, or null if the token is invalid\n */\n static async validateToken(\n accessToken: string,\n maxAge?: number,\n ): Promise<OAuthTokenIntrospectionResponse | null> {\n const synapseClient = new SynapseOpenApiClient({\n accessToken,\n basePath: getEndpoint(BackendDestinationEnum.REPO_ENDPOINT),\n })\n try {\n const response =\n await synapseClient.openIDConnectServicesClient.postAuthV1Oauth2Introspect(\n {\n oAuthTokenIntrospectionRequest: {\n token: accessToken,\n max_age: maxAge,\n },\n },\n )\n if (!response.active) {\n throw new Error('Token is not active.')\n }\n return response\n } catch (e) {\n console.error(\n 'Error validating the access token. Clearing session. Error: ',\n (e as Error).message,\n )\n }\n\n return null\n }\n\n /**\n * Get the realm principals for the current token.\n */\n static async getCurrentRealmPrincipals(\n accessToken: string,\n ): Promise<RealmPrincipal> {\n const synapseClient = new SynapseOpenApiClient({\n accessToken,\n basePath: getEndpoint(BackendDestinationEnum.REPO_ENDPOINT),\n })\n return synapseClient.realmServicesClient.getRepoV1RealmPrincipals()\n }\n\n /**\n * Initialize the session for an anonymous user.\n * @return the new anonymous token.\n */\n private async initAnonymousUserState(): Promise<string> {\n if (this.options.onMissingExpectedAuthentication) {\n this.options.onMissingExpectedAuthentication()\n }\n return SynapseClient.signOut(this.options.defaultRealm)\n }\n\n private updateState(newState: SessionState): void {\n this.state = newState\n this.emitChange()\n }\n\n private emitChange(): void {\n for (const listener of this.listeners) {\n listener()\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SynapseSessionManager.js","names":[],"sources":["../../../../src/utils/AppUtils/session/SynapseSessionManager.ts"],"sourcesContent":["import SynapseClient from '@/synapse-client'\nimport {\n BackendDestinationEnum,\n getEndpoint,\n} from '@/utils/functions/getEndpoint'\nimport {\n OAuthTokenIntrospectionResponse,\n RealmPrincipal,\n SynapseClient as SynapseOpenApiClient,\n SynapseClientError,\n} from '@sage-bionetworks/synapse-client'\n\nconst REFRESH_INTERVAL_MS = 60_000\n\nexport type SessionState = {\n token: string | undefined\n realmId: string | undefined\n userId: string | undefined\n isAuthenticated: boolean\n hasInitializedSession: boolean\n}\n\nexport type SynapseSessionManagerOptions = {\n /** The realm that an unauthenticated user should be signed in to. Defaults to \"0\", the public Synapse realm */\n defaultRealm?: string\n /** If defined, specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated.\n * If the elapsed time is greater than this value, the session will be cleared and the user will have to re-authenticate. */\n maxAge?: number\n /** Called if the user is not authenticated when the session is initialized. */\n onMissingExpectedAuthentication?: () => void\n /** Called when a stored token fails validation (e.g. expired). Called after the session manager signs out.\n * If not provided, the session manager will call `window.location.reload()` after signing out. */\n onSessionInvalid?: () => void\n}\n\nconst INITIAL_STATE: SessionState = {\n token: undefined,\n realmId: undefined,\n userId: undefined,\n isAuthenticated: false,\n hasInitializedSession: false,\n}\n\n/**\n * Framework-agnostic session manager for Synapse applications.\n *\n * Handles the full session lifecycle: token retrieval from browser cookies,\n * token validation via OAuth2 introspection, periodic refresh, and sign-out.\n *\n * Can be used directly from any JavaScript context (GWT/JsInterop, vanilla JS, etc.)\n * or wrapped by a framework-specific adapter (e.g. a React hook via `useSyncExternalStore`).\n *\n * Implements the external store contract: use {@link subscribe} and {@link getSnapshot}\n * to integrate with React's `useSyncExternalStore`, or any other subscription-based framework.\n */\nexport class SynapseSessionManager {\n private state: SessionState = INITIAL_STATE\n private listeners = new Set<() => void>()\n private intervalId: ReturnType<typeof setInterval> | null = null\n private options: Required<\n Pick<SynapseSessionManagerOptions, 'defaultRealm'>\n > &\n SynapseSessionManagerOptions\n\n constructor(options: SynapseSessionManagerOptions = {}) {\n this.options = {\n ...options,\n defaultRealm: options.defaultRealm ?? '0',\n }\n // Bind methods so they can be passed directly to useSyncExternalStore\n this.subscribe = this.subscribe.bind(this)\n this.getSnapshot = this.getSnapshot.bind(this)\n this.getServerSnapshot = this.getServerSnapshot.bind(this)\n }\n\n /**\n * Subscribe to state changes. The listener is called whenever session state is updated.\n * @returns an unsubscribe function.\n *\n * Compatible with React's `useSyncExternalStore(manager.subscribe, manager.getSnapshot)`.\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Get the current session state snapshot. Returns a stable reference that only\n * changes when the state is updated.\n *\n * Compatible with React's `useSyncExternalStore(manager.subscribe, manager.getSnapshot)`.\n */\n getSnapshot(): SessionState {\n return this.state\n }\n\n /**\n * Get the server-side (SSR) snapshot: always returns the unauthenticated initial state.\n * Required by React's `useSyncExternalStore` when rendering on the server.\n */\n getServerSnapshot(): SessionState {\n return INITIAL_STATE\n }\n\n /**\n * Update mutable options. Values are read at `refreshSession`/`clearSession` call time,\n * so changes take effect on the next refresh cycle.\n */\n setOptions(\n options: Pick<SynapseSessionManagerOptions, 'defaultRealm' | 'maxAge'>,\n ): void {\n if (options.defaultRealm !== undefined) {\n this.options.defaultRealm = options.defaultRealm\n }\n if (options.maxAge !== undefined) {\n this.options.maxAge = options.maxAge\n }\n }\n\n /**\n * Start the session manager: performs an initial refresh and sets up the periodic refresh interval.\n */\n start(): void {\n this.refreshSession()\n this.intervalId = setInterval(() => {\n this.refreshSession()\n }, REFRESH_INTERVAL_MS)\n }\n\n /**\n * Stop the periodic refresh and clean up resources.\n */\n dispose(): void {\n if (this.intervalId !== null) {\n clearInterval(this.intervalId)\n this.intervalId = null\n }\n }\n\n /**\n * Refresh the session by reading the stored token, validating it, and updating state.\n * If no token is stored, initializes an anonymous session.\n * If the token is invalid, signs out and triggers onSessionInvalid (or reloads the page).\n */\n async refreshSession(): Promise<void> {\n let token = await SynapseSessionManager.getStoredToken()\n\n if (!token) {\n token = await this.initAnonymousUserState()\n }\n\n const introspectionResponse = await SynapseSessionManager.validateToken(\n token,\n this.options.maxAge,\n )\n if (introspectionResponse === null) {\n // Token was invalid (e.g. expired). Clear from browser storage and get a fresh anonymous token.\n await SynapseClient.signOut(this.options.defaultRealm)\n if (this.options.onSessionInvalid) {\n this.options.onSessionInvalid()\n } else {\n window.location.reload()\n }\n return\n }\n\n const currentUserId = introspectionResponse.sub\n\n let realmPrincipals: RealmPrincipal | undefined\n try {\n realmPrincipals = await SynapseSessionManager.getCurrentRealmPrincipals(\n token,\n )\n } catch (error) {\n // Special handling for Terms of Service and 2FA errors - allow initialization to continue\n // so the app can prompt the user to complete the required action\n const isTosError =\n error instanceof SynapseClientError &&\n error.reason?.toLowerCase().includes('terms of service')\n const isTwoFaError =\n error instanceof SynapseClientError &&\n error.errorResponse &&\n 'errorCode' in error.errorResponse &&\n error.errorResponse.errorCode === 'TWO_FA_ENABLED_REQUIRED'\n\n if (isTosError || isTwoFaError) {\n const errorType = isTosError\n ? 'Terms of Service not accepted'\n : 'Two factor authentication required'\n console.warn(\n `${errorType}. Session will be initialized without realm principals.`,\n )\n // Continue initialization with partial data - the app's flow will handle this\n this.updateState({\n token,\n hasInitializedSession: true,\n realmId: this.options.defaultRealm,\n userId: currentUserId,\n isAuthenticated: true, // User has a valid token, even if ToS/2FA not complete\n })\n return\n }\n // For any other error, re-throw to be handled by caller\n console.error('Error fetching realm principals: ', error)\n throw error\n }\n\n const isAuthenticated = currentUserId !== realmPrincipals.anonymousUser\n\n /*\n NOTE: It's possible for a user to possess an anonymous token from a different realm (e.g. if they started on an app with a default realm, \n then navigated to a different app with a different default realm). Currently, we will do nothing, so the user will stay in the realm in which they started.\n We may consider different approaches in the future as new requirements arise, such as\n - Replacing the anonymous token with a new one from the default realm (could cause issues if two apps with different realms are used in different tabs)\n - Storing anonymous tokens per-realm\n - Adding UI to choose a realm or account to use\n */\n\n this.updateState({\n token,\n hasInitializedSession: true,\n realmId: realmPrincipals.realmId,\n userId: currentUserId,\n isAuthenticated,\n })\n }\n\n /**\n * Clear the current session by signing out and initializing an anonymous session.\n * Does NOT handle navigation/reload — the caller is responsible for that.\n */\n async clearSession(): Promise<void> {\n await this.initAnonymousUserState()\n await this.refreshSession()\n }\n\n /**\n * Attempt to get the stored access token from the browser cookie.\n * Returns the token string, or undefined if not found or on error.\n */\n static async getStoredToken(): Promise<string | undefined> {\n try {\n return await SynapseClient.getAccessTokenFromCookie()\n } catch (e) {\n console.error('Unable to get the access token: ', e)\n return undefined\n }\n }\n\n /**\n * Validate the token by calling the token introspection service.\n * @return the introspection response if valid, or null if the token is invalid\n */\n static async validateToken(\n accessToken: string,\n maxAge?: number,\n ): Promise<OAuthTokenIntrospectionResponse | null> {\n const synapseClient = new SynapseOpenApiClient({\n accessToken,\n basePath: getEndpoint(BackendDestinationEnum.REPO_ENDPOINT),\n })\n try {\n const response =\n await synapseClient.openIDConnectServicesClient.postAuthV1Oauth2Introspect(\n {\n oAuthTokenIntrospectionRequest: {\n token: accessToken,\n max_age: maxAge,\n },\n },\n )\n if (!response.active) {\n throw new Error('Token is not active.')\n }\n return response\n } catch (e) {\n console.error(\n 'Error validating the access token. Clearing session. Error: ',\n (e as Error).message,\n )\n }\n\n return null\n }\n\n /**\n * Get the realm principals for the current token.\n */\n static async getCurrentRealmPrincipals(\n accessToken: string,\n ): Promise<RealmPrincipal> {\n const synapseClient = new SynapseOpenApiClient({\n accessToken,\n basePath: getEndpoint(BackendDestinationEnum.REPO_ENDPOINT),\n })\n return synapseClient.realmServicesClient.getRepoV1RealmPrincipals()\n }\n\n /**\n * Initialize the session for an anonymous user.\n * @return the new anonymous token.\n */\n private async initAnonymousUserState(): Promise<string> {\n if (this.options.onMissingExpectedAuthentication) {\n this.options.onMissingExpectedAuthentication()\n }\n return SynapseClient.signOut(this.options.defaultRealm)\n }\n\n private updateState(newState: SessionState): void {\n this.state = newState\n this.emitChange()\n }\n\n private emitChange(): void {\n for (const listener of this.listeners) {\n listener()\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSessionManager.js","names":[],"sources":["../../../../src/utils/AppUtils/session/useSessionManager.ts"],"sourcesContent":["import { useTermsOfServiceStatus } from '@/synapse-queries/termsOfService/useTermsOfService'\nimport { useGetTwoFactorEnrollmentStatusWithAccessToken } from '@/synapse-queries/auth/useTwoFactorEnrollment'\nimport { TwoFactorAuthErrorResponse } from '@sage-bionetworks/synapse-client/generated/models/TwoFactorAuthErrorResponse'\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react'\nimport { useNavigate } from 'react-router'\nimport useDetectSSOCode from '../../hooks/useDetectSSOCode'\nimport { restoreLastPlace } from '../AppUtils'\nimport { ApplicationSessionContextType } from './ApplicationSessionContext'\nimport { SynapseSessionManager } from './SynapseSessionManager'\n\nexport type UseSessionManagerOptions = {\n /** The realm that an unauthenticated user should be signed in to. Defaults to \"0\", the public Synapse realm */\n defaultRealm?: string\n /* If defined, specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated. If the elapsed time is greater than this value, the session will be cleared and the user will have to re-authenticate. */\n maxAge?: number\n /** Called if the user is not authenticated when the session is initialized. Can be used to trigger an error if authentication is expected. */\n onMissingExpectedAuthentication?: () => void\n /*\n * Callback invoked after the user has successfully signed in through SSO when the purpose of signing in is to disable 2FA on the account.\n * The twoFactorAuthSSOError and twoFaResetToken are passed in the callback and can be used to complete the 2FA reset process.\n * This only needs to be implemented if the app implements the workflow to disable 2FA (i.e. only if the app supports account management).\n */\n onTwoFactorAuthResetThroughSSO?: (\n twoFactorAuthSSOError: TwoFactorAuthErrorResponse,\n twoFaResetToken: string,\n ) => void\n}\n\nexport type UseSessionManagerReturn = {\n sessionContext: ApplicationSessionContextType\n token: string | undefined\n}\n\n/**\n * Hook that manages the Synapse user session lifecycle.\n *\n * Handles token acquisition, validation, SSO detection, and provides\n * all values needed for the ApplicationSessionContext and SynapseContext.\n *\n * This is a React wrapper around {@link SynapseSessionManager}, which contains\n * the framework-agnostic session logic. Session state is synchronized via\n * `useSyncExternalStore`.\n *\n * Must be called within a react-router Router and a react-query QueryClientProvider.\n */\nexport function useSessionManager(\n options: UseSessionManagerOptions = {},\n): UseSessionManagerReturn {\n const {\n defaultRealm = '0',\n maxAge,\n onMissingExpectedAuthentication,\n onTwoFactorAuthResetThroughSSO,\n } = options\n\n const navigate = useNavigate()\n\n const [twoFactorAuthSSOError, setTwoFactorAuthSSOError] = useState<\n TwoFactorAuthErrorResponse | undefined\n >()\n\n // Store latest callback refs so the manager doesn't need to be recreated when callbacks change\n const onMissingExpectedAuthenticationRef = useRef(\n onMissingExpectedAuthentication,\n )\n onMissingExpectedAuthenticationRef.current = onMissingExpectedAuthentication\n\n const [manager] = useState(\n () =>\n new SynapseSessionManager({\n defaultRealm,\n maxAge,\n onMissingExpectedAuthentication: () => {\n onMissingExpectedAuthenticationRef.current?.()\n },\n }),\n )\n\n // Keep mutable options in sync with props\n useEffect(() => {\n manager.setOptions({ defaultRealm, maxAge })\n }, [manager, defaultRealm, maxAge])\n\n const sessionState = useSyncExternalStore(\n manager.subscribe,\n manager.getSnapshot,\n manager.getServerSnapshot,\n )\n\n useEffect(() => {\n manager.start()\n return () => manager.dispose()\n }, [manager])\n\n const { data: tosStatus } = useTermsOfServiceStatus(sessionState.token, {\n enabled: sessionState.isAuthenticated,\n })\n const { data: twoFactorStatus } =\n useGetTwoFactorEnrollmentStatusWithAccessToken(sessionState.token, {\n enabled: sessionState.isAuthenticated,\n })\n\n const refreshSession = useCallback(async () => {\n setTwoFactorAuthSSOError(undefined)\n await manager.refreshSession()\n }, [manager])\n\n const clearSession = useCallback(\n async (onBeforeReload?: () => void) => {\n await manager.clearSession()\n if (onBeforeReload) {\n onBeforeReload()\n }\n // In all cases when the session is cleared we should refresh the page to ensure private data is not being shown\n navigate(0)\n },\n [navigate, manager],\n )\n\n const { isLoading: isLoadingSSO } = useDetectSSOCode({\n onSignInComplete: () => {\n restoreLastPlace(navigate)\n refreshSession()\n },\n onTwoFactorAuthRequired: twoFactorAuthError => {\n setTwoFactorAuthSSOError(twoFactorAuthError)\n },\n onTwoFactorAuthResetTokenPresent: (twoFactorAuthError, twoFaResetToken) => {\n setTwoFactorAuthSSOError(twoFactorAuthError)\n if (onTwoFactorAuthResetThroughSSO) {\n onTwoFactorAuthResetThroughSSO(twoFactorAuthError, twoFaResetToken)\n }\n },\n onError: (err: unknown) => {\n // Throw the error so it propagates to an error boundary\n throw err\n },\n isInitializingSession: !sessionState.hasInitializedSession,\n isAuthenticated: sessionState.isAuthenticated,\n })\n\n const sessionContext: ApplicationSessionContextType = {\n token: sessionState.token,\n userId: sessionState.userId,\n realmId: sessionState.realmId,\n isAuthenticated: sessionState.isAuthenticated,\n termsOfServiceStatus: tosStatus,\n refreshSession,\n clearSession,\n isLoadingSSO,\n twoFactorAuthSSOErrorResponse: twoFactorAuthSSOError,\n hasInitializedSession: sessionState.hasInitializedSession,\n twoFactorStatus,\n }\n\n return { sessionContext, token: sessionState.token }\n}\n"],"mappings":";;;;;;;;AAmDA,SAAgB,EACd,IAAoC,EAAE,EACb;CACzB,IAAM,EACJ,kBAAe,KACf,WACA,oCACA,sCACE,GAEE,IAAW,GAAa,EAExB,CAAC,GAAuB,KAA4B,GAEvD,EAGG,IAAqC,EACzC,EACD;AACD,GAAmC,UAAU;CAE7C,IAAM,CAAC,KAAW,QAEd,IAAI,EAAsB;EACxB;EACA;EACA,uCAAuC;AACrC,KAAmC,WAAW;;EAEjD,CAAC,CACL;AAGD,SAAgB;AACd,IAAQ,WAAW;GAAE;GAAc;GAAQ,CAAC;IAC3C;EAAC;EAAS;EAAc;EAAO,CAAC;CAEnC,IAAM,IAAe,EACnB,EAAQ,WACR,EAAQ,aACR,EAAQ,kBACT;AAED,UACE,EAAQ,OAAO,QACF,EAAQ,SAAS,GAC7B,CAAC,EAAQ,CAAC;CAEb,IAAM,EAAE,MAAM,MAAc,EAAwB,EAAa,OAAO,EACtE,SAAS,EAAa,iBACvB,CAAC,EACI,EAAE,MAAM,MACZ,EAA+C,EAAa,OAAO,EACjE,SAAS,EAAa,iBACvB,CAAC,EAEE,IAAiB,EAAY,YAAY;AAE7C,EADA,EAAyB,KAAA,EAAU,EACnC,MAAM,EAAQ,gBAAgB;IAC7B,CAAC,EAAQ,CAAC,EAEP,IAAe,EACnB,OAAO,MAAgC;AAMrC,EALA,MAAM,EAAQ,cAAc,EACxB,KACF,GAAgB,EAGlB,EAAS,EAAE;IAEb,CAAC,GAAU,EAAQ,CACpB,EAEK,EAAE,WAAW,MAAiB,EAAiB;EACnD,wBAAwB;AAEtB,GADA,EAAiB,EAAS,EAC1B,GAAgB;;EAElB,0BAAyB,MAAsB;AAC7C,KAAyB,EAAmB;;EAE9C,mCAAmC,GAAoB,MAAoB;AAEzE,GADA,EAAyB,EAAmB,EACxC,KACF,EAA+B,GAAoB,EAAgB;;EAGvE,UAAU,MAAiB;AAEzB,SAAM;;EAER,uBAAuB,CAAC,EAAa;EACrC,iBAAiB,EAAa;EAC/B,CAAC;AAgBF,QAAO;EAAE,
|
|
1
|
+
{"version":3,"file":"useSessionManager.js","names":[],"sources":["../../../../src/utils/AppUtils/session/useSessionManager.ts"],"sourcesContent":["import { useTermsOfServiceStatus } from '@/synapse-queries/termsOfService/useTermsOfService'\nimport { useGetTwoFactorEnrollmentStatusWithAccessToken } from '@/synapse-queries/auth/useTwoFactorEnrollment'\nimport { TwoFactorAuthErrorResponse } from '@sage-bionetworks/synapse-client/generated/models/TwoFactorAuthErrorResponse'\nimport {\n useCallback,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react'\nimport { useNavigate } from 'react-router'\nimport useDetectSSOCode from '../../hooks/useDetectSSOCode'\nimport { restoreLastPlace } from '../AppUtils'\nimport { ApplicationSessionContextType } from './ApplicationSessionContext'\nimport { SynapseSessionManager } from './SynapseSessionManager'\n\nexport type UseSessionManagerOptions = {\n /** The realm that an unauthenticated user should be signed in to. Defaults to \"0\", the public Synapse realm */\n defaultRealm?: string\n /* If defined, specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated. If the elapsed time is greater than this value, the session will be cleared and the user will have to re-authenticate. */\n maxAge?: number\n /** Called if the user is not authenticated when the session is initialized. Can be used to trigger an error if authentication is expected. */\n onMissingExpectedAuthentication?: () => void\n /*\n * Callback invoked after the user has successfully signed in through SSO when the purpose of signing in is to disable 2FA on the account.\n * The twoFactorAuthSSOError and twoFaResetToken are passed in the callback and can be used to complete the 2FA reset process.\n * This only needs to be implemented if the app implements the workflow to disable 2FA (i.e. only if the app supports account management).\n */\n onTwoFactorAuthResetThroughSSO?: (\n twoFactorAuthSSOError: TwoFactorAuthErrorResponse,\n twoFaResetToken: string,\n ) => void\n}\n\nexport type UseSessionManagerReturn = {\n sessionContext: ApplicationSessionContextType\n token: string | undefined\n}\n\n/**\n * Hook that manages the Synapse user session lifecycle.\n *\n * Handles token acquisition, validation, SSO detection, and provides\n * all values needed for the ApplicationSessionContext and SynapseContext.\n *\n * This is a React wrapper around {@link SynapseSessionManager}, which contains\n * the framework-agnostic session logic. Session state is synchronized via\n * `useSyncExternalStore`.\n *\n * Must be called within a react-router Router and a react-query QueryClientProvider.\n */\nexport function useSessionManager(\n options: UseSessionManagerOptions = {},\n): UseSessionManagerReturn {\n const {\n defaultRealm = '0',\n maxAge,\n onMissingExpectedAuthentication,\n onTwoFactorAuthResetThroughSSO,\n } = options\n\n const navigate = useNavigate()\n\n const [twoFactorAuthSSOError, setTwoFactorAuthSSOError] = useState<\n TwoFactorAuthErrorResponse | undefined\n >()\n\n // Store latest callback refs so the manager doesn't need to be recreated when callbacks change\n const onMissingExpectedAuthenticationRef = useRef(\n onMissingExpectedAuthentication,\n )\n onMissingExpectedAuthenticationRef.current = onMissingExpectedAuthentication\n\n const [manager] = useState(\n () =>\n new SynapseSessionManager({\n defaultRealm,\n maxAge,\n onMissingExpectedAuthentication: () => {\n onMissingExpectedAuthenticationRef.current?.()\n },\n }),\n )\n\n // Keep mutable options in sync with props\n useEffect(() => {\n manager.setOptions({ defaultRealm, maxAge })\n }, [manager, defaultRealm, maxAge])\n\n const sessionState = useSyncExternalStore(\n manager.subscribe,\n manager.getSnapshot,\n manager.getServerSnapshot,\n )\n\n useEffect(() => {\n manager.start()\n return () => manager.dispose()\n }, [manager])\n\n const { data: tosStatus } = useTermsOfServiceStatus(sessionState.token, {\n enabled: sessionState.isAuthenticated,\n })\n const { data: twoFactorStatus } =\n useGetTwoFactorEnrollmentStatusWithAccessToken(sessionState.token, {\n enabled: sessionState.isAuthenticated,\n })\n\n const refreshSession = useCallback(async () => {\n setTwoFactorAuthSSOError(undefined)\n await manager.refreshSession()\n }, [manager])\n\n const clearSession = useCallback(\n async (onBeforeReload?: () => void) => {\n await manager.clearSession()\n if (onBeforeReload) {\n onBeforeReload()\n }\n // In all cases when the session is cleared we should refresh the page to ensure private data is not being shown\n navigate(0)\n },\n [navigate, manager],\n )\n\n const { isLoading: isLoadingSSO } = useDetectSSOCode({\n onSignInComplete: () => {\n restoreLastPlace(navigate)\n refreshSession()\n },\n onTwoFactorAuthRequired: twoFactorAuthError => {\n setTwoFactorAuthSSOError(twoFactorAuthError)\n },\n onTwoFactorAuthResetTokenPresent: (twoFactorAuthError, twoFaResetToken) => {\n setTwoFactorAuthSSOError(twoFactorAuthError)\n if (onTwoFactorAuthResetThroughSSO) {\n onTwoFactorAuthResetThroughSSO(twoFactorAuthError, twoFaResetToken)\n }\n },\n onError: (err: unknown) => {\n // Throw the error so it propagates to an error boundary\n throw err\n },\n isInitializingSession: !sessionState.hasInitializedSession,\n isAuthenticated: sessionState.isAuthenticated,\n })\n\n const sessionContext: ApplicationSessionContextType = {\n token: sessionState.token,\n userId: sessionState.userId,\n realmId: sessionState.realmId,\n isAuthenticated: sessionState.isAuthenticated,\n termsOfServiceStatus: tosStatus,\n refreshSession,\n clearSession,\n isLoadingSSO,\n twoFactorAuthSSOErrorResponse: twoFactorAuthSSOError,\n hasInitializedSession: sessionState.hasInitializedSession,\n twoFactorStatus,\n }\n\n return { sessionContext, token: sessionState.token }\n}\n"],"mappings":";;;;;;;;AAmDA,SAAgB,EACd,IAAoC,EAAE,EACb;CACzB,IAAM,EACJ,kBAAe,KACf,WACA,oCACA,sCACE,GAEE,IAAW,GAAa,EAExB,CAAC,GAAuB,KAA4B,GAEvD,EAGG,IAAqC,EACzC,EACD;AACD,GAAmC,UAAU;CAE7C,IAAM,CAAC,KAAW,QAEd,IAAI,EAAsB;EACxB;EACA;EACA,uCAAuC;AACrC,KAAmC,WAAW;;EAEjD,CAAC,CACL;AAGD,SAAgB;AACd,IAAQ,WAAW;GAAE;GAAc;GAAQ,CAAC;IAC3C;EAAC;EAAS;EAAc;EAAO,CAAC;CAEnC,IAAM,IAAe,EACnB,EAAQ,WACR,EAAQ,aACR,EAAQ,kBACT;AAED,UACE,EAAQ,OAAO,QACF,EAAQ,SAAS,GAC7B,CAAC,EAAQ,CAAC;CAEb,IAAM,EAAE,MAAM,MAAc,EAAwB,EAAa,OAAO,EACtE,SAAS,EAAa,iBACvB,CAAC,EACI,EAAE,MAAM,MACZ,EAA+C,EAAa,OAAO,EACjE,SAAS,EAAa,iBACvB,CAAC,EAEE,IAAiB,EAAY,YAAY;AAE7C,EADA,EAAyB,KAAA,EAAU,EACnC,MAAM,EAAQ,gBAAgB;IAC7B,CAAC,EAAQ,CAAC,EAEP,IAAe,EACnB,OAAO,MAAgC;AAMrC,EALA,MAAM,EAAQ,cAAc,EACxB,KACF,GAAgB,EAGlB,EAAS,EAAE;IAEb,CAAC,GAAU,EAAQ,CACpB,EAEK,EAAE,WAAW,MAAiB,EAAiB;EACnD,wBAAwB;AAEtB,GADA,EAAiB,EAAS,EAC1B,GAAgB;;EAElB,0BAAyB,MAAsB;AAC7C,KAAyB,EAAmB;;EAE9C,mCAAmC,GAAoB,MAAoB;AAEzE,GADA,EAAyB,EAAmB,EACxC,KACF,EAA+B,GAAoB,EAAgB;;EAGvE,UAAU,MAAiB;AAEzB,SAAM;;EAER,uBAAuB,CAAC,EAAa;EACrC,iBAAiB,EAAa;EAC/B,CAAC;AAgBF,QAAO;EAAE,gBAAA;GAbP,OAAO,EAAa;GACpB,QAAQ,EAAa;GACrB,SAAS,EAAa;GACtB,iBAAiB,EAAa;GAC9B,sBAAsB;GACtB;GACA;GACA;GACA,+BAA+B;GAC/B,uBAAuB,EAAa;GACpC;GAGO;EAAgB,OAAO,EAAa;EAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PermissionLevelToAccessType.js","names":[],"sources":["../../src/utils/PermissionLevelToAccessType.ts"],"sourcesContent":["import { ACCESS_TYPE } from '@sage-bionetworks/synapse-types'\n\nconst entityPermissionLevels = [\n 'CAN_VIEW',\n 'CAN_DOWNLOAD',\n 'CAN_EDIT',\n 'CAN_EDIT_DELETE',\n 'CAN_ADMINISTER',\n] as const\nexport type EntityPermissionsLevel = (typeof entityPermissionLevels)[number]\n\nconst accessRequirementPermissionLevels = [\n 'CAN_REVIEW_SUBMISSIONS',\n 'IS_EXEMPTION_ELIGIBLE',\n 'CAN_REVIEW_SUBMISSIONS_AND_IS_EXEMPTION_ELIGIBLE',\n] as const\n\nexport type AccessRequirementPermissionsLevel =\n (typeof accessRequirementPermissionLevels)[number]\n\nconst oauthClientPermissionLevels = ['CAN_ADMINISTER_OAUTH_CLIENT'] as const\n\nexport type OAuthClientPermissionsLevel =\n (typeof oauthClientPermissionLevels)[number]\n\nconst portalClientPermissionLevels = ['CAN_ADMINISTER_PORTAL'] as const\n\nexport type PortalClientPermissionsLevel =\n (typeof portalClientPermissionLevels)[number]\n\nexport type PermissionLevel =\n | EntityPermissionsLevel\n | AccessRequirementPermissionsLevel\n | OAuthClientPermissionsLevel\n | PortalClientPermissionsLevel\n\nconst permissionLevelToAccessType: Record<PermissionLevel, ACCESS_TYPE[]> = {\n // Entity\n CAN_VIEW: [ACCESS_TYPE.READ],\n CAN_DOWNLOAD: [ACCESS_TYPE.READ, ACCESS_TYPE.DOWNLOAD],\n CAN_EDIT: [\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DOWNLOAD,\n ACCESS_TYPE.UPDATE,\n ],\n CAN_EDIT_DELETE: [\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DOWNLOAD,\n ACCESS_TYPE.UPDATE,\n ACCESS_TYPE.DELETE,\n ],\n CAN_ADMINISTER: [\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DOWNLOAD,\n ACCESS_TYPE.UPDATE,\n ACCESS_TYPE.DELETE,\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.CHANGE_PERMISSIONS,\n ACCESS_TYPE.CHANGE_SETTINGS,\n ACCESS_TYPE.MODERATE,\n ],\n\n // Access Requirements\n CAN_REVIEW_SUBMISSIONS: [ACCESS_TYPE.REVIEW_SUBMISSIONS],\n IS_EXEMPTION_ELIGIBLE: [ACCESS_TYPE.EXEMPTION_ELIGIBLE],\n CAN_REVIEW_SUBMISSIONS_AND_IS_EXEMPTION_ELIGIBLE: [\n ACCESS_TYPE.EXEMPTION_ELIGIBLE,\n ACCESS_TYPE.REVIEW_SUBMISSIONS,\n ],\n CAN_ADMINISTER_OAUTH_CLIENT: [\n ACCESS_TYPE.READ,\n ACCESS_TYPE.CHANGE_PERMISSIONS,\n ACCESS_TYPE.DELETE,\n ACCESS_TYPE.UPDATE,\n ],\n CAN_ADMINISTER_PORTAL: [\n ACCESS_TYPE.CHANGE_PERMISSIONS,\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.UPDATE,\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DELETE,\n ],\n}\n\nconst prepAccessTypeForComparison = (accessType: ACCESS_TYPE[]) => {\n return JSON.stringify(accessType.sort())\n}\n\nexport const getPermissionLevelFromAccessType = (accessType: ACCESS_TYPE[]) => {\n const lookupValue = prepAccessTypeForComparison(accessType)\n const permissionLevel = Object.keys(permissionLevelToAccessType).find(key => {\n return (\n lookupValue ===\n prepAccessTypeForComparison(\n permissionLevelToAccessType[key as PermissionLevel],\n )\n )\n })\n\n return permissionLevel as PermissionLevel\n}\n\nexport const getAccessTypeFromPermissionLevel = (\n permissionLevel: PermissionLevel,\n) => {\n return permissionLevelToAccessType[permissionLevel]\n}\n\nexport const permissionLevelToLabel: Record<PermissionLevel, string> = {\n // Entity\n CAN_VIEW: 'Can view',\n CAN_DOWNLOAD: 'Can download',\n CAN_EDIT: 'Can edit',\n CAN_EDIT_DELETE: 'Can edit & delete',\n CAN_ADMINISTER: 'Administrator',\n\n // Access requirement\n CAN_REVIEW_SUBMISSIONS: 'Can Review',\n IS_EXEMPTION_ELIGIBLE: 'Exempt Eligible',\n CAN_REVIEW_SUBMISSIONS_AND_IS_EXEMPTION_ELIGIBLE:\n 'Can Review & Exempt Eligible',\n\n // OAuth Client\n CAN_ADMINISTER_OAUTH_CLIENT: 'Administrator',\n\n // Portal\n CAN_ADMINISTER_PORTAL: 'Administrator',\n}\n"],"mappings":";;;;;;;IAoCM,IAAsE;CAE1E,UAAU,CAAC,EAAY,KAAK;CAC5B,cAAc,CAAC,EAAY,MAAM,EAAY,SAAS;CACtD,UAAU;EACR,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACD,iBAAiB;EACf,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACD,gBAAgB;EACd,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CAGD,wBAAwB,CAAC,EAAY,mBAAmB;CACxD,uBAAuB,CAAC,EAAY,mBAAmB;CACvD,kDAAkD,CAChD,EAAY,oBACZ,EAAY,mBACb;CACD,6BAA6B;EAC3B,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACD,uBAAuB;EACrB,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACF,EAEK,KAA+B,MAC5B,KAAK,UAAU,EAAW,MAAM,CAAC,EAG7B,KAAoC,MAA8B;CAC7E,IAAM,IAAc,EAA4B,EAAW;AAU3D,QATwB,OAAO,KAAK,EAA4B,CAAC,MAAK,MAElE,MACA,EACE,EAA4B,GAC7B,
|
|
1
|
+
{"version":3,"file":"PermissionLevelToAccessType.js","names":[],"sources":["../../src/utils/PermissionLevelToAccessType.ts"],"sourcesContent":["import { ACCESS_TYPE } from '@sage-bionetworks/synapse-types'\n\nconst entityPermissionLevels = [\n 'CAN_VIEW',\n 'CAN_DOWNLOAD',\n 'CAN_EDIT',\n 'CAN_EDIT_DELETE',\n 'CAN_ADMINISTER',\n] as const\nexport type EntityPermissionsLevel = (typeof entityPermissionLevels)[number]\n\nconst accessRequirementPermissionLevels = [\n 'CAN_REVIEW_SUBMISSIONS',\n 'IS_EXEMPTION_ELIGIBLE',\n 'CAN_REVIEW_SUBMISSIONS_AND_IS_EXEMPTION_ELIGIBLE',\n] as const\n\nexport type AccessRequirementPermissionsLevel =\n (typeof accessRequirementPermissionLevels)[number]\n\nconst oauthClientPermissionLevels = ['CAN_ADMINISTER_OAUTH_CLIENT'] as const\n\nexport type OAuthClientPermissionsLevel =\n (typeof oauthClientPermissionLevels)[number]\n\nconst portalClientPermissionLevels = ['CAN_ADMINISTER_PORTAL'] as const\n\nexport type PortalClientPermissionsLevel =\n (typeof portalClientPermissionLevels)[number]\n\nexport type PermissionLevel =\n | EntityPermissionsLevel\n | AccessRequirementPermissionsLevel\n | OAuthClientPermissionsLevel\n | PortalClientPermissionsLevel\n\nconst permissionLevelToAccessType: Record<PermissionLevel, ACCESS_TYPE[]> = {\n // Entity\n CAN_VIEW: [ACCESS_TYPE.READ],\n CAN_DOWNLOAD: [ACCESS_TYPE.READ, ACCESS_TYPE.DOWNLOAD],\n CAN_EDIT: [\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DOWNLOAD,\n ACCESS_TYPE.UPDATE,\n ],\n CAN_EDIT_DELETE: [\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DOWNLOAD,\n ACCESS_TYPE.UPDATE,\n ACCESS_TYPE.DELETE,\n ],\n CAN_ADMINISTER: [\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DOWNLOAD,\n ACCESS_TYPE.UPDATE,\n ACCESS_TYPE.DELETE,\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.CHANGE_PERMISSIONS,\n ACCESS_TYPE.CHANGE_SETTINGS,\n ACCESS_TYPE.MODERATE,\n ],\n\n // Access Requirements\n CAN_REVIEW_SUBMISSIONS: [ACCESS_TYPE.REVIEW_SUBMISSIONS],\n IS_EXEMPTION_ELIGIBLE: [ACCESS_TYPE.EXEMPTION_ELIGIBLE],\n CAN_REVIEW_SUBMISSIONS_AND_IS_EXEMPTION_ELIGIBLE: [\n ACCESS_TYPE.EXEMPTION_ELIGIBLE,\n ACCESS_TYPE.REVIEW_SUBMISSIONS,\n ],\n CAN_ADMINISTER_OAUTH_CLIENT: [\n ACCESS_TYPE.READ,\n ACCESS_TYPE.CHANGE_PERMISSIONS,\n ACCESS_TYPE.DELETE,\n ACCESS_TYPE.UPDATE,\n ],\n CAN_ADMINISTER_PORTAL: [\n ACCESS_TYPE.CHANGE_PERMISSIONS,\n ACCESS_TYPE.CREATE,\n ACCESS_TYPE.UPDATE,\n ACCESS_TYPE.READ,\n ACCESS_TYPE.DELETE,\n ],\n}\n\nconst prepAccessTypeForComparison = (accessType: ACCESS_TYPE[]) => {\n return JSON.stringify(accessType.sort())\n}\n\nexport const getPermissionLevelFromAccessType = (accessType: ACCESS_TYPE[]) => {\n const lookupValue = prepAccessTypeForComparison(accessType)\n const permissionLevel = Object.keys(permissionLevelToAccessType).find(key => {\n return (\n lookupValue ===\n prepAccessTypeForComparison(\n permissionLevelToAccessType[key as PermissionLevel],\n )\n )\n })\n\n return permissionLevel as PermissionLevel\n}\n\nexport const getAccessTypeFromPermissionLevel = (\n permissionLevel: PermissionLevel,\n) => {\n return permissionLevelToAccessType[permissionLevel]\n}\n\nexport const permissionLevelToLabel: Record<PermissionLevel, string> = {\n // Entity\n CAN_VIEW: 'Can view',\n CAN_DOWNLOAD: 'Can download',\n CAN_EDIT: 'Can edit',\n CAN_EDIT_DELETE: 'Can edit & delete',\n CAN_ADMINISTER: 'Administrator',\n\n // Access requirement\n CAN_REVIEW_SUBMISSIONS: 'Can Review',\n IS_EXEMPTION_ELIGIBLE: 'Exempt Eligible',\n CAN_REVIEW_SUBMISSIONS_AND_IS_EXEMPTION_ELIGIBLE:\n 'Can Review & Exempt Eligible',\n\n // OAuth Client\n CAN_ADMINISTER_OAUTH_CLIENT: 'Administrator',\n\n // Portal\n CAN_ADMINISTER_PORTAL: 'Administrator',\n}\n"],"mappings":";;;;;;;IAoCM,IAAsE;CAE1E,UAAU,CAAC,EAAY,KAAK;CAC5B,cAAc,CAAC,EAAY,MAAM,EAAY,SAAS;CACtD,UAAU;EACR,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACD,iBAAiB;EACf,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACD,gBAAgB;EACd,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CAGD,wBAAwB,CAAC,EAAY,mBAAmB;CACxD,uBAAuB,CAAC,EAAY,mBAAmB;CACvD,kDAAkD,CAChD,EAAY,oBACZ,EAAY,mBACb;CACD,6BAA6B;EAC3B,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACD,uBAAuB;EACrB,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACZ,EAAY;EACb;CACF,EAEK,KAA+B,MAC5B,KAAK,UAAU,EAAW,MAAM,CAAC,EAG7B,KAAoC,MAA8B;CAC7E,IAAM,IAAc,EAA4B,EAAW;AAU3D,QATwB,OAAO,KAAK,EAA4B,CAAC,MAAK,MAElE,MACA,EACE,EAA4B,GAC7B,CAIE;GAGI,KACX,MAEO,EAA4B,IAGxB,IAA0D;CAErE,UAAU;CACV,cAAc;CACd,UAAU;CACV,iBAAiB;CACjB,gBAAgB;CAGhB,wBAAwB;CACxB,uBAAuB;CACvB,kDACE;CAGF,6BAA6B;CAG7B,uBAAuB;CACxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EvaluationUtils.js","names":[],"sources":["../../../../src/utils/challenge/evaluation/EvaluationUtils.ts"],"sourcesContent":["import { EvaluationRound } from '@sage-bionetworks/synapse-client'\nimport dayjs from 'dayjs'\n\n/**\n * Given a list of evaluation rounds:\n * - If there is a current round, return it\n * - Else if there is one or more upcoming rounds, return the soonest\n * - Else return the most recent round in the past\n * @param rounds\n */\nexport function getCurrentOrNextOrLatestRound(\n rounds: EvaluationRound[],\n): EvaluationRound | undefined {\n // See if there is a current round. If so, return it!\n const currentRound = rounds.find(\n round =>\n dayjs().isAfter(dayjs(round.roundStart)) &&\n dayjs().isBefore(dayjs(round.roundEnd)),\n )\n if (currentRound) {\n return currentRound\n }\n\n // If there are rounds in the future, return the soonest one.\n const nextRound = rounds.reduce((prev: EvaluationRound | undefined, curr) => {\n const isFutureRound = dayjs().isBefore(curr.roundStart)\n if (!isFutureRound) return prev\n if (prev == undefined) {\n return curr\n } else if (dayjs(curr.roundStart).isBefore(prev.roundStart)) return curr\n return prev\n }, undefined)\n\n if (nextRound) {\n return nextRound\n }\n\n // There are no current or future rounds, so return the most recent round in the past\n const latestRound = rounds.reduce((prev, curr) => {\n const isLater = dayjs(curr.roundEnd).isAfter(dayjs(prev.roundEnd))\n if (isLater) {\n return curr\n }\n return prev\n }, rounds[0])\n return latestRound\n}\n"],"mappings":";;AAUA,SAAgB,EACd,GAC6B;AAiC7B,QA/BqB,EAAO,MAC1B,MACE,GAAO,CAAC,QAAQ,EAAM,EAAM,WAAW,CAAC,IACxC,GAAO,CAAC,SAAS,EAAM,EAAM,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"EvaluationUtils.js","names":[],"sources":["../../../../src/utils/challenge/evaluation/EvaluationUtils.ts"],"sourcesContent":["import { EvaluationRound } from '@sage-bionetworks/synapse-client'\nimport dayjs from 'dayjs'\n\n/**\n * Given a list of evaluation rounds:\n * - If there is a current round, return it\n * - Else if there is one or more upcoming rounds, return the soonest\n * - Else return the most recent round in the past\n * @param rounds\n */\nexport function getCurrentOrNextOrLatestRound(\n rounds: EvaluationRound[],\n): EvaluationRound | undefined {\n // See if there is a current round. If so, return it!\n const currentRound = rounds.find(\n round =>\n dayjs().isAfter(dayjs(round.roundStart)) &&\n dayjs().isBefore(dayjs(round.roundEnd)),\n )\n if (currentRound) {\n return currentRound\n }\n\n // If there are rounds in the future, return the soonest one.\n const nextRound = rounds.reduce((prev: EvaluationRound | undefined, curr) => {\n const isFutureRound = dayjs().isBefore(curr.roundStart)\n if (!isFutureRound) return prev\n if (prev == undefined) {\n return curr\n } else if (dayjs(curr.roundStart).isBefore(prev.roundStart)) return curr\n return prev\n }, undefined)\n\n if (nextRound) {\n return nextRound\n }\n\n // There are no current or future rounds, so return the most recent round in the past\n const latestRound = rounds.reduce((prev, curr) => {\n const isLater = dayjs(curr.roundEnd).isAfter(dayjs(prev.roundEnd))\n if (isLater) {\n return curr\n }\n return prev\n }, rounds[0])\n return latestRound\n}\n"],"mappings":";;AAUA,SAAgB,EACd,GAC6B;AAiC7B,QA/BqB,EAAO,MAC1B,MACE,GAAO,CAAC,QAAQ,EAAM,EAAM,WAAW,CAAC,IACxC,GAAO,CAAC,SAAS,EAAM,EAAM,SAAS,CAAC,CAEvC,IAKc,EAAO,QAAQ,GAAmC,MAC5C,GAAO,CAAC,SAAS,EAAK,WACvC,KACD,KAAQ,QAED,EAAM,EAAK,WAAW,CAAC,SAAS,EAAK,WAAW,IADlD,IAEF,GACN,KAAA,EAEC,IAKgB,EAAO,QAAQ,GAAM,MACvB,EAAM,EAAK,SAAS,CAAC,QAAQ,EAAM,EAAK,SAAS,CAC7D,GACK,IAEF,GACN,EAAO,GACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SynapseContext.js","names":[],"sources":["../../../src/utils/context/SynapseContext.tsx"],"sourcesContent":["import { SynapseErrorBoundary } from '@/components/error/ErrorBanner'\nimport { KeyFactory } from '@/synapse-queries/KeyFactory'\nimport { SynapseClient } from '@sage-bionetworks/synapse-client/SynapseClient'\nimport { createContext, PropsWithChildren, useContext, useMemo } from 'react'\nimport { BackendDestinationEnum, getEndpoint } from '../functions/getEndpoint'\n\nexport type SynapseContextType = {\n /** The user's access token. If undefined, the user is not logged in */\n accessToken: string | undefined\n /** Whether the user is authenticated */\n isAuthenticated: boolean\n /** If the user has enabled experimental mode */\n isInExperimentalMode: boolean\n /** If the user prefers time to be displayed in UTC format */\n utcTime: boolean\n /** Whether to wrap all children of this context in an error boundary. Useful if this context wraps just one component. */\n withErrorBoundary: boolean\n /** The URL of the download cart page in the current app. Used to properly link components */\n downloadCartPageUrl: string\n /** The URL of the people search page in the current app */\n peopleSearchPageUrl?: string\n /** The key factory to use for react-query. Generated automatically. */\n keyFactory: KeyFactory\n /** The appId identifying the product. Used to brand the Synapse account management site, aka OneSage. */\n appId?: string\n /* API client objects for Synapse. Generated automatically. */\n synapseClient: SynapseClient\n}\n\nconst defaultContext = {\n accessToken: undefined,\n isAuthenticated: false,\n isInExperimentalMode: false,\n utcTime: false,\n withErrorBoundary: false,\n keyFactory: new KeyFactory(undefined),\n downloadCartPageUrl: '/DownloadCart',\n peopleSearchPageUrl: '/PeopleSearch:',\n appId: undefined,\n synapseClient: new SynapseClient(),\n} satisfies SynapseContextType\n\n/**\n * This must be exported to use the context in class components.\n */\nexport const SynapseContext = createContext<SynapseContextType>(defaultContext)\n\nexport type SynapseContextProviderProps = PropsWithChildren<{\n synapseContext?: Partial<SynapseContextType>\n}>\n\n/**\n * Provides context necessary for most components in SRC.\n *\n * The SynapseContextProvider must be wrapped in a react-query QueryClientProvider.\n * @returns\n */\nexport function SynapseContextProvider(props: SynapseContextProviderProps) {\n const { children, synapseContext: providedContext } = props\n const queryKeyFactory = useMemo(\n () => new KeyFactory(providedContext?.accessToken),\n [providedContext?.accessToken],\n )\n\n const basePath = getEndpoint(BackendDestinationEnum.REPO_ENDPOINT)\n\n const synapseApiClient = useMemo(() => {\n if (providedContext?.synapseClient) {\n return providedContext?.synapseClient\n }\n const configurationParameters = {\n accessToken: providedContext?.accessToken,\n basePath: basePath,\n }\n return new SynapseClient(configurationParameters)\n }, [providedContext?.synapseClient, providedContext?.accessToken, basePath])\n\n const synapseContext: SynapseContextType = useMemo(\n () => ({\n accessToken: providedContext?.accessToken,\n isAuthenticated: providedContext?.isAuthenticated ?? false,\n isInExperimentalMode: providedContext?.isInExperimentalMode ?? false,\n utcTime: providedContext?.utcTime ?? false,\n withErrorBoundary: providedContext?.withErrorBoundary ?? false,\n downloadCartPageUrl:\n providedContext?.downloadCartPageUrl ?? '/DownloadCart',\n peopleSearchPageUrl:\n providedContext?.peopleSearchPageUrl ?? '/PeopleSearch:',\n keyFactory: providedContext?.keyFactory ?? queryKeyFactory,\n appId: providedContext?.appId,\n synapseClient: synapseApiClient,\n }),\n [\n providedContext?.accessToken,\n providedContext?.isAuthenticated,\n providedContext?.downloadCartPageUrl,\n providedContext?.peopleSearchPageUrl,\n providedContext?.isInExperimentalMode,\n providedContext?.keyFactory,\n providedContext?.utcTime,\n providedContext?.withErrorBoundary,\n providedContext?.appId,\n queryKeyFactory,\n synapseApiClient,\n ],\n )\n\n return (\n <SynapseContext.Provider value={synapseContext}>\n {synapseContext?.withErrorBoundary ? (\n <SynapseErrorBoundary>{children}</SynapseErrorBoundary>\n ) : (\n children\n )}\n </SynapseContext.Provider>\n )\n}\n\nexport const SynapseContextConsumer = SynapseContext.Consumer\n\nexport function useSynapseContext(): SynapseContextType {\n const context = useContext(SynapseContext)\n if (context === undefined) {\n throw new Error(\n 'useSynapseContext must be used within a SynapseContextProvider',\n )\n }\n return context\n}\n"],"mappings":";;;;;;AA6CA,IAAa,IAAiB,
|
|
1
|
+
{"version":3,"file":"SynapseContext.js","names":[],"sources":["../../../src/utils/context/SynapseContext.tsx"],"sourcesContent":["import { SynapseErrorBoundary } from '@/components/error/ErrorBanner'\nimport { KeyFactory } from '@/synapse-queries/KeyFactory'\nimport { SynapseClient } from '@sage-bionetworks/synapse-client/SynapseClient'\nimport { createContext, PropsWithChildren, useContext, useMemo } from 'react'\nimport { BackendDestinationEnum, getEndpoint } from '../functions/getEndpoint'\n\nexport type SynapseContextType = {\n /** The user's access token. If undefined, the user is not logged in */\n accessToken: string | undefined\n /** Whether the user is authenticated */\n isAuthenticated: boolean\n /** If the user has enabled experimental mode */\n isInExperimentalMode: boolean\n /** If the user prefers time to be displayed in UTC format */\n utcTime: boolean\n /** Whether to wrap all children of this context in an error boundary. Useful if this context wraps just one component. */\n withErrorBoundary: boolean\n /** The URL of the download cart page in the current app. Used to properly link components */\n downloadCartPageUrl: string\n /** The URL of the people search page in the current app */\n peopleSearchPageUrl?: string\n /** The key factory to use for react-query. Generated automatically. */\n keyFactory: KeyFactory\n /** The appId identifying the product. Used to brand the Synapse account management site, aka OneSage. */\n appId?: string\n /* API client objects for Synapse. Generated automatically. */\n synapseClient: SynapseClient\n}\n\nconst defaultContext = {\n accessToken: undefined,\n isAuthenticated: false,\n isInExperimentalMode: false,\n utcTime: false,\n withErrorBoundary: false,\n keyFactory: new KeyFactory(undefined),\n downloadCartPageUrl: '/DownloadCart',\n peopleSearchPageUrl: '/PeopleSearch:',\n appId: undefined,\n synapseClient: new SynapseClient(),\n} satisfies SynapseContextType\n\n/**\n * This must be exported to use the context in class components.\n */\nexport const SynapseContext = createContext<SynapseContextType>(defaultContext)\n\nexport type SynapseContextProviderProps = PropsWithChildren<{\n synapseContext?: Partial<SynapseContextType>\n}>\n\n/**\n * Provides context necessary for most components in SRC.\n *\n * The SynapseContextProvider must be wrapped in a react-query QueryClientProvider.\n * @returns\n */\nexport function SynapseContextProvider(props: SynapseContextProviderProps) {\n const { children, synapseContext: providedContext } = props\n const queryKeyFactory = useMemo(\n () => new KeyFactory(providedContext?.accessToken),\n [providedContext?.accessToken],\n )\n\n const basePath = getEndpoint(BackendDestinationEnum.REPO_ENDPOINT)\n\n const synapseApiClient = useMemo(() => {\n if (providedContext?.synapseClient) {\n return providedContext?.synapseClient\n }\n const configurationParameters = {\n accessToken: providedContext?.accessToken,\n basePath: basePath,\n }\n return new SynapseClient(configurationParameters)\n }, [providedContext?.synapseClient, providedContext?.accessToken, basePath])\n\n const synapseContext: SynapseContextType = useMemo(\n () => ({\n accessToken: providedContext?.accessToken,\n isAuthenticated: providedContext?.isAuthenticated ?? false,\n isInExperimentalMode: providedContext?.isInExperimentalMode ?? false,\n utcTime: providedContext?.utcTime ?? false,\n withErrorBoundary: providedContext?.withErrorBoundary ?? false,\n downloadCartPageUrl:\n providedContext?.downloadCartPageUrl ?? '/DownloadCart',\n peopleSearchPageUrl:\n providedContext?.peopleSearchPageUrl ?? '/PeopleSearch:',\n keyFactory: providedContext?.keyFactory ?? queryKeyFactory,\n appId: providedContext?.appId,\n synapseClient: synapseApiClient,\n }),\n [\n providedContext?.accessToken,\n providedContext?.isAuthenticated,\n providedContext?.downloadCartPageUrl,\n providedContext?.peopleSearchPageUrl,\n providedContext?.isInExperimentalMode,\n providedContext?.keyFactory,\n providedContext?.utcTime,\n providedContext?.withErrorBoundary,\n providedContext?.appId,\n queryKeyFactory,\n synapseApiClient,\n ],\n )\n\n return (\n <SynapseContext.Provider value={synapseContext}>\n {synapseContext?.withErrorBoundary ? (\n <SynapseErrorBoundary>{children}</SynapseErrorBoundary>\n ) : (\n children\n )}\n </SynapseContext.Provider>\n )\n}\n\nexport const SynapseContextConsumer = SynapseContext.Consumer\n\nexport function useSynapseContext(): SynapseContextType {\n const context = useContext(SynapseContext)\n if (context === undefined) {\n throw new Error(\n 'useSynapseContext must be used within a SynapseContextProvider',\n )\n }\n return context\n}\n"],"mappings":";;;;;;AA6CA,IAAa,IAAiB,EAAkC;CAf9D,aAAa,KAAA;CACb,iBAAiB;CACjB,sBAAsB;CACtB,SAAS;CACT,mBAAmB;CACnB,YAAY,IAAI,EAAW,KAAA,EAAU;CACrC,qBAAqB;CACrB,qBAAqB;CACrB,OAAO,KAAA;CACP,eAAe,IAAI,GAAe;CAM4B,CAAe;AAY/E,SAAgB,EAAuB,GAAoC;CACzE,IAAM,EAAE,aAAU,gBAAgB,MAAoB,GAChD,IAAkB,QAChB,IAAI,EAAW,GAAiB,YAAY,EAClD,CAAC,GAAiB,YAAY,CAC/B,EAEK,IAAW,EAAY,EAAuB,cAAc,EAE5D,IAAmB,QACnB,GAAiB,gBACZ,GAAiB,gBAMnB,IAAI,EAAc;EAHvB,aAAa,GAAiB;EACpB;EAEa,CAAwB,EAChD;EAAC,GAAiB;EAAe,GAAiB;EAAa;EAAS,CAAC,EAEtE,IAAqC,SAClC;EACL,aAAa,GAAiB;EAC9B,iBAAiB,GAAiB,mBAAmB;EACrD,sBAAsB,GAAiB,wBAAwB;EAC/D,SAAS,GAAiB,WAAW;EACrC,mBAAmB,GAAiB,qBAAqB;EACzD,qBACE,GAAiB,uBAAuB;EAC1C,qBACE,GAAiB,uBAAuB;EAC1C,YAAY,GAAiB,cAAc;EAC3C,OAAO,GAAiB;EACxB,eAAe;EAChB,GACD;EACE,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAe,UAAhB;EAAyB,OAAO;YAC7B,GAAgB,oBACf,kBAAC,GAAD,EAAuB,aAAgC,CAAA,GAEvD;EAEsB,CAAA;;AAI9B,IAAa,IAAyB,EAAe;AAErD,SAAgB,IAAwC;CACtD,IAAM,IAAU,EAAW,EAAe;AAC1C,KAAI,MAAY,KAAA,EACd,OAAU,MACR,iEACD;AAEH,QAAO"}
|
|
@@ -26,4 +26,8 @@ export declare function isEntityPublic(resourceAccess: ResourceAccess[], realmPr
|
|
|
26
26
|
publicGroup?: string;
|
|
27
27
|
anonymousUser?: string;
|
|
28
28
|
}): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Consolidate access types for duplicate entries for the same principal into a single entry with the union of all access types.
|
|
31
|
+
*/
|
|
32
|
+
export declare function consolidateResourceAccessList(list: ResourceAccess[]): ResourceAccess[];
|
|
29
33
|
//# sourceMappingURL=AccessControlListUtils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccessControlListUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/functions/AccessControlListUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,cAAc,EAAE,MAAM,iCAAiC,CAAA;AAC7E,OAAO,EACL,iBAAiB,EACjB,cAAc,IAAI,2BAA2B,EAC9C,MAAM,kCAAkC,CAAA;AAazC;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,CAAC,EAAE,cAAc,EAAE,EACnB,CAAC,EAAE,cAAc,EAAE,WAMpB;AAED,qHAAqH;AACrH,wBAAgB,6BAA6B,CAC3C,iBAAiB,CAAC,EAAE,GAAG,CAAC,2BAA2B,CAAC,GACnD,cAAc,EAAE,CAOlB;AAED,6HAA6H;AAC7H,wBAAgB,kCAAkC,CAChD,GAAG,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,EACzC,kBAAkB,EAAE,cAAc,EAAE,GACnC,iBAAiB,CASnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,cAAc,EAAE,cAAc,EAAE,EAChC,eAAe,EAAE;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,GACA,OAAO,CAUT"}
|
|
1
|
+
{"version":3,"file":"AccessControlListUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/functions/AccessControlListUtils.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,cAAc,EAAE,MAAM,iCAAiC,CAAA;AAC7E,OAAO,EACL,iBAAiB,EACjB,cAAc,IAAI,2BAA2B,EAC9C,MAAM,kCAAkC,CAAA;AAazC;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,CAAC,EAAE,cAAc,EAAE,EACnB,CAAC,EAAE,cAAc,EAAE,WAMpB;AAED,qHAAqH;AACrH,wBAAgB,6BAA6B,CAC3C,iBAAiB,CAAC,EAAE,GAAG,CAAC,2BAA2B,CAAC,GACnD,cAAc,EAAE,CAOlB;AAED,6HAA6H;AAC7H,wBAAgB,kCAAkC,CAChD,GAAG,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,EACzC,kBAAkB,EAAE,cAAc,EAAE,GACnC,iBAAiB,CASnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,cAAc,EAAE,cAAc,EAAE,EAChC,eAAe,EAAE;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,GACA,OAAO,CAUT;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,cAAc,EAAE,GACrB,cAAc,EAAE,CAgBlB"}
|
|
@@ -28,7 +28,18 @@ function o(e, t) {
|
|
|
28
28
|
].filter((e) => e !== void 0);
|
|
29
29
|
return e.some((e) => n.includes(String(e.principalId)));
|
|
30
30
|
}
|
|
31
|
+
function s(e) {
|
|
32
|
+
let t = /* @__PURE__ */ new Map();
|
|
33
|
+
for (let n of e) {
|
|
34
|
+
let e = t.get(n.principalId);
|
|
35
|
+
e ? n.accessType.forEach((t) => e.add(t)) : t.set(n.principalId, new Set(n.accessType));
|
|
36
|
+
}
|
|
37
|
+
return Array.from(t, ([e, t]) => ({
|
|
38
|
+
principalId: e,
|
|
39
|
+
accessType: Array.from(t)
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
31
42
|
//#endregion
|
|
32
|
-
export { i as convertResourceAccessSetToSRC, o as isEntityPublic, r as resourceAccessListIsEqual, a as updateACLWithSRCResourceAccessList };
|
|
43
|
+
export { s as consolidateResourceAccessList, i as convertResourceAccessSetToSRC, o as isEntityPublic, r as resourceAccessListIsEqual, a as updateACLWithSRCResourceAccessList };
|
|
33
44
|
|
|
34
45
|
//# sourceMappingURL=AccessControlListUtils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccessControlListUtils.js","names":[],"sources":["../../../src/utils/functions/AccessControlListUtils.ts"],"sourcesContent":["import { cloneDeep, isEqual } from 'lodash-es'\nimport { ACCESS_TYPE, ResourceAccess } from '@sage-bionetworks/synapse-types'\nimport {\n AccessControlList,\n ResourceAccess as SynapseClientResourceAccess,\n} from '@sage-bionetworks/synapse-client'\n\nfunction sortResourceAccessList(\n resourceAccessList: ResourceAccess[],\n): ResourceAccess[] {\n const clone = cloneDeep(resourceAccessList)\n // Sort the resource access list by principal ID\n clone.sort((a, b) => b.principalId - a.principalId)\n // In each resource access, sort the access type set\n clone.forEach(ra => ra.accessType.sort())\n return clone\n}\n\n/**\n * Compares two objects for equality, ignoring the order of elements in arrays.\n * @param a\n * @param b\n */\nexport function resourceAccessListIsEqual(\n a: ResourceAccess[],\n b: ResourceAccess[],\n) {\n const aSorted = sortResourceAccessList(a)\n const bSorted = sortResourceAccessList(b)\n\n return isEqual(aSorted, bSorted)\n}\n\n/** Conversion utility to go from synapse-client ResourceAccess Set to the SRC SynapseClient ResourceAccess[] type */\nexport function convertResourceAccessSetToSRC(\n resourceAccessSet?: Set<SynapseClientResourceAccess>,\n): ResourceAccess[] {\n return Array.from(resourceAccessSet ?? []).map(item => ({\n principalId: item.principalId ?? -1,\n accessType: Array.isArray(item.accessType)\n ? (item.accessType as ACCESS_TYPE[])\n : (Array.from(item.accessType ?? []) as ACCESS_TYPE[]),\n }))\n}\n\n/** Conversion utility to go from synapse-react-client ResourceAccess[] to the synapse-client ResourceAccess Set in an ACL */\nexport function updateACLWithSRCResourceAccessList(\n acl: AccessControlList | null | undefined,\n resourceAccessList: ResourceAccess[],\n): AccessControlList {\n const newAcl = { ...acl }\n newAcl.resourceAccess = new Set(\n resourceAccessList.map(ra => ({\n principalId: ra.principalId,\n accessType: new Set(ra.accessType),\n })),\n )\n return newAcl\n}\n\n/**\n * Determines if an entity is publicly accessible by checking if any public principals\n * (authenticated users, public group, or anonymous user) have access in the resource access list.\n *\n * @param resourceAccess - The list of resource access entries to check\n * @param realmPrincipals - An object containing the principal IDs for public groups\n * @param realmPrincipals.authenticatedUsers - The principal ID for authenticated users\n * @param realmPrincipals.publicGroup - The principal ID for the public group\n * @param realmPrincipals.anonymousUser - The principal ID for anonymous users\n * @returns true if any public principal has access, false otherwise\n */\nexport function isEntityPublic(\n resourceAccess: ResourceAccess[],\n realmPrincipals: {\n authenticatedUsers?: string\n publicGroup?: string\n anonymousUser?: string\n },\n): boolean {\n const publicPrincipalIds = [\n realmPrincipals.authenticatedUsers,\n realmPrincipals.publicGroup,\n realmPrincipals.anonymousUser,\n ].filter((id): id is string => id !== undefined)\n\n return resourceAccess.some(ra =>\n publicPrincipalIds.includes(String(ra.principalId)),\n )\n}\n"],"mappings":";;AAOA,SAAS,EACP,GACkB;CAClB,IAAM,IAAQ,EAAU,EAAmB;AAK3C,QAHA,EAAM,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,YAAY,EAEnD,EAAM,SAAQ,MAAM,EAAG,WAAW,MAAM,CAAC,EAClC;;AAQT,SAAgB,EACd,GACA,GACA;AAIA,QAAO,EAHS,EAAuB,
|
|
1
|
+
{"version":3,"file":"AccessControlListUtils.js","names":[],"sources":["../../../src/utils/functions/AccessControlListUtils.ts"],"sourcesContent":["import { cloneDeep, isEqual } from 'lodash-es'\nimport { ACCESS_TYPE, ResourceAccess } from '@sage-bionetworks/synapse-types'\nimport {\n AccessControlList,\n ResourceAccess as SynapseClientResourceAccess,\n} from '@sage-bionetworks/synapse-client'\n\nfunction sortResourceAccessList(\n resourceAccessList: ResourceAccess[],\n): ResourceAccess[] {\n const clone = cloneDeep(resourceAccessList)\n // Sort the resource access list by principal ID\n clone.sort((a, b) => b.principalId - a.principalId)\n // In each resource access, sort the access type set\n clone.forEach(ra => ra.accessType.sort())\n return clone\n}\n\n/**\n * Compares two objects for equality, ignoring the order of elements in arrays.\n * @param a\n * @param b\n */\nexport function resourceAccessListIsEqual(\n a: ResourceAccess[],\n b: ResourceAccess[],\n) {\n const aSorted = sortResourceAccessList(a)\n const bSorted = sortResourceAccessList(b)\n\n return isEqual(aSorted, bSorted)\n}\n\n/** Conversion utility to go from synapse-client ResourceAccess Set to the SRC SynapseClient ResourceAccess[] type */\nexport function convertResourceAccessSetToSRC(\n resourceAccessSet?: Set<SynapseClientResourceAccess>,\n): ResourceAccess[] {\n return Array.from(resourceAccessSet ?? []).map(item => ({\n principalId: item.principalId ?? -1,\n accessType: Array.isArray(item.accessType)\n ? (item.accessType as ACCESS_TYPE[])\n : (Array.from(item.accessType ?? []) as ACCESS_TYPE[]),\n }))\n}\n\n/** Conversion utility to go from synapse-react-client ResourceAccess[] to the synapse-client ResourceAccess Set in an ACL */\nexport function updateACLWithSRCResourceAccessList(\n acl: AccessControlList | null | undefined,\n resourceAccessList: ResourceAccess[],\n): AccessControlList {\n const newAcl = { ...acl }\n newAcl.resourceAccess = new Set(\n resourceAccessList.map(ra => ({\n principalId: ra.principalId,\n accessType: new Set(ra.accessType),\n })),\n )\n return newAcl\n}\n\n/**\n * Determines if an entity is publicly accessible by checking if any public principals\n * (authenticated users, public group, or anonymous user) have access in the resource access list.\n *\n * @param resourceAccess - The list of resource access entries to check\n * @param realmPrincipals - An object containing the principal IDs for public groups\n * @param realmPrincipals.authenticatedUsers - The principal ID for authenticated users\n * @param realmPrincipals.publicGroup - The principal ID for the public group\n * @param realmPrincipals.anonymousUser - The principal ID for anonymous users\n * @returns true if any public principal has access, false otherwise\n */\nexport function isEntityPublic(\n resourceAccess: ResourceAccess[],\n realmPrincipals: {\n authenticatedUsers?: string\n publicGroup?: string\n anonymousUser?: string\n },\n): boolean {\n const publicPrincipalIds = [\n realmPrincipals.authenticatedUsers,\n realmPrincipals.publicGroup,\n realmPrincipals.anonymousUser,\n ].filter((id): id is string => id !== undefined)\n\n return resourceAccess.some(ra =>\n publicPrincipalIds.includes(String(ra.principalId)),\n )\n}\n\n/**\n * Consolidate access types for duplicate entries for the same principal into a single entry with the union of all access types.\n */\nexport function consolidateResourceAccessList(\n list: ResourceAccess[],\n): ResourceAccess[] {\n const permissionsMap = new Map<number, Set<ACCESS_TYPE>>()\n for (const item of list) {\n const permissions = permissionsMap.get(item.principalId)\n\n if (permissions) {\n item.accessType.forEach(type => permissions.add(type))\n } else {\n permissionsMap.set(item.principalId, new Set(item.accessType))\n }\n }\n\n return Array.from(permissionsMap, ([principalId, accessTypeSet]) => ({\n principalId,\n accessType: Array.from(accessTypeSet),\n }))\n}\n"],"mappings":";;AAOA,SAAS,EACP,GACkB;CAClB,IAAM,IAAQ,EAAU,EAAmB;AAK3C,QAHA,EAAM,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,YAAY,EAEnD,EAAM,SAAQ,MAAM,EAAG,WAAW,MAAM,CAAC,EAClC;;AAQT,SAAgB,EACd,GACA,GACA;AAIA,QAAO,EAHS,EAAuB,EAGxB,EAFC,EAAuB,EAEf,CAAQ;;AAIlC,SAAgB,EACd,GACkB;AAClB,QAAO,MAAM,KAAK,KAAqB,EAAE,CAAC,CAAC,KAAI,OAAS;EACtD,aAAa,EAAK,eAAe;EACjC,YAAY,MAAM,QAAQ,EAAK,WAAW,GACrC,EAAK,aACL,MAAM,KAAK,EAAK,cAAc,EAAE,CAAC;EACvC,EAAE;;AAIL,SAAgB,EACd,GACA,GACmB;CACnB,IAAM,IAAS,EAAE,GAAG,GAAK;AAOzB,QANA,EAAO,iBAAiB,IAAI,IAC1B,EAAmB,KAAI,OAAO;EAC5B,aAAa,EAAG;EAChB,YAAY,IAAI,IAAI,EAAG,WAAW;EACnC,EAAE,CACJ,EACM;;AAcT,SAAgB,EACd,GACA,GAKS;CACT,IAAM,IAAqB;EACzB,EAAgB;EAChB,EAAgB;EAChB,EAAgB;EACjB,CAAC,QAAQ,MAAqB,MAAO,KAAA,EAAU;AAEhD,QAAO,EAAe,MAAK,MACzB,EAAmB,SAAS,OAAO,EAAG,YAAY,CAAC,CACpD;;AAMH,SAAgB,EACd,GACkB;CAClB,IAAM,oBAAiB,IAAI,KAA+B;AAC1D,MAAK,IAAM,KAAQ,GAAM;EACvB,IAAM,IAAc,EAAe,IAAI,EAAK,YAAY;AAExD,EAAI,IACF,EAAK,WAAW,SAAQ,MAAQ,EAAY,IAAI,EAAK,CAAC,GAEtD,EAAe,IAAI,EAAK,aAAa,IAAI,IAAI,EAAK,WAAW,CAAC;;AAIlE,QAAO,MAAM,KAAK,IAAiB,CAAC,GAAa,QAAoB;EACnE;EACA,YAAY,MAAM,KAAK,EAAc;EACtC,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GridApiUtils.js","names":[],"sources":["../../../src/utils/functions/GridApiUtils.ts"],"sourcesContent":["import { SynapseClient } from '@sage-bionetworks/synapse-client/SynapseClient'\nimport {\n waitForAsyncResult,\n CreateGridRequest,\n CreateGridResponse,\n} from '@sage-bionetworks/synapse-client'\n\nexport default async function startGridSession(\n synapseClient: SynapseClient,\n request: CreateGridRequest,\n) {\n const asyncJobId =\n await synapseClient.gridServicesClient.postRepoV1GridSessionAsyncStart({\n createGridRequest: request,\n })\n if (!asyncJobId.token) {\n throw new Error('No async job token returned from startGridSession')\n }\n // getRepoV1GridSessionAsyncStart does not return 202 processing jobs as expected\n // so use the getAsynchronousJobJobId endpoint instead\n const asyncJobResponse = await waitForAsyncResult(() =>\n synapseClient.asynchronousJobServicesClient.getRepoV1AsynchronousJobJobId({\n jobId: asyncJobId.token!,\n }),\n )\n return asyncJobResponse.responseBody as CreateGridResponse\n}\n"],"mappings":";;AAOA,eAA8B,EAC5B,GACA,GACA;CACA,IAAM,IACJ,MAAM,EAAc,mBAAmB,gCAAgC,EACrE,mBAAmB,GACpB,CAAC;AACJ,KAAI,CAAC,EAAW,MACd,OAAU,MAAM,oDAAoD;AAStE,
|
|
1
|
+
{"version":3,"file":"GridApiUtils.js","names":[],"sources":["../../../src/utils/functions/GridApiUtils.ts"],"sourcesContent":["import { SynapseClient } from '@sage-bionetworks/synapse-client/SynapseClient'\nimport {\n waitForAsyncResult,\n CreateGridRequest,\n CreateGridResponse,\n} from '@sage-bionetworks/synapse-client'\n\nexport default async function startGridSession(\n synapseClient: SynapseClient,\n request: CreateGridRequest,\n) {\n const asyncJobId =\n await synapseClient.gridServicesClient.postRepoV1GridSessionAsyncStart({\n createGridRequest: request,\n })\n if (!asyncJobId.token) {\n throw new Error('No async job token returned from startGridSession')\n }\n // getRepoV1GridSessionAsyncStart does not return 202 processing jobs as expected\n // so use the getAsynchronousJobJobId endpoint instead\n const asyncJobResponse = await waitForAsyncResult(() =>\n synapseClient.asynchronousJobServicesClient.getRepoV1AsynchronousJobJobId({\n jobId: asyncJobId.token!,\n }),\n )\n return asyncJobResponse.responseBody as CreateGridResponse\n}\n"],"mappings":";;AAOA,eAA8B,EAC5B,GACA,GACA;CACA,IAAM,IACJ,MAAM,EAAc,mBAAmB,gCAAgC,EACrE,mBAAmB,GACpB,CAAC;AACJ,KAAI,CAAC,EAAW,MACd,OAAU,MAAM,oDAAoD;AAStE,SAAO,MALwB,QAC7B,EAAc,8BAA8B,8BAA8B,EACxE,OAAO,EAAW,OACnB,CAAC,CACH,EACuB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryFilterUtils.js","names":[],"sources":["../../../src/utils/functions/QueryFilterUtils.ts"],"sourcesContent":["import {\n ColumnSingleValueFilterOperator,\n ColumnSingleValueQueryFilter,\n QueryFilter,\n Row,\n SelectColumn,\n} from '@sage-bionetworks/synapse-types'\n\n/**\n * Given a list of selected rows, return a QueryFilter that will only return those rows\n * @param primaryKeyColumnNames\n * @param selectedRows\n * @param selectColumns\n */\nexport function getPrimaryKeyINFilter(\n primaryKeyColumnNames: string[],\n selectedRows: Row[],\n selectColumns: SelectColumn[],\n): QueryFilter {\n if (!primaryKeyColumnNames || primaryKeyColumnNames.length !== 1) {\n // If the key is undefined, then the user should have never been able to apply a filter\n // TODO: Handling a composite key would be tricky since the QueryFilter API currently only allows you to specify one column\n throw new Error('rowSelectionPrimaryKey must be defined and have length 1')\n }\n // Apply a filter that should only return the selected rows\n const primaryKeyColumnIndex = selectColumns.findIndex(\n sc => sc.name === primaryKeyColumnNames[0],\n )\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: primaryKeyColumnNames[0],\n operator: ColumnSingleValueFilterOperator.IN,\n values: selectedRows.map(row => row.values[primaryKeyColumnIndex]!),\n }\n return filter\n}\n"],"mappings":";;AAcA,SAAgB,EACd,GACA,GACA,GACa;AACb,KAAI,CAAC,KAAyB,EAAsB,WAAW,EAG7D,OAAU,MAAM,2DAA2D;CAG7E,IAAM,IAAwB,EAAc,WAC1C,MAAM,EAAG,SAAS,EAAsB,GACzC;AAQD,
|
|
1
|
+
{"version":3,"file":"QueryFilterUtils.js","names":[],"sources":["../../../src/utils/functions/QueryFilterUtils.ts"],"sourcesContent":["import {\n ColumnSingleValueFilterOperator,\n ColumnSingleValueQueryFilter,\n QueryFilter,\n Row,\n SelectColumn,\n} from '@sage-bionetworks/synapse-types'\n\n/**\n * Given a list of selected rows, return a QueryFilter that will only return those rows\n * @param primaryKeyColumnNames\n * @param selectedRows\n * @param selectColumns\n */\nexport function getPrimaryKeyINFilter(\n primaryKeyColumnNames: string[],\n selectedRows: Row[],\n selectColumns: SelectColumn[],\n): QueryFilter {\n if (!primaryKeyColumnNames || primaryKeyColumnNames.length !== 1) {\n // If the key is undefined, then the user should have never been able to apply a filter\n // TODO: Handling a composite key would be tricky since the QueryFilter API currently only allows you to specify one column\n throw new Error('rowSelectionPrimaryKey must be defined and have length 1')\n }\n // Apply a filter that should only return the selected rows\n const primaryKeyColumnIndex = selectColumns.findIndex(\n sc => sc.name === primaryKeyColumnNames[0],\n )\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: primaryKeyColumnNames[0],\n operator: ColumnSingleValueFilterOperator.IN,\n values: selectedRows.map(row => row.values[primaryKeyColumnIndex]!),\n }\n return filter\n}\n"],"mappings":";;AAcA,SAAgB,EACd,GACA,GACA,GACa;AACb,KAAI,CAAC,KAAyB,EAAsB,WAAW,EAG7D,OAAU,MAAM,2DAA2D;CAG7E,IAAM,IAAwB,EAAc,WAC1C,MAAM,EAAG,SAAS,EAAsB,GACzC;AAQD,QAAO;EANL,cACE;EACF,YAAY,EAAsB;EAClC,UAAU,EAAgC;EAC1C,QAAQ,EAAa,KAAI,MAAO,EAAI,OAAO,GAAwB;EAE9D"}
|
|
@@ -3,4 +3,8 @@ import { Realm } from '@sage-bionetworks/synapse-client';
|
|
|
3
3
|
* Check if a realm has the ARCUS_BIOSCIENCES identity provider
|
|
4
4
|
*/
|
|
5
5
|
export declare function hasArcusProvider(realm?: Realm): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Check if a realm has the SAGE_BIONETWORKS identity provider
|
|
8
|
+
*/
|
|
9
|
+
export declare function hasSageBionetworksProvider(realm?: Realm): boolean;
|
|
6
10
|
//# sourceMappingURL=RealmUtils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RealmUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/functions/RealmUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,EACN,MAAM,kCAAkC,CAAA;AAEzC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"RealmUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/functions/RealmUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,EACN,MAAM,kCAAkC,CAAA;AAEzC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAEvD;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAEjE"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { OAuthIdentityProviderProviderEnum as e, instanceOfOAuthIdentityProvider as t } from "@sage-bionetworks/synapse-client";
|
|
2
2
|
//#region src/utils/functions/RealmUtils.ts
|
|
3
|
-
function n(
|
|
4
|
-
return
|
|
3
|
+
function n(t) {
|
|
4
|
+
return i(t, e.ARCUS_BIOSCIENCES);
|
|
5
|
+
}
|
|
6
|
+
function r(t) {
|
|
7
|
+
return i(t, e.SAGE_BIONETWORKS);
|
|
8
|
+
}
|
|
9
|
+
function i(e, n) {
|
|
10
|
+
return e?.identityProvider ? e.identityProvider.some((e) => t(e) && e.provider === n) : !1;
|
|
5
11
|
}
|
|
6
12
|
//#endregion
|
|
7
|
-
export { n as hasArcusProvider };
|
|
13
|
+
export { n as hasArcusProvider, r as hasSageBionetworksProvider };
|
|
8
14
|
|
|
9
15
|
//# sourceMappingURL=RealmUtils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RealmUtils.js","names":[],"sources":["../../../src/utils/functions/RealmUtils.ts"],"sourcesContent":["import {\n instanceOfOAuthIdentityProvider,\n OAuthIdentityProviderProviderEnum,\n Realm,\n} from '@sage-bionetworks/synapse-client'\n\n/**\n * Check if a realm has the ARCUS_BIOSCIENCES identity provider\n */\nexport function hasArcusProvider(realm?: Realm): boolean {\n if (!realm?.identityProvider) return false\n return realm.identityProvider.some(\n (provider: any) =>\n instanceOfOAuthIdentityProvider(provider) &&\n provider.provider ===
|
|
1
|
+
{"version":3,"file":"RealmUtils.js","names":[],"sources":["../../../src/utils/functions/RealmUtils.ts"],"sourcesContent":["import {\n instanceOfOAuthIdentityProvider,\n OAuthIdentityProviderProviderEnum,\n Realm,\n} from '@sage-bionetworks/synapse-client'\n\n/**\n * Check if a realm has the ARCUS_BIOSCIENCES identity provider\n */\nexport function hasArcusProvider(realm?: Realm): boolean {\n return hasProvider(realm, OAuthIdentityProviderProviderEnum.ARCUS_BIOSCIENCES)\n}\n\n/**\n * Check if a realm has the SAGE_BIONETWORKS identity provider\n */\nexport function hasSageBionetworksProvider(realm?: Realm): boolean {\n return hasProvider(realm, OAuthIdentityProviderProviderEnum.SAGE_BIONETWORKS)\n}\n\nfunction hasProvider(\n realm?: Realm,\n providerEnum?: OAuthIdentityProviderProviderEnum,\n): boolean {\n if (!realm?.identityProvider) return false\n return realm.identityProvider.some(\n (provider: any) =>\n instanceOfOAuthIdentityProvider(provider) &&\n provider.provider === providerEnum,\n )\n}\n"],"mappings":";;AASA,SAAgB,EAAiB,GAAwB;AACvD,QAAO,EAAY,GAAO,EAAkC,kBAAkB;;AAMhF,SAAgB,EAA2B,GAAwB;AACjE,QAAO,EAAY,GAAO,EAAkC,iBAAiB;;AAG/E,SAAS,EACP,GACA,GACS;AAET,QADK,GAAO,mBACL,EAAM,iBAAiB,MAC3B,MACC,EAAgC,EAAS,IACzC,EAAS,aAAa,EACzB,GALoC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SanitizeHtmlUtils.js","names":[],"sources":["../../../src/utils/functions/SanitizeHtmlUtils.ts"],"sourcesContent":["import DOMPurify, { clearWindow, removeAllHooks } from 'isomorphic-dompurify'\nimport xssLib from 'xss'\nimport type { IFilterXSSOptions } from 'xss'\n\n// xss is a CJS-only module; pull runtime values from the default import to\n// avoid \"named export not found\" errors in Vite dev mode (native ESM).\nconst { safeAttrValue, escapeAttrValue } = xssLib as unknown as {\n safeAttrValue: typeof import('xss')['safeAttrValue']\n escapeAttrValue: typeof import('xss')['escapeAttrValue']\n}\n\n// allow list used by both the xss config and DOMPurify config\nconst xssWhiteList: XSS.IWhiteList = {\n a: ['target', 'href', 'title', 'rel'],\n abbr: ['title'],\n address: [],\n area: ['shape', 'coords', 'href', 'alt'],\n article: [],\n aside: [],\n audio: ['autoplay', 'controls', 'loop', 'preload', 'src'],\n b: [],\n bdi: ['dir'],\n bdo: ['dir'],\n big: [],\n blockquote: ['cite'],\n body: [],\n br: [],\n button: ['class'],\n caption: [],\n center: [],\n cite: [],\n code: [],\n col: ['align', 'valign', 'span', 'width'],\n colgroup: ['align', 'valign', 'span', 'width'],\n dd: [],\n del: ['datetime'],\n details: ['open'],\n div: ['class'],\n dl: [],\n dt: [],\n em: [],\n font: ['color', 'size', 'face'],\n footer: [],\n g: [],\n h1: ['toc'],\n h2: ['toc'],\n h3: ['toc'],\n h4: ['toc'],\n h5: ['toc'],\n h6: ['toc'],\n head: [],\n header: [],\n hr: [],\n html: [],\n i: [],\n img: ['src', 'alt', 'title', 'width', 'height'],\n ins: ['datetime'],\n li: ['class'],\n mark: [],\n nav: [],\n noscript: [],\n ol: ['class'],\n p: [],\n pre: [],\n s: [],\n section: [],\n small: [],\n span: ['data-widgetparams', 'data-widget-type', 'class', 'id'],\n sub: [],\n summary: [],\n sup: [],\n strong: [],\n svg: [],\n table: ['width', 'border', 'align', 'valign', 'class'],\n tbody: ['align', 'valign'],\n td: ['width', 'rowspan', 'colspan', 'align', 'valign', 'style'],\n tfoot: ['align', 'valign'],\n th: ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],\n thead: ['class', 'align', 'valign'],\n tr: ['rowspan', 'align', 'valign'],\n tt: [],\n u: [],\n ul: ['class'],\n video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width'],\n}\n\n// PORTALS-1450: including 'style' in the allow-list will cause string values to come through, which crashes the app when used (because it uses jsx).\nexport const xssOptions: IFilterXSSOptions = {\n whiteList: xssWhiteList,\n stripIgnoreTagBody: true, // filter out all tags not in the whitelist\n allowCommentTag: false,\n css: {\n whiteList: {\n // SWC-7015 - Allow text-align property in style attr to support GFM table syntax\n // The 'style' attribute is used to set text alignment for table cells, so explicitly allow it here\n 'text-align': true,\n },\n },\n onIgnoreTag: function (tag, html, options) {\n if (tag === '!doctype') {\n // do not filter doctype\n return html\n }\n return undefined\n },\n safeAttrValue: function (tag, name, value, cssFilter) {\n // Apply default safeAttrValue filtering:\n value = safeAttrValue(tag, name, value, cssFilter)\n // If the tag is an image and the attribute is 'src', only allow data:image URIs or http(s) URLs\n if (tag === 'img' && name === 'src') {\n if (\n !(\n value &&\n (value.startsWith('data:image/') || value.startsWith('http'))\n )\n ) {\n return ''\n }\n }\n value = escapeAttrValue(value)\n return value\n },\n}\n\nconst ALLOWED_TAGS: string[] = Object.keys(xssWhiteList as object)\nconst ALLOWED_ATTR: string[] = [\n ...new Set(\n Object.values(xssWhiteList as object).flat() as string[], // Flatten all attributes into a single array\n ),\n]\n\nconst ALLOWED_STYLES: Record<string, boolean> = {\n 'text-align': true,\n}\n// Regex to allow only secure image sources\nconst ALLOWED_URI_REGEXP: RegExp = /^(https?:|data:image\\/)/\n\nconst domPurifyConfig = {\n ALLOWED_TAGS,\n ALLOWED_ATTR,\n KEEP_CONTENT: true,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n}\n\nfunction configureDomPurify() {\n // Configure DOMPurify with TypeScript support\n DOMPurify.setConfig(domPurifyConfig)\n\n // Hook: Sanitize attributes (equivalent to safeAttrValue)\n DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {\n const { attrName, attrValue } = data\n\n // Ensure only safe src values for images\n if (node.nodeName.toLowerCase() === 'img' && attrName === 'src') {\n if (!ALLOWED_URI_REGEXP.test(attrValue)) {\n data.keepAttr = false // Remove unsafe src values\n }\n }\n\n if (attrName === 'data-widgetparams' || attrName === 'data-widget-type') {\n data.attrValue = attrValue\n } else if (attrName === 'style') {\n const safeStyles = attrValue\n .split(';')\n .map(style => style.trim())\n .filter(style => {\n const [property] = style.split(':').map(s => s.trim())\n return ALLOWED_STYLES[property] || false // Only allow explicitly listed styles\n })\n .join('; ')\n data.attrValue = safeStyles\n } else {\n data.attrValue = DOMPurify.sanitize(attrValue, domPurifyConfig)\n }\n })\n\n // Hook: Prevent removal of `!doctype` (equivalent to onIgnoreTag)\n DOMPurify.addHook('uponSanitizeElement', (node, data) => {\n if (data.tagName === '!doctype') {\n // Reinsert the doctype manually after sanitization\n if (node.parentNode) {\n node.parentNode.insertBefore(\n document.implementation.createDocumentType('html', '', ''),\n node,\n )\n }\n }\n })\n}\n\nexport function sanitize(input: string): string {\n configureDomPurify()\n const result = DOMPurify.sanitize(input, domPurifyConfig)\n // clearWindow clears memory usage in Node.js, noop in browser:\n clearWindow()\n removeAllHooks()\n return result\n}\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"SanitizeHtmlUtils.js","names":[],"sources":["../../../src/utils/functions/SanitizeHtmlUtils.ts"],"sourcesContent":["import DOMPurify, { clearWindow, removeAllHooks } from 'isomorphic-dompurify'\nimport xssLib from 'xss'\nimport type { IFilterXSSOptions } from 'xss'\n\n// xss is a CJS-only module; pull runtime values from the default import to\n// avoid \"named export not found\" errors in Vite dev mode (native ESM).\nconst { safeAttrValue, escapeAttrValue } = xssLib as unknown as {\n safeAttrValue: typeof import('xss')['safeAttrValue']\n escapeAttrValue: typeof import('xss')['escapeAttrValue']\n}\n\n// allow list used by both the xss config and DOMPurify config\nconst xssWhiteList: XSS.IWhiteList = {\n a: ['target', 'href', 'title', 'rel'],\n abbr: ['title'],\n address: [],\n area: ['shape', 'coords', 'href', 'alt'],\n article: [],\n aside: [],\n audio: ['autoplay', 'controls', 'loop', 'preload', 'src'],\n b: [],\n bdi: ['dir'],\n bdo: ['dir'],\n big: [],\n blockquote: ['cite'],\n body: [],\n br: [],\n button: ['class'],\n caption: [],\n center: [],\n cite: [],\n code: [],\n col: ['align', 'valign', 'span', 'width'],\n colgroup: ['align', 'valign', 'span', 'width'],\n dd: [],\n del: ['datetime'],\n details: ['open'],\n div: ['class'],\n dl: [],\n dt: [],\n em: [],\n font: ['color', 'size', 'face'],\n footer: [],\n g: [],\n h1: ['toc'],\n h2: ['toc'],\n h3: ['toc'],\n h4: ['toc'],\n h5: ['toc'],\n h6: ['toc'],\n head: [],\n header: [],\n hr: [],\n html: [],\n i: [],\n img: ['src', 'alt', 'title', 'width', 'height'],\n ins: ['datetime'],\n li: ['class'],\n mark: [],\n nav: [],\n noscript: [],\n ol: ['class'],\n p: [],\n pre: [],\n s: [],\n section: [],\n small: [],\n span: ['data-widgetparams', 'data-widget-type', 'class', 'id'],\n sub: [],\n summary: [],\n sup: [],\n strong: [],\n svg: [],\n table: ['width', 'border', 'align', 'valign', 'class'],\n tbody: ['align', 'valign'],\n td: ['width', 'rowspan', 'colspan', 'align', 'valign', 'style'],\n tfoot: ['align', 'valign'],\n th: ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],\n thead: ['class', 'align', 'valign'],\n tr: ['rowspan', 'align', 'valign'],\n tt: [],\n u: [],\n ul: ['class'],\n video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width'],\n}\n\n// PORTALS-1450: including 'style' in the allow-list will cause string values to come through, which crashes the app when used (because it uses jsx).\nexport const xssOptions: IFilterXSSOptions = {\n whiteList: xssWhiteList,\n stripIgnoreTagBody: true, // filter out all tags not in the whitelist\n allowCommentTag: false,\n css: {\n whiteList: {\n // SWC-7015 - Allow text-align property in style attr to support GFM table syntax\n // The 'style' attribute is used to set text alignment for table cells, so explicitly allow it here\n 'text-align': true,\n },\n },\n onIgnoreTag: function (tag, html, options) {\n if (tag === '!doctype') {\n // do not filter doctype\n return html\n }\n return undefined\n },\n safeAttrValue: function (tag, name, value, cssFilter) {\n // Apply default safeAttrValue filtering:\n value = safeAttrValue(tag, name, value, cssFilter)\n // If the tag is an image and the attribute is 'src', only allow data:image URIs or http(s) URLs\n if (tag === 'img' && name === 'src') {\n if (\n !(\n value &&\n (value.startsWith('data:image/') || value.startsWith('http'))\n )\n ) {\n return ''\n }\n }\n value = escapeAttrValue(value)\n return value\n },\n}\n\nconst ALLOWED_TAGS: string[] = Object.keys(xssWhiteList as object)\nconst ALLOWED_ATTR: string[] = [\n ...new Set(\n Object.values(xssWhiteList as object).flat() as string[], // Flatten all attributes into a single array\n ),\n]\n\nconst ALLOWED_STYLES: Record<string, boolean> = {\n 'text-align': true,\n}\n// Regex to allow only secure image sources\nconst ALLOWED_URI_REGEXP: RegExp = /^(https?:|data:image\\/)/\n\nconst domPurifyConfig = {\n ALLOWED_TAGS,\n ALLOWED_ATTR,\n KEEP_CONTENT: true,\n RETURN_DOM_FRAGMENT: false,\n RETURN_TRUSTED_TYPE: false,\n}\n\nfunction configureDomPurify() {\n // Configure DOMPurify with TypeScript support\n DOMPurify.setConfig(domPurifyConfig)\n\n // Hook: Sanitize attributes (equivalent to safeAttrValue)\n DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {\n const { attrName, attrValue } = data\n\n // Ensure only safe src values for images\n if (node.nodeName.toLowerCase() === 'img' && attrName === 'src') {\n if (!ALLOWED_URI_REGEXP.test(attrValue)) {\n data.keepAttr = false // Remove unsafe src values\n }\n }\n\n if (attrName === 'data-widgetparams' || attrName === 'data-widget-type') {\n data.attrValue = attrValue\n } else if (attrName === 'style') {\n const safeStyles = attrValue\n .split(';')\n .map(style => style.trim())\n .filter(style => {\n const [property] = style.split(':').map(s => s.trim())\n return ALLOWED_STYLES[property] || false // Only allow explicitly listed styles\n })\n .join('; ')\n data.attrValue = safeStyles\n } else {\n data.attrValue = DOMPurify.sanitize(attrValue, domPurifyConfig)\n }\n })\n\n // Hook: Prevent removal of `!doctype` (equivalent to onIgnoreTag)\n DOMPurify.addHook('uponSanitizeElement', (node, data) => {\n if (data.tagName === '!doctype') {\n // Reinsert the doctype manually after sanitization\n if (node.parentNode) {\n node.parentNode.insertBefore(\n document.implementation.createDocumentType('html', '', ''),\n node,\n )\n }\n }\n })\n}\n\nexport function sanitize(input: string): string {\n configureDomPurify()\n const result = DOMPurify.sanitize(input, domPurifyConfig)\n // clearWindow clears memory usage in Node.js, noop in browser:\n clearWindow()\n removeAllHooks()\n return result\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SanitizeHtmlUtils.test-utils.js","names":[],"sources":["../../../src/utils/functions/SanitizeHtmlUtils.test-utils.ts"],"sourcesContent":["/**\n * Shared test factory for SanitizeHtmlUtils.\n * Imported by both the CSR (jsdom) and SSR (node) test files so test cases\n * are defined once and run in both environments.\n *\n * Uses explicit JSDOM for HTML parsing so the helpers work regardless of\n * whether a global `document` is available.\n */\nimport xss from 'xss'\nimport { JSDOM } from 'jsdom'\nimport { sanitize, xssOptions } from './SanitizeHtmlUtils'\n\nfunction parseHTML(html: string) {\n return new JSDOM(html).window.document.documentElement\n}\n\nexport function runDomPurifyTests() {\n describe('HTML Sanitization - DOMPurify', () => {\n test('Prevents XSS through onload', () => {\n const sanitized = sanitize(\"<span onload='alert('XSS')'>foo</span>\")\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onload')).toBeNull()\n })\n test('Prevents XSS through onclick', () => {\n const sanitized = sanitize(\"<span onclick='alert('XSS')'>foo</span>\")\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onclick')).toBeNull()\n })\n test('Prevents XSS through href', () => {\n const script = 'javascript:alert(1)'\n const sanitized = sanitize(`<a href=\"${script}\">foo</a>`)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n // Verify script was removed\n expect(anchor.getAttribute('href')).toBeNull()\n })\n test('Allows valid link href', () => {\n const validHref = 'https://synapse.org'\n const sanitized = sanitize(`<a href=\"${validHref}\">foo</a>`)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(validHref)\n })\n test('Allows link rel property', () => {\n const href = 'https://synapse.org'\n const rel = 'noopener noreferrer'\n const sanitized = sanitize(`<a href=\"${href}\" rel=\"${rel}\">foo</a>`)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(href)\n expect(anchor.getAttribute('rel')).toEqual(rel)\n })\n\n describe.each(['td', 'th'])('%s cell text alignment', tag => {\n test(`Allows ${tag} with text-align style`, () => {\n const input = `<table><tbody><tr><${tag} style=\"text-align:center\">foo</${tag}></tr></tbody></table>`\n const expected = input\n const sanitized = sanitize(input)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n\n test(`Removes styles other than text-align from ${tag}`, () => {\n // Some other style is applied, which is not allowed (may be a click-jacking attempt!)\n const input = `<table><tbody><tr><${tag} style=\"text-align:center; position: absolute;\">foo</${tag}></tr></tbody></table>`\n // position is not an allowed CSS property\n const expected = `<table><tbody><tr><${tag} style=\"text-align:center\">foo</${tag}></tr></tbody></table>`\n const sanitized = sanitize(input)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n })\n })\n}\n\nexport function runXssTests() {\n describe('HTML Sanitization - xss (legacy)', () => {\n test('Prevents XSS through onload', () => {\n const sanitized = xss(\n \"<span onload='alert('XSS')'>foo</span>\",\n xssOptions,\n )\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onload')).toBeNull()\n })\n test('Prevents XSS through onclick', () => {\n const sanitized = xss(\n \"<span onclick='alert('XSS')'>foo</span>\",\n xssOptions,\n )\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onclick')).toBeNull()\n })\n test('Prevents XSS through href', () => {\n const script = 'javascript:alert(1)'\n const sanitized = xss(`<a href=\"${script}\">foo</a>`, xssOptions)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n // Verify script was removed\n expect(anchor.getAttribute('href')).toEqual('')\n })\n test('Allows valid link href', () => {\n const validHref = 'https://synapse.org'\n const sanitized = xss(`<a href=\"${validHref}\">foo</a>`, xssOptions)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(validHref)\n })\n test('Allows link rel property', () => {\n const href = 'https://synapse.org'\n const rel = 'noopener noreferrer'\n const sanitized = xss(\n `<a href=\"${href}\" rel=\"${rel}\">foo</a>`,\n xssOptions,\n )\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(href)\n expect(anchor.getAttribute('rel')).toEqual(rel)\n })\n\n describe.each(['td', 'th'])('%s cell text alignment', tag => {\n test(`Allows ${tag} with text-align style`, () => {\n const input = `<table><tbody><tr><${tag} style=\"text-align:center;\">foo</${tag}></tr></tbody></table>`\n const expected = input\n const sanitized = xss(input, xssOptions)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n\n test(`Removes styles other than text-align from ${tag}`, () => {\n // Some other style is applied, which is not allowed (may be a click-jacking attempt!)\n const input = `<table><tbody><tr><${tag} style=\"text-align:center; position: absolute;\">foo</${tag}></tr></tbody></table>`\n // position is not an allowed CSS property\n const expected = `<table><tbody><tr><${tag} style=\"text-align:center;\">foo</${tag}></tr></tbody></table>`\n const sanitized = xss(input, xssOptions)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n })\n })\n}\n"],"mappings":";;;;AAYA,SAAS,EAAU,GAAc;AAC/B,QAAO,IAAI,EAAM,EAAK,CAAC,OAAO,SAAS;;AAGzC,SAAgB,IAAoB;AAClC,UAAS,uCAAuC;AA2C9C,EA1CA,KAAK,qCAAqC;GAGxC,IAAM,IADO,EADK,EAAS,
|
|
1
|
+
{"version":3,"file":"SanitizeHtmlUtils.test-utils.js","names":[],"sources":["../../../src/utils/functions/SanitizeHtmlUtils.test-utils.ts"],"sourcesContent":["/**\n * Shared test factory for SanitizeHtmlUtils.\n * Imported by both the CSR (jsdom) and SSR (node) test files so test cases\n * are defined once and run in both environments.\n *\n * Uses explicit JSDOM for HTML parsing so the helpers work regardless of\n * whether a global `document` is available.\n */\nimport xss from 'xss'\nimport { JSDOM } from 'jsdom'\nimport { sanitize, xssOptions } from './SanitizeHtmlUtils'\n\nfunction parseHTML(html: string) {\n return new JSDOM(html).window.document.documentElement\n}\n\nexport function runDomPurifyTests() {\n describe('HTML Sanitization - DOMPurify', () => {\n test('Prevents XSS through onload', () => {\n const sanitized = sanitize(\"<span onload='alert('XSS')'>foo</span>\")\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onload')).toBeNull()\n })\n test('Prevents XSS through onclick', () => {\n const sanitized = sanitize(\"<span onclick='alert('XSS')'>foo</span>\")\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onclick')).toBeNull()\n })\n test('Prevents XSS through href', () => {\n const script = 'javascript:alert(1)'\n const sanitized = sanitize(`<a href=\"${script}\">foo</a>`)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n // Verify script was removed\n expect(anchor.getAttribute('href')).toBeNull()\n })\n test('Allows valid link href', () => {\n const validHref = 'https://synapse.org'\n const sanitized = sanitize(`<a href=\"${validHref}\">foo</a>`)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(validHref)\n })\n test('Allows link rel property', () => {\n const href = 'https://synapse.org'\n const rel = 'noopener noreferrer'\n const sanitized = sanitize(`<a href=\"${href}\" rel=\"${rel}\">foo</a>`)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(href)\n expect(anchor.getAttribute('rel')).toEqual(rel)\n })\n\n describe.each(['td', 'th'])('%s cell text alignment', tag => {\n test(`Allows ${tag} with text-align style`, () => {\n const input = `<table><tbody><tr><${tag} style=\"text-align:center\">foo</${tag}></tr></tbody></table>`\n const expected = input\n const sanitized = sanitize(input)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n\n test(`Removes styles other than text-align from ${tag}`, () => {\n // Some other style is applied, which is not allowed (may be a click-jacking attempt!)\n const input = `<table><tbody><tr><${tag} style=\"text-align:center; position: absolute;\">foo</${tag}></tr></tbody></table>`\n // position is not an allowed CSS property\n const expected = `<table><tbody><tr><${tag} style=\"text-align:center\">foo</${tag}></tr></tbody></table>`\n const sanitized = sanitize(input)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n })\n })\n}\n\nexport function runXssTests() {\n describe('HTML Sanitization - xss (legacy)', () => {\n test('Prevents XSS through onload', () => {\n const sanitized = xss(\n \"<span onload='alert('XSS')'>foo</span>\",\n xssOptions,\n )\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onload')).toBeNull()\n })\n test('Prevents XSS through onclick', () => {\n const sanitized = xss(\n \"<span onclick='alert('XSS')'>foo</span>\",\n xssOptions,\n )\n const html = parseHTML(sanitized)\n const span = html.querySelector('span')!\n expect(span).not.toBeNull()\n expect(span.getAttribute('onclick')).toBeNull()\n })\n test('Prevents XSS through href', () => {\n const script = 'javascript:alert(1)'\n const sanitized = xss(`<a href=\"${script}\">foo</a>`, xssOptions)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n // Verify script was removed\n expect(anchor.getAttribute('href')).toEqual('')\n })\n test('Allows valid link href', () => {\n const validHref = 'https://synapse.org'\n const sanitized = xss(`<a href=\"${validHref}\">foo</a>`, xssOptions)\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(validHref)\n })\n test('Allows link rel property', () => {\n const href = 'https://synapse.org'\n const rel = 'noopener noreferrer'\n const sanitized = xss(\n `<a href=\"${href}\" rel=\"${rel}\">foo</a>`,\n xssOptions,\n )\n const html = parseHTML(sanitized)\n const anchor = html.querySelector('a')!\n expect(anchor).not.toBeNull()\n expect(anchor.getAttribute('href')).toEqual(href)\n expect(anchor.getAttribute('rel')).toEqual(rel)\n })\n\n describe.each(['td', 'th'])('%s cell text alignment', tag => {\n test(`Allows ${tag} with text-align style`, () => {\n const input = `<table><tbody><tr><${tag} style=\"text-align:center;\">foo</${tag}></tr></tbody></table>`\n const expected = input\n const sanitized = xss(input, xssOptions)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n\n test(`Removes styles other than text-align from ${tag}`, () => {\n // Some other style is applied, which is not allowed (may be a click-jacking attempt!)\n const input = `<table><tbody><tr><${tag} style=\"text-align:center; position: absolute;\">foo</${tag}></tr></tbody></table>`\n // position is not an allowed CSS property\n const expected = `<table><tbody><tr><${tag} style=\"text-align:center;\">foo</${tag}></tr></tbody></table>`\n const sanitized = xss(input, xssOptions)\n\n // style should be allowed in this case\n expect(sanitized).toEqual(expected)\n })\n })\n })\n}\n"],"mappings":";;;;AAYA,SAAS,EAAU,GAAc;AAC/B,QAAO,IAAI,EAAM,EAAK,CAAC,OAAO,SAAS;;AAGzC,SAAgB,IAAoB;AAClC,UAAS,uCAAuC;AA2C9C,EA1CA,KAAK,qCAAqC;GAGxC,IAAM,IADO,EADK,EAAS,yCACJ,CACV,CAAK,cAAc,OAAO;AAEvC,GADA,OAAO,EAAK,CAAC,IAAI,UAAU,EAC3B,OAAO,EAAK,aAAa,SAAS,CAAC,CAAC,UAAU;IAC9C,EACF,KAAK,sCAAsC;GAGzC,IAAM,IADO,EADK,EAAS,0CACJ,CACV,CAAK,cAAc,OAAO;AAEvC,GADA,OAAO,EAAK,CAAC,IAAI,UAAU,EAC3B,OAAO,EAAK,aAAa,UAAU,CAAC,CAAC,UAAU;IAC/C,EACF,KAAK,mCAAmC;GAItC,IAAM,IADO,EADK,EAAS,0CACJ,CACR,CAAK,cAAc,IAAI;AAGtC,GAFA,OAAO,EAAO,CAAC,IAAI,UAAU,EAE7B,OAAO,EAAO,aAAa,OAAO,CAAC,CAAC,UAAU;IAC9C,EACF,KAAK,gCAAgC;GACnC,IAAM,IAAY,uBAGZ,IADO,EADK,EAAS,YAAY,EAAU,WAC1B,CACR,CAAK,cAAc,IAAI;AAEtC,GADA,OAAO,EAAO,CAAC,IAAI,UAAU,EAC7B,OAAO,EAAO,aAAa,OAAO,CAAC,CAAC,QAAQ,EAAU;IACtD,EACF,KAAK,kCAAkC;GACrC,IAAM,IAAO,uBACP,IAAM,uBAGN,IADO,EADK,EAAS,YAAY,EAAK,SAAS,EAAI,WAClC,CACR,CAAK,cAAc,IAAI;AAGtC,GAFA,OAAO,EAAO,CAAC,IAAI,UAAU,EAC7B,OAAO,EAAO,aAAa,OAAO,CAAC,CAAC,QAAQ,EAAK,EACjD,OAAO,EAAO,aAAa,MAAM,CAAC,CAAC,QAAQ,EAAI;IAC/C,EAEF,SAAS,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,2BAA0B,MAAO;AAU3D,GATA,KAAK,UAAU,EAAI,+BAA+B;IAChD,IAAM,IAAQ,sBAAsB,EAAI,kCAAkC,EAAI,yBACxE,IAAW,GACX,IAAY,EAAS,EAAM;AAGjC,WAAO,EAAU,CAAC,QAAQ,EAAS;KACnC,EAEF,KAAK,6CAA6C,WAAa;IAE7D,IAAM,IAAQ,sBAAsB,EAAI,uDAAuD,EAAI,yBAE7F,IAAW,sBAAsB,EAAI,kCAAkC,EAAI,yBAC3E,IAAY,EAAS,EAAM;AAGjC,WAAO,EAAU,CAAC,QAAQ,EAAS;KACnC;IACF;GACF;;AAGJ,SAAgB,IAAc;AAC5B,UAAS,0CAA0C;AAoDjD,EAnDA,KAAK,qCAAqC;GAMxC,IAAM,IADO,EAJK,EAChB,0CACA,EAEqB,CACV,CAAK,cAAc,OAAO;AAEvC,GADA,OAAO,EAAK,CAAC,IAAI,UAAU,EAC3B,OAAO,EAAK,aAAa,SAAS,CAAC,CAAC,UAAU;IAC9C,EACF,KAAK,sCAAsC;GAMzC,IAAM,IADO,EAJK,EAChB,2CACA,EAEqB,CACV,CAAK,cAAc,OAAO;AAEvC,GADA,OAAO,EAAK,CAAC,IAAI,UAAU,EAC3B,OAAO,EAAK,aAAa,UAAU,CAAC,CAAC,UAAU;IAC/C,EACF,KAAK,mCAAmC;GAItC,IAAM,IADO,EADK,EAAI,2CAA+B,EAC9B,CACR,CAAK,cAAc,IAAI;AAGtC,GAFA,OAAO,EAAO,CAAC,IAAI,UAAU,EAE7B,OAAO,EAAO,aAAa,OAAO,CAAC,CAAC,QAAQ,GAAG;IAC/C,EACF,KAAK,gCAAgC;GACnC,IAAM,IAAY,uBAGZ,IADO,EADK,EAAI,YAAY,EAAU,YAAY,EACjC,CACR,CAAK,cAAc,IAAI;AAEtC,GADA,OAAO,EAAO,CAAC,IAAI,UAAU,EAC7B,OAAO,EAAO,aAAa,OAAO,CAAC,CAAC,QAAQ,EAAU;IACtD,EACF,KAAK,kCAAkC;GACrC,IAAM,IAAO,uBACP,IAAM,uBAMN,IADO,EAJK,EAChB,YAAY,EAAK,SAAS,EAAI,YAC9B,EAEqB,CACR,CAAK,cAAc,IAAI;AAGtC,GAFA,OAAO,EAAO,CAAC,IAAI,UAAU,EAC7B,OAAO,EAAO,aAAa,OAAO,CAAC,CAAC,QAAQ,EAAK,EACjD,OAAO,EAAO,aAAa,MAAM,CAAC,CAAC,QAAQ,EAAI;IAC/C,EAEF,SAAS,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,2BAA0B,MAAO;AAU3D,GATA,KAAK,UAAU,EAAI,+BAA+B;IAChD,IAAM,IAAQ,sBAAsB,EAAI,mCAAmC,EAAI,yBACzE,IAAW,GACX,IAAY,EAAI,GAAO,EAAW;AAGxC,WAAO,EAAU,CAAC,QAAQ,EAAS;KACnC,EAEF,KAAK,6CAA6C,WAAa;IAE7D,IAAM,IAAQ,sBAAsB,EAAI,uDAAuD,EAAI,yBAE7F,IAAW,sBAAsB,EAAI,mCAAmC,EAAI,yBAC5E,IAAY,EAAI,GAAO,EAAW;AAGxC,WAAO,EAAU,CAAC,QAAQ,EAAS;KACnC;IACF;GACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SqlFunctions.js","names":[],"sources":["../../../src/utils/functions/SqlFunctions.ts"],"sourcesContent":["import {\n ColumnModel,\n ColumnMultiValueFunction,\n ColumnMultiValueFunctionQueryFilter,\n ColumnSingleValueFilterOperator,\n ColumnSingleValueQueryFilter,\n QueryFilter,\n Row,\n SelectColumn,\n TextMatchesQueryFilter,\n} from '@sage-bionetworks/synapse-types'\nimport { SYNAPSE_ENTITY_ID_REGEX } from './RegularExpressions'\nimport { splitAndTrim } from './StringUtils'\n\nexport type SQLOperator =\n | ColumnSingleValueFilterOperator\n | ColumnMultiValueFunction\n\nconst WITHOUT_SYN_PREFIX = 3\nexport const QUERY_FILTERS_SESSION_STORAGE_KEY = (key: string) =>\n `${key}-temp-QueryFilter-array`\n\nexport function removePrefixIfSynId(value: string) {\n if (value.match(SYNAPSE_ENTITY_ID_REGEX)) {\n return value.substring(WITHOUT_SYN_PREFIX)\n }\n return value\n}\nexport const getIgnoredQueryFilterSearchParamKey = (\n key: string,\n namespace?: string,\n) => {\n return `__${namespace ?? ''}_${key}`\n}\n\n// Special search parameter key that will automatically apply a FTS search term to a Query Wrapper if present\nexport const SEARCH_TERM = 'SEARCH_TERM'\nexport const SEARCH_ROLE = 'SEARCH_ROLE'\n\n/** @deprecated Use SEARCH_TERM instead */\nexport const FTS_SEARCH_TERM = 'FTS_SEARCH_TERM'\n/** @deprecated Use SEARCH_ROLE instead */\nexport const FTS_SEARCH_ROLE = 'FTS_SEARCH_ROLE'\n\n/**\n * Look in local storage for a set of QueryFilters to apply. In addition, given the search params,\n * generate a set of QueryFilters to narrow the query to view just related data.\n * May return null if a QueryFilter should not be added.\n * @param sql\n * @param searchParams\n * @param operator\n * @returns\n */\nexport const getAdditionalFilters = (\n searchParams?: Record<string, string>,\n operator: SQLOperator = ColumnSingleValueFilterOperator.LIKE,\n sessionStorageKey?: string,\n): QueryFilter[] | undefined => {\n const sessionStorageQueryFiltersString = sessionStorageKey\n ? sessionStorage.getItem(\n QUERY_FILTERS_SESSION_STORAGE_KEY(sessionStorageKey),\n )\n : undefined\n let additionalFilters: QueryFilter[] = []\n if (sessionStorageQueryFiltersString) {\n additionalFilters = JSON.parse(\n sessionStorageQueryFiltersString,\n ) as QueryFilter[]\n }\n if (searchParams) {\n const isQueryWrapperKey = (key: string) =>\n key.startsWith('qw') || key.startsWith('__')\n additionalFilters = additionalFilters.concat(\n Object.keys(searchParams || {})\n .filter(\n key =>\n !isQueryWrapperKey(key) &&\n searchParams[key] != undefined &&\n searchParams[key].trim() != '',\n )\n .map(key => {\n if (key == SEARCH_TERM || key == FTS_SEARCH_TERM) {\n const filter: TextMatchesQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.TextMatchesQueryFilter',\n searchExpression: searchParams[key],\n }\n return filter\n }\n switch (operator) {\n case ColumnSingleValueFilterOperator.EQUAL: {\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: key,\n operator: operator,\n values: [searchParams[key]],\n }\n return filter\n }\n case ColumnSingleValueFilterOperator.IN: {\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: key,\n operator: operator,\n values: splitAndTrim(searchParams[key]),\n }\n return filter\n }\n case ColumnMultiValueFunction.HAS: {\n const filter: ColumnMultiValueFunctionQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnMultiValueFunctionQueryFilter',\n columnName: key,\n function: operator,\n _function: operator,\n values: splitAndTrim(searchParams[key]),\n }\n return filter\n }\n case ColumnSingleValueFilterOperator.LIKE: {\n // If we use a LIKE statement with a synId the backend will look for a string with the first three\n // characters being 'syn', however, it stores synIds without 'syn', so the query will fail\n // The backend usually parses 'syn' out, but not with the LIKE clause since its expecting a regex, so we\n // parse this out. This will cause a bug if something matches the synId regex but is in free text.\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: key,\n operator: operator,\n // Add wildcards around the value\n values: [`%${removePrefixIfSynId(searchParams[key])}%`],\n }\n return filter\n }\n case ColumnMultiValueFunction.HAS_LIKE: {\n const filter: ColumnMultiValueFunctionQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnMultiValueFunctionQueryFilter',\n columnName: key,\n function: operator,\n _function: operator,\n values: searchParams[key].split(',').map(param => {\n // Remove synId prefix for the same reasons as in the LIKE case\n return `%${removePrefixIfSynId(param)}%`\n }),\n }\n return filter\n }\n }\n }),\n )\n }\n\n return additionalFilters.length === 0 ? undefined : additionalFilters\n}\n\n//parses synapse entity id from a sql query string\n//look for a pattern of 'from[some number of spaces]syn[somenumbers]` case insensitive\nexport const parseEntityIdFromSqlStatement = (sql: string): string => {\n const matches = sql.match(/(from)\\s+(syn)\\d+/gi)\n return matches && matches[0] ? matches[0].substr(5).trim() : ''\n}\n\nexport const parseEntityIdAndVersionFromSqlStatement = (\n sql: string,\n): { entityId?: string; versionNumber?: number } => {\n const regex = /from\\s+(syn\\d+)(?:\\.(\\d+))?/i\n const matches = regex.exec(sql)\n if (!matches) {\n return { entityId: undefined, versionNumber: undefined }\n }\n return {\n entityId: matches[1],\n versionNumber: matches[2] ? parseInt(matches[2]) : undefined,\n }\n}\n\nexport const resultToJson = (\n headerColumns: SelectColumn[],\n rowColumns: Row[],\n) => {\n const rows = rowColumns.map(row => row.values)\n const headers = headerColumns.map(column => column.name)\n return rows.map(row => {\n const obj: Record<string, string | null> = {}\n row.forEach((text, cellIndex) => {\n obj[headers[cellIndex]] = text\n })\n return obj\n })\n}\n\n/**\n * Finds the index of the given column name in the selectColumns or columnModels.\n *\n * @returns a non-negative integer if the column is found, or undefined if the column is not found\n * @param columnName\n * @param selectColumns\n * @param columnModels\n */\nexport const getColumnIndex = (\n columnName?: string,\n selectColumns?: SelectColumn[],\n columnModels?: ColumnModel[],\n): number | undefined => {\n if (!columnName) {\n return undefined\n }\n\n const lowerCaseColumnName = columnName.toLowerCase()\n\n if (selectColumns) {\n const findIndexResult = selectColumns.findIndex(\n el => el.name?.toLowerCase() === lowerCaseColumnName,\n )\n if (findIndexResult != -1) {\n return findIndexResult\n }\n }\n\n if (columnModels) {\n const findIndexResult = columnModels.findIndex(\n el => el.name?.toLowerCase() === lowerCaseColumnName,\n )\n if (findIndexResult !== -1) {\n return findIndexResult\n }\n }\n\n return undefined\n}\n"],"mappings":";;;;AAkBA,IAAM,IAAqB,GACd,KAAqC,MAChD,GAAG,EAAI;AAET,SAAgB,EAAoB,GAAe;AAIjD,QAHI,EAAM,MAAM,EAAwB,GAC/B,EAAM,UAAU,EAAmB,GAErC;;AAET,IAAa,KACX,GACA,MAEO,KAAK,KAAa,GAAG,GAAG,KAIpB,IAAc,eACd,IAAc,eAGd,IAAkB,mBAElB,IAAkB,mBAWlB,KACX,GACA,IAAwB,EAAgC,MACxD,MAC8B;CAC9B,IAAM,IAAmC,IACrC,eAAe,QACb,EAAkC,EAAkB,CACrD,GACD,KAAA,GACA,IAAmC,EAAE;AAMzC,KALI,MACF,IAAoB,KAAK,MACvB,EACD,GAEC,GAAc;EAChB,IAAM,KAAqB,MACzB,EAAI,WAAW,KAAK,IAAI,EAAI,WAAW,KAAK;AAC9C,MAAoB,EAAkB,OACpC,OAAO,KAAK,KAAgB,EAAE,CAAC,CAC5B,QACC,MACE,CAAC,EAAkB,EAAI,IACvB,EAAa,MAAQ,QACrB,EAAa,GAAK,MAAM,IAAI,GAC/B,CACA,KAAI,MAAO;AACV,OAAI,KAAA,iBAAsB,KAAA,kBAMxB,QALuC;IACrC,cACE;IACF,kBAAkB,EAAa;IAChC;AAGH,WAAQ,GAAR;IACE,KAAK,EAAgC,MAQnC,QAP6C;KAC3C,cACE;KACF,YAAY;KACF;KACV,QAAQ,CAAC,EAAa,GAAK;KAC5B;IAGH,KAAK,EAAgC,GAQnC,QAP6C;KAC3C,cACE;KACF,YAAY;KACF;KACV,QAAQ,EAAa,EAAa,GAAK;KACxC;IAGH,KAAK,EAAyB,IAS5B,QARoD;KAClD,cACE;KACF,YAAY;KACZ,UAAU;KACV,WAAW;KACX,QAAQ,EAAa,EAAa,GAAK;KACxC;IAGH,KAAK,EAAgC,KAanC,QAR6C;KAC3C,cACE;KACF,YAAY;KACF;KAEV,QAAQ,CAAC,IAAI,EAAoB,EAAa,GAAK,CAAC,GAAG;KACxD;IAGH,KAAK,EAAyB,SAY5B,QAXoD;KAClD,cACE;KACF,YAAY;KACZ,UAAU;KACV,WAAW;KACX,QAAQ,EAAa,GAAK,MAAM,IAAI,CAAC,KAAI,MAEhC,IAAI,EAAoB,EAAM,CAAC,GACtC;KACH;;IAIL,CACL;;AAGH,QAAO,EAAkB,WAAW,IAAI,KAAA,IAAY;GAKzC,KAAiC,MAAwB;CACpE,IAAM,IAAU,EAAI,MAAM,sBAAsB;AAChD,QAAO,KAAW,EAAQ,KAAK,EAAQ,GAAG,OAAO,EAAE,CAAC,MAAM,GAAG;GAGlD,KACX,MACkD;CAElD,IAAM,IADQ,+BACQ,KAAK,EAAI;AAI/B,QAHK,IAGE;EACL,UAAU,EAAQ;EAClB,eAAe,EAAQ,KAAK,SAAS,EAAQ,GAAG,GAAG,KAAA;EACpD,GALQ;EAAE,UAAU,KAAA;EAAW,eAAe,KAAA;EAAW;GAQ/C,KACX,GACA,MACG;CACH,IAAM,IAAO,EAAW,KAAI,MAAO,EAAI,OAAO,EACxC,IAAU,EAAc,KAAI,MAAU,EAAO,KAAK;AACxD,QAAO,EAAK,KAAI,MAAO;EACrB,IAAM,IAAqC,EAAE;AAI7C,SAHA,EAAI,SAAS,GAAM,MAAc;AAC/B,KAAI,EAAQ,MAAc;IAC1B,EACK;GACP;GAWS,KACX,GACA,GACA,MACuB;AACvB,KAAI,CAAC,EACH;CAGF,IAAM,IAAsB,EAAW,aAAa;AAEpD,KAAI,GAAe;EACjB,IAAM,IAAkB,EAAc,WACpC,MAAM,EAAG,MAAM,aAAa,KAAK,EAClC;AACD,MAAI,KAAmB,GACrB,QAAO;;AAIX,KAAI,GAAc;EAChB,IAAM,IAAkB,EAAa,WACnC,MAAM,EAAG,MAAM,aAAa,KAAK,EAClC;AACD,MAAI,MAAoB,GACtB,QAAO"}
|
|
1
|
+
{"version":3,"file":"SqlFunctions.js","names":[],"sources":["../../../src/utils/functions/SqlFunctions.ts"],"sourcesContent":["import {\n ColumnModel,\n ColumnMultiValueFunction,\n ColumnMultiValueFunctionQueryFilter,\n ColumnSingleValueFilterOperator,\n ColumnSingleValueQueryFilter,\n QueryFilter,\n Row,\n SelectColumn,\n TextMatchesQueryFilter,\n} from '@sage-bionetworks/synapse-types'\nimport { SYNAPSE_ENTITY_ID_REGEX } from './RegularExpressions'\nimport { splitAndTrim } from './StringUtils'\n\nexport type SQLOperator =\n | ColumnSingleValueFilterOperator\n | ColumnMultiValueFunction\n\nconst WITHOUT_SYN_PREFIX = 3\nexport const QUERY_FILTERS_SESSION_STORAGE_KEY = (key: string) =>\n `${key}-temp-QueryFilter-array`\n\nexport function removePrefixIfSynId(value: string) {\n if (value.match(SYNAPSE_ENTITY_ID_REGEX)) {\n return value.substring(WITHOUT_SYN_PREFIX)\n }\n return value\n}\nexport const getIgnoredQueryFilterSearchParamKey = (\n key: string,\n namespace?: string,\n) => {\n return `__${namespace ?? ''}_${key}`\n}\n\n// Special search parameter key that will automatically apply a FTS search term to a Query Wrapper if present\nexport const SEARCH_TERM = 'SEARCH_TERM'\nexport const SEARCH_ROLE = 'SEARCH_ROLE'\n\n/** @deprecated Use SEARCH_TERM instead */\nexport const FTS_SEARCH_TERM = 'FTS_SEARCH_TERM'\n/** @deprecated Use SEARCH_ROLE instead */\nexport const FTS_SEARCH_ROLE = 'FTS_SEARCH_ROLE'\n\n/**\n * Look in local storage for a set of QueryFilters to apply. In addition, given the search params,\n * generate a set of QueryFilters to narrow the query to view just related data.\n * May return null if a QueryFilter should not be added.\n * @param sql\n * @param searchParams\n * @param operator\n * @returns\n */\nexport const getAdditionalFilters = (\n searchParams?: Record<string, string>,\n operator: SQLOperator = ColumnSingleValueFilterOperator.LIKE,\n sessionStorageKey?: string,\n): QueryFilter[] | undefined => {\n const sessionStorageQueryFiltersString = sessionStorageKey\n ? sessionStorage.getItem(\n QUERY_FILTERS_SESSION_STORAGE_KEY(sessionStorageKey),\n )\n : undefined\n let additionalFilters: QueryFilter[] = []\n if (sessionStorageQueryFiltersString) {\n additionalFilters = JSON.parse(\n sessionStorageQueryFiltersString,\n ) as QueryFilter[]\n }\n if (searchParams) {\n const isQueryWrapperKey = (key: string) =>\n key.startsWith('qw') || key.startsWith('__')\n additionalFilters = additionalFilters.concat(\n Object.keys(searchParams || {})\n .filter(\n key =>\n !isQueryWrapperKey(key) &&\n searchParams[key] != undefined &&\n searchParams[key].trim() != '',\n )\n .map(key => {\n if (key == SEARCH_TERM || key == FTS_SEARCH_TERM) {\n const filter: TextMatchesQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.TextMatchesQueryFilter',\n searchExpression: searchParams[key],\n }\n return filter\n }\n switch (operator) {\n case ColumnSingleValueFilterOperator.EQUAL: {\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: key,\n operator: operator,\n values: [searchParams[key]],\n }\n return filter\n }\n case ColumnSingleValueFilterOperator.IN: {\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: key,\n operator: operator,\n values: splitAndTrim(searchParams[key]),\n }\n return filter\n }\n case ColumnMultiValueFunction.HAS: {\n const filter: ColumnMultiValueFunctionQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnMultiValueFunctionQueryFilter',\n columnName: key,\n function: operator,\n _function: operator,\n values: splitAndTrim(searchParams[key]),\n }\n return filter\n }\n case ColumnSingleValueFilterOperator.LIKE: {\n // If we use a LIKE statement with a synId the backend will look for a string with the first three\n // characters being 'syn', however, it stores synIds without 'syn', so the query will fail\n // The backend usually parses 'syn' out, but not with the LIKE clause since its expecting a regex, so we\n // parse this out. This will cause a bug if something matches the synId regex but is in free text.\n const filter: ColumnSingleValueQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',\n columnName: key,\n operator: operator,\n // Add wildcards around the value\n values: [`%${removePrefixIfSynId(searchParams[key])}%`],\n }\n return filter\n }\n case ColumnMultiValueFunction.HAS_LIKE: {\n const filter: ColumnMultiValueFunctionQueryFilter = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.ColumnMultiValueFunctionQueryFilter',\n columnName: key,\n function: operator,\n _function: operator,\n values: searchParams[key].split(',').map(param => {\n // Remove synId prefix for the same reasons as in the LIKE case\n return `%${removePrefixIfSynId(param)}%`\n }),\n }\n return filter\n }\n }\n }),\n )\n }\n\n return additionalFilters.length === 0 ? undefined : additionalFilters\n}\n\n//parses synapse entity id from a sql query string\n//look for a pattern of 'from[some number of spaces]syn[somenumbers]` case insensitive\nexport const parseEntityIdFromSqlStatement = (sql: string): string => {\n const matches = sql.match(/(from)\\s+(syn)\\d+/gi)\n return matches && matches[0] ? matches[0].substr(5).trim() : ''\n}\n\nexport const parseEntityIdAndVersionFromSqlStatement = (\n sql: string,\n): { entityId?: string; versionNumber?: number } => {\n const regex = /from\\s+(syn\\d+)(?:\\.(\\d+))?/i\n const matches = regex.exec(sql)\n if (!matches) {\n return { entityId: undefined, versionNumber: undefined }\n }\n return {\n entityId: matches[1],\n versionNumber: matches[2] ? parseInt(matches[2]) : undefined,\n }\n}\n\nexport const resultToJson = (\n headerColumns: SelectColumn[],\n rowColumns: Row[],\n) => {\n const rows = rowColumns.map(row => row.values)\n const headers = headerColumns.map(column => column.name)\n return rows.map(row => {\n const obj: Record<string, string | null> = {}\n row.forEach((text, cellIndex) => {\n obj[headers[cellIndex]] = text\n })\n return obj\n })\n}\n\n/**\n * Finds the index of the given column name in the selectColumns or columnModels.\n *\n * @returns a non-negative integer if the column is found, or undefined if the column is not found\n * @param columnName\n * @param selectColumns\n * @param columnModels\n */\nexport const getColumnIndex = (\n columnName?: string,\n selectColumns?: SelectColumn[],\n columnModels?: ColumnModel[],\n): number | undefined => {\n if (!columnName) {\n return undefined\n }\n\n const lowerCaseColumnName = columnName.toLowerCase()\n\n if (selectColumns) {\n const findIndexResult = selectColumns.findIndex(\n el => el.name?.toLowerCase() === lowerCaseColumnName,\n )\n if (findIndexResult != -1) {\n return findIndexResult\n }\n }\n\n if (columnModels) {\n const findIndexResult = columnModels.findIndex(\n el => el.name?.toLowerCase() === lowerCaseColumnName,\n )\n if (findIndexResult !== -1) {\n return findIndexResult\n }\n }\n\n return undefined\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StringUtils.js","names":[],"sources":["../../../src/utils/functions/StringUtils.ts"],"sourcesContent":["import { parseSynId, SYNAPSE_ENTITY_ID_REGEX } from './RegularExpressions'\n\n/**\n * Truncates a string to be a specified length, then optionally append a suffix.\n */\nexport function truncateString(str: string, maxLength: number, suffix = '…') {\n if (str.length <= maxLength) {\n return str\n }\n return str.substring(0, maxLength) + suffix\n}\n\nexport function hex2ascii(inputString: string): string {\n const hex: string = inputString.toString()\n let str: string = ''\n for (let n = 0; n < hex.length; n += 2) {\n str += String.fromCharCode(parseInt(hex.substring(n, n + 2), 16))\n }\n return str\n}\n\n/**\n * Returns a hash code from a string. Uses Java's String.hashCode() algorithm: https://devdocs.io/openjdk~8/java/lang/string#hashCode--\n * @param {String} str The string to hash.\n * @return {Number} A 32bit integer\n * @see https://stackoverflow.com/a/8831937\n */\nexport function hashCode(str?: string | null) {\n if (str == null) {\n str = ''\n }\n let hash = 0\n for (let i = 0, len = str.length; i < len; i++) {\n const chr = str.charCodeAt(i)\n hash = (hash << 5) - hash + chr\n hash |= 0 // Convert to 32bit integer\n }\n return hash\n}\n\nexport function normalizeNumericId(id: string | number): number {\n if (id == null) {\n return NaN\n }\n if (typeof id === 'number') {\n return id\n }\n if (id.match(SYNAPSE_ENTITY_ID_REGEX)) {\n // parse the ID to remove a possible version suffix ('syn123.4' -> 'syn123')\n const entityId = parseSynId(id)!.targetId\n return parseInt(entityId.substring('syn'.length))\n } else {\n return parseInt(id)\n }\n}\n\n// Used for client-side validation. Note, the server has the final say. Integration tested in ChangePassword.integration.test.tsx\n// See https://github.com/Sage-Bionetworks/Synapse-Repository-Services/blob/develop/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/password/PasswordValidatorImpl.java#L47 for rules\nexport function validatePassword(newPassword: string) {\n if (newPassword.trim().length < 8) {\n return 'A valid password must be at least 8 characters long'\n }\n const hasLetter = /[a-zA-Z]/.test(newPassword) // Checks for at least one letter\n if (!hasLetter) {\n return 'A valid password must include letters'\n }\n\n const hasNumber = /\\d/.test(newPassword) // Checks for at least one number\n if (!hasNumber) {\n return 'A valid password must include digits (0-9)'\n }\n\n const hasSpecialChar = /[~!@#$%^&*_\\-+=`|\\\\(){}[\\]:;\"'<>,.?/]/.test(\n newPassword,\n ) // Checks for at least one special character\n if (!hasSpecialChar) {\n return 'A valid password must include special characters ~!@#$%^&*_-+=`|\\\\(){}[]:;\"\\'<>,.?/'\n }\n return undefined\n}\n\nexport function copyStringToClipboard(value: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // PORTALS-3571: setTimeout necessary on Safari\n setTimeout(() => {\n // use the Clipboard API\n // https://caniuse.com/mdn-api_clipboard_writetext\n navigator.clipboard.writeText(value).then(resolve).catch(reject)\n }, 0)\n })\n}\n\nexport function replaceFileExtension(\n filePath: string,\n extension: string,\n): string {\n const pathParts = filePath.split(/[\\\\/]/) // split on both '/' and '\\'\n const filename = pathParts[pathParts.length - 1] // last part of path\n const baseName = filename.replace(/\\.[^/.]+$/, '') // remove extension\n return `${baseName}.${extension}`\n}\n\nexport function stringListToArray(input: string): string[] {\n let inputArray: string[]\n\n if (Array.isArray(input)) {\n inputArray = input.map(String)\n } else {\n try {\n const parsed = JSON.parse(input ?? '[]')\n inputArray = Array.isArray(parsed) ? parsed.map(String) : [String(parsed)]\n } catch {\n inputArray = input ? [String(input)] : []\n }\n }\n return inputArray\n}\n\n// Split a value by a separator and remove leading/trailing spaces\nexport function splitAndTrim(value: string, sep: string = ','): string[] {\n return value?.split(sep).map(v => v.trim()) ?? []\n}\n"],"mappings":";;AAKA,SAAgB,EAAe,GAAa,GAAmB,IAAS,KAAK;AAI3E,QAHI,EAAI,UAAU,IACT,IAEF,EAAI,UAAU,GAAG,EAAU,GAAG;;AAGvC,SAAgB,EAAU,GAA6B;CACrD,IAAM,IAAc,EAAY,UAAU,EACtC,IAAc;AAClB,MAAK,IAAI,IAAI,GAAG,IAAI,EAAI,QAAQ,KAAK,EACnC,MAAO,OAAO,aAAa,SAAS,EAAI,UAAU,GAAG,IAAI,EAAE,EAAE,GAAG,CAAC;AAEnE,QAAO;;AAST,SAAgB,EAAS,GAAqB;AAC5C,CACE,MAAM;CAER,IAAI,IAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAM,EAAI,QAAQ,IAAI,GAAK,KAAK;EAC9C,IAAM,IAAM,EAAI,WAAW,EAAE;AAE7B,EADA,KAAQ,KAAQ,KAAK,IAAO,GAC5B,KAAQ;;AAEV,QAAO;;AAGT,SAAgB,EAAmB,GAA6B;AAC9D,KAAI,KAAM,KACR,QAAO;AAET,KAAI,OAAO,KAAO,SAChB,QAAO;AAET,KAAI,EAAG,MAAM,EAAwB,EAAE;EAErC,IAAM,IAAW,EAAW,EAAG,CAAE;AACjC,SAAO,SAAS,EAAS,UAAU,EAAa,CAAC;OAEjD,QAAO,SAAS,EAAG;;AAMvB,SAAgB,EAAiB,GAAqB;AACpD,KAAI,EAAY,MAAM,CAAC,SAAS,EAC9B,QAAO;AAGT,KAAI,CADc,WAAW,KAAK,
|
|
1
|
+
{"version":3,"file":"StringUtils.js","names":[],"sources":["../../../src/utils/functions/StringUtils.ts"],"sourcesContent":["import { parseSynId, SYNAPSE_ENTITY_ID_REGEX } from './RegularExpressions'\n\n/**\n * Truncates a string to be a specified length, then optionally append a suffix.\n */\nexport function truncateString(str: string, maxLength: number, suffix = '…') {\n if (str.length <= maxLength) {\n return str\n }\n return str.substring(0, maxLength) + suffix\n}\n\nexport function hex2ascii(inputString: string): string {\n const hex: string = inputString.toString()\n let str: string = ''\n for (let n = 0; n < hex.length; n += 2) {\n str += String.fromCharCode(parseInt(hex.substring(n, n + 2), 16))\n }\n return str\n}\n\n/**\n * Returns a hash code from a string. Uses Java's String.hashCode() algorithm: https://devdocs.io/openjdk~8/java/lang/string#hashCode--\n * @param {String} str The string to hash.\n * @return {Number} A 32bit integer\n * @see https://stackoverflow.com/a/8831937\n */\nexport function hashCode(str?: string | null) {\n if (str == null) {\n str = ''\n }\n let hash = 0\n for (let i = 0, len = str.length; i < len; i++) {\n const chr = str.charCodeAt(i)\n hash = (hash << 5) - hash + chr\n hash |= 0 // Convert to 32bit integer\n }\n return hash\n}\n\nexport function normalizeNumericId(id: string | number): number {\n if (id == null) {\n return NaN\n }\n if (typeof id === 'number') {\n return id\n }\n if (id.match(SYNAPSE_ENTITY_ID_REGEX)) {\n // parse the ID to remove a possible version suffix ('syn123.4' -> 'syn123')\n const entityId = parseSynId(id)!.targetId\n return parseInt(entityId.substring('syn'.length))\n } else {\n return parseInt(id)\n }\n}\n\n// Used for client-side validation. Note, the server has the final say. Integration tested in ChangePassword.integration.test.tsx\n// See https://github.com/Sage-Bionetworks/Synapse-Repository-Services/blob/develop/services/repository-managers/src/main/java/org/sagebionetworks/repo/manager/password/PasswordValidatorImpl.java#L47 for rules\nexport function validatePassword(newPassword: string) {\n if (newPassword.trim().length < 8) {\n return 'A valid password must be at least 8 characters long'\n }\n const hasLetter = /[a-zA-Z]/.test(newPassword) // Checks for at least one letter\n if (!hasLetter) {\n return 'A valid password must include letters'\n }\n\n const hasNumber = /\\d/.test(newPassword) // Checks for at least one number\n if (!hasNumber) {\n return 'A valid password must include digits (0-9)'\n }\n\n const hasSpecialChar = /[~!@#$%^&*_\\-+=`|\\\\(){}[\\]:;\"'<>,.?/]/.test(\n newPassword,\n ) // Checks for at least one special character\n if (!hasSpecialChar) {\n return 'A valid password must include special characters ~!@#$%^&*_-+=`|\\\\(){}[]:;\"\\'<>,.?/'\n }\n return undefined\n}\n\nexport function copyStringToClipboard(value: string): Promise<void> {\n return new Promise((resolve, reject) => {\n // PORTALS-3571: setTimeout necessary on Safari\n setTimeout(() => {\n // use the Clipboard API\n // https://caniuse.com/mdn-api_clipboard_writetext\n navigator.clipboard.writeText(value).then(resolve).catch(reject)\n }, 0)\n })\n}\n\nexport function replaceFileExtension(\n filePath: string,\n extension: string,\n): string {\n const pathParts = filePath.split(/[\\\\/]/) // split on both '/' and '\\'\n const filename = pathParts[pathParts.length - 1] // last part of path\n const baseName = filename.replace(/\\.[^/.]+$/, '') // remove extension\n return `${baseName}.${extension}`\n}\n\nexport function stringListToArray(input: string): string[] {\n let inputArray: string[]\n\n if (Array.isArray(input)) {\n inputArray = input.map(String)\n } else {\n try {\n const parsed = JSON.parse(input ?? '[]')\n inputArray = Array.isArray(parsed) ? parsed.map(String) : [String(parsed)]\n } catch {\n inputArray = input ? [String(input)] : []\n }\n }\n return inputArray\n}\n\n// Split a value by a separator and remove leading/trailing spaces\nexport function splitAndTrim(value: string, sep: string = ','): string[] {\n return value?.split(sep).map(v => v.trim()) ?? []\n}\n"],"mappings":";;AAKA,SAAgB,EAAe,GAAa,GAAmB,IAAS,KAAK;AAI3E,QAHI,EAAI,UAAU,IACT,IAEF,EAAI,UAAU,GAAG,EAAU,GAAG;;AAGvC,SAAgB,EAAU,GAA6B;CACrD,IAAM,IAAc,EAAY,UAAU,EACtC,IAAc;AAClB,MAAK,IAAI,IAAI,GAAG,IAAI,EAAI,QAAQ,KAAK,EACnC,MAAO,OAAO,aAAa,SAAS,EAAI,UAAU,GAAG,IAAI,EAAE,EAAE,GAAG,CAAC;AAEnE,QAAO;;AAST,SAAgB,EAAS,GAAqB;AAC5C,CACE,MAAM;CAER,IAAI,IAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAM,EAAI,QAAQ,IAAI,GAAK,KAAK;EAC9C,IAAM,IAAM,EAAI,WAAW,EAAE;AAE7B,EADA,KAAQ,KAAQ,KAAK,IAAO,GAC5B,KAAQ;;AAEV,QAAO;;AAGT,SAAgB,EAAmB,GAA6B;AAC9D,KAAI,KAAM,KACR,QAAO;AAET,KAAI,OAAO,KAAO,SAChB,QAAO;AAET,KAAI,EAAG,MAAM,EAAwB,EAAE;EAErC,IAAM,IAAW,EAAW,EAAG,CAAE;AACjC,SAAO,SAAS,EAAS,UAAU,EAAa,CAAC;OAEjD,QAAO,SAAS,EAAG;;AAMvB,SAAgB,EAAiB,GAAqB;AACpD,KAAI,EAAY,MAAM,CAAC,SAAS,EAC9B,QAAO;AAGT,KAAI,CADc,WAAW,KAAK,EAC7B,CACH,QAAO;AAIT,KAAI,CADc,KAAK,KAAK,EACvB,CACH,QAAO;AAMT,KAAI,CAHmB,wCAAwC,KAC7D,EAEG,CACH,QAAO;;AAKX,SAAgB,EAAsB,GAA8B;AAClE,QAAO,IAAI,SAAS,GAAS,MAAW;AAEtC,mBAAiB;AAGf,aAAU,UAAU,UAAU,EAAM,CAAC,KAAK,EAAQ,CAAC,MAAM,EAAO;KAC/D,EAAE;GACL;;AAGJ,SAAgB,EACd,GACA,GACQ;CACR,IAAM,IAAY,EAAS,MAAM,QAAQ;AAGzC,QAAO,GAFU,EAAU,EAAU,SAAS,GACpB,QAAQ,aAAa,GACrC,CAAS,GAAG;;AAGxB,SAAgB,EAAkB,GAAyB;CACzD,IAAI;AAEJ,KAAI,MAAM,QAAQ,EAAM,CACtB,KAAa,EAAM,IAAI,OAAO;KAE9B,KAAI;EACF,IAAM,IAAS,KAAK,MAAM,KAAS,KAAK;AACxC,MAAa,MAAM,QAAQ,EAAO,GAAG,EAAO,IAAI,OAAO,GAAG,CAAC,OAAO,EAAO,CAAC;SACpE;AACN,MAAa,IAAQ,CAAC,OAAO,EAAM,CAAC,GAAG,EAAE;;AAG7C,QAAO;;AAIT,SAAgB,EAAa,GAAe,IAAc,KAAe;AACvE,QAAO,GAAO,MAAM,EAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,IAAI,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deepLinkingUtils.js","names":[],"sources":["../../../src/utils/functions/deepLinkingUtils.ts"],"sourcesContent":["import { Query, QueryBundleRequest } from '@sage-bionetworks/synapse-types'\nimport { isEqual } from 'lodash-es'\nimport { parseEntityIdFromSqlStatement } from './SqlFunctions'\n\n/**\n * Compresses a string using the browser's CompressionStream API and returns base64.\n * @param str - The string to compress\n * @returns Base64-encoded compressed string\n */\nasync function compressString(str: string): Promise<string> {\n const encoder = new TextEncoder()\n const data = encoder.encode(str)\n\n const compressionStream = new CompressionStream('gzip')\n const writer = compressionStream.writable.getWriter()\n\n // Start consuming from the readable side immediately to prevent backpressure deadlock\n const readPromise = new Response(compressionStream.readable).arrayBuffer()\n\n await writer.write(data)\n await writer.close()\n\n const compressedArrayBuffer = await readPromise\n\n // Convert to base64\n const bytes = new Uint8Array(compressedArrayBuffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i])\n }\n return btoa(binary)\n}\n\n/**\n * Decompresses a base64-encoded gzip string using the browser's DecompressionStream API.\n * @param base64Str - The base64-encoded compressed string\n * @returns The decompressed string\n */\nasync function decompressString(base64Str: string): Promise<string> {\n // Convert from base64 to Uint8Array\n const binary = atob(base64Str)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n\n const decompressionStream = new DecompressionStream('gzip')\n const writer = decompressionStream.writable.getWriter()\n\n // Start consuming from the readable side immediately to prevent backpressure deadlock\n const readPromise = new Response(decompressionStream.readable).arrayBuffer()\n\n await writer.write(bytes)\n await writer.close()\n\n const decompressedArrayBuffer = await readPromise\n\n const decoder = new TextDecoder()\n return decoder.decode(decompressedArrayBuffer)\n}\n\n//id consists of a component class/function name and it's index\nfunction getComponentSearchHashId(\n componentName: string,\n componentIndex: number,\n): string {\n return `${componentName}${componentIndex}`\n}\n\n/**\n * Computes the difference between the current query and the initial query.\n * Returns only the fields that differ from the initial query.\n * Dynamically handles all fields in the Query type, making it safe for future changes.\n * @param currentQuery - The current query state\n * @param initQuery - The initial query state\n * @returns A partial Query object containing only the differences, or null if queries are equal\n */\nfunction computeQueryDiff(\n currentQuery: Query,\n initQuery: Query,\n): Partial<Query> | null {\n if (isEqual(currentQuery, initQuery)) {\n return null\n }\n\n const diff: Partial<Query> = {}\n\n // Get the union of keys from both currentQuery and initQuery to handle\n // both additions/changes and removals/omissions\n const allKeys = new Set([\n ...Object.keys(currentQuery),\n ...Object.keys(initQuery),\n ]) as Set<keyof Query>\n\n for (const key of allKeys) {\n if (!isEqual(currentQuery[key], initQuery[key])) {\n // Type assertion needed for dynamic key assignment\n // Include currentQuery[key] even if undefined to represent removals\n ;(diff as any)[key] = currentQuery[key]\n }\n }\n\n return Object.keys(diff).length > 0 ? diff : null\n}\n\n/**\n * Applies a query diff to the initial query to reconstruct the current query.\n * @param initQuery - The initial query state\n * @param diff - The partial query containing only the differences\n * @returns The reconstructed query\n */\nfunction applyQueryDiff(initQuery: Query, diff: Partial<Query>): Query {\n return {\n ...initQuery,\n ...diff,\n }\n}\n\n/**\n * Updates the url with the components' new search params. Stores only the\n * differences between currentQuery and initQuery, compressed with gzip and base64-encoded.\n * If the queries are equal, the search param will be removed.\n * @param componentName\n * @param componentIndex\n * @param currentQuery - The current query state, or null to remove the param\n * @param initQuery - The initial query state to compute diff against\n */\nexport async function updateUrlWithNewSearchParam(\n componentName: string,\n componentIndex: number | undefined,\n currentQuery: Query | null,\n initQuery: Query | null,\n): Promise<void> {\n const componentSearchHashId =\n componentIndex !== undefined\n ? getComponentSearchHashId(componentName, componentIndex)\n : componentName\n\n const currentSearch = new URLSearchParams(window.location.search)\n\n if (currentQuery && initQuery) {\n const diff = computeQueryDiff(currentQuery, initQuery)\n if (diff) {\n // Compress the diff and encode as base64\n const jsonString = JSON.stringify(diff)\n const compressed = await compressString(jsonString)\n currentSearch.set(componentSearchHashId, compressed)\n } else {\n // Queries are equal, remove the param\n currentSearch.delete(componentSearchHashId)\n }\n } else {\n // No current query or no init query, remove the param\n currentSearch.delete(componentSearchHashId)\n }\n\n const searchString = currentSearch.toString()\n window.history.replaceState(\n window.history.state,\n '',\n window.location.pathname +\n (Array.from(currentSearch).length > 0\n ? `?${searchString.toString()}`\n : ''),\n )\n}\n\n/**\n * Retrieves a query request from the URL by applying the stored diff to the initial query.\n * @param componentIndex\n * @param initQuery - The initial query to apply the diff to\n * @returns A partial QueryBundleRequest with the reconstructed query, or undefined if no diff is stored\n */\nexport async function getQueryRequestFromLink(\n componentIndex: number,\n initQuery: Query,\n): Promise<Partial<QueryBundleRequest> | undefined> {\n const paramKey = getComponentSearchHashId('qw', componentIndex)\n const searchParamValue = new URLSearchParams(window.location.search).get(\n paramKey,\n )\n\n let queryRequest: Partial<QueryBundleRequest> | undefined = undefined\n if (searchParamValue) {\n try {\n // Decompress and parse the diff from the URL\n const jsonString = await decompressString(searchParamValue)\n const diff = JSON.parse(jsonString) as Partial<Query>\n // Apply the diff to the init query to reconstruct the current query\n const query = applyQueryDiff(initQuery, diff)\n if (query.sql) {\n queryRequest = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.QueryBundleRequest',\n entityId: parseEntityIdFromSqlStatement(query.sql),\n query,\n }\n }\n } catch (error) {\n console.error('Failed to parse query diff from URL:', error)\n }\n }\n\n // check for legacy search param for backward compatibility, but prioritize the new format if both are present\n const legacyParamKey = getComponentSearchHashId(\n 'QueryWrapper',\n componentIndex,\n )\n const legacySearchParamValue = new URLSearchParams(\n window.location.search,\n ).get(legacyParamKey)\n if (legacySearchParamValue && !queryRequest) {\n try {\n const query = JSON.parse(legacySearchParamValue) as Query\n if (query.sql) {\n queryRequest = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.QueryBundleRequest',\n entityId: parseEntityIdFromSqlStatement(query.sql),\n query,\n }\n }\n } catch (error) {\n console.error('Failed to parse legacy query from URL:', error)\n }\n }\n return queryRequest\n}\n\n/**\n * Generates a URL with a compressed query diff parameter.\n * @param path - The base path for the URL\n * @param componentIndex - The component index (e.g., 0)\n * @param currentQuery - The query to encode\n * @param initQuery - The initial/default query to compute the diff against\n * @returns A Promise that resolves to the full URL with compressed query parameter\n */\nexport async function generateCompressedQueryURL(\n path: string,\n componentIndex: number,\n currentQuery: Query,\n initQuery: Query,\n): Promise<string> {\n const diff = computeQueryDiff(currentQuery, initQuery)\n if (!diff) {\n // Queries are equal, no need for a parameter\n return path\n }\n\n const jsonString = JSON.stringify(diff)\n const compressed = await compressString(jsonString)\n const paramKey = getComponentSearchHashId('qw', componentIndex)\n\n return `${path}?${paramKey}=${encodeURIComponent(compressed)}`\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"deepLinkingUtils.js","names":[],"sources":["../../../src/utils/functions/deepLinkingUtils.ts"],"sourcesContent":["import { Query, QueryBundleRequest } from '@sage-bionetworks/synapse-types'\nimport { isEqual } from 'lodash-es'\nimport { parseEntityIdFromSqlStatement } from './SqlFunctions'\n\n/**\n * Compresses a string using the browser's CompressionStream API and returns base64.\n * @param str - The string to compress\n * @returns Base64-encoded compressed string\n */\nasync function compressString(str: string): Promise<string> {\n const encoder = new TextEncoder()\n const data = encoder.encode(str)\n\n const compressionStream = new CompressionStream('gzip')\n const writer = compressionStream.writable.getWriter()\n\n // Start consuming from the readable side immediately to prevent backpressure deadlock\n const readPromise = new Response(compressionStream.readable).arrayBuffer()\n\n await writer.write(data)\n await writer.close()\n\n const compressedArrayBuffer = await readPromise\n\n // Convert to base64\n const bytes = new Uint8Array(compressedArrayBuffer)\n let binary = ''\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i])\n }\n return btoa(binary)\n}\n\n/**\n * Decompresses a base64-encoded gzip string using the browser's DecompressionStream API.\n * @param base64Str - The base64-encoded compressed string\n * @returns The decompressed string\n */\nasync function decompressString(base64Str: string): Promise<string> {\n // Convert from base64 to Uint8Array\n const binary = atob(base64Str)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n\n const decompressionStream = new DecompressionStream('gzip')\n const writer = decompressionStream.writable.getWriter()\n\n // Start consuming from the readable side immediately to prevent backpressure deadlock\n const readPromise = new Response(decompressionStream.readable).arrayBuffer()\n\n await writer.write(bytes)\n await writer.close()\n\n const decompressedArrayBuffer = await readPromise\n\n const decoder = new TextDecoder()\n return decoder.decode(decompressedArrayBuffer)\n}\n\n//id consists of a component class/function name and it's index\nfunction getComponentSearchHashId(\n componentName: string,\n componentIndex: number,\n): string {\n return `${componentName}${componentIndex}`\n}\n\n/**\n * Computes the difference between the current query and the initial query.\n * Returns only the fields that differ from the initial query.\n * Dynamically handles all fields in the Query type, making it safe for future changes.\n * @param currentQuery - The current query state\n * @param initQuery - The initial query state\n * @returns A partial Query object containing only the differences, or null if queries are equal\n */\nfunction computeQueryDiff(\n currentQuery: Query,\n initQuery: Query,\n): Partial<Query> | null {\n if (isEqual(currentQuery, initQuery)) {\n return null\n }\n\n const diff: Partial<Query> = {}\n\n // Get the union of keys from both currentQuery and initQuery to handle\n // both additions/changes and removals/omissions\n const allKeys = new Set([\n ...Object.keys(currentQuery),\n ...Object.keys(initQuery),\n ]) as Set<keyof Query>\n\n for (const key of allKeys) {\n if (!isEqual(currentQuery[key], initQuery[key])) {\n // Type assertion needed for dynamic key assignment\n // Include currentQuery[key] even if undefined to represent removals\n ;(diff as any)[key] = currentQuery[key]\n }\n }\n\n return Object.keys(diff).length > 0 ? diff : null\n}\n\n/**\n * Applies a query diff to the initial query to reconstruct the current query.\n * @param initQuery - The initial query state\n * @param diff - The partial query containing only the differences\n * @returns The reconstructed query\n */\nfunction applyQueryDiff(initQuery: Query, diff: Partial<Query>): Query {\n return {\n ...initQuery,\n ...diff,\n }\n}\n\n/**\n * Updates the url with the components' new search params. Stores only the\n * differences between currentQuery and initQuery, compressed with gzip and base64-encoded.\n * If the queries are equal, the search param will be removed.\n * @param componentName\n * @param componentIndex\n * @param currentQuery - The current query state, or null to remove the param\n * @param initQuery - The initial query state to compute diff against\n */\nexport async function updateUrlWithNewSearchParam(\n componentName: string,\n componentIndex: number | undefined,\n currentQuery: Query | null,\n initQuery: Query | null,\n): Promise<void> {\n const componentSearchHashId =\n componentIndex !== undefined\n ? getComponentSearchHashId(componentName, componentIndex)\n : componentName\n\n const currentSearch = new URLSearchParams(window.location.search)\n\n if (currentQuery && initQuery) {\n const diff = computeQueryDiff(currentQuery, initQuery)\n if (diff) {\n // Compress the diff and encode as base64\n const jsonString = JSON.stringify(diff)\n const compressed = await compressString(jsonString)\n currentSearch.set(componentSearchHashId, compressed)\n } else {\n // Queries are equal, remove the param\n currentSearch.delete(componentSearchHashId)\n }\n } else {\n // No current query or no init query, remove the param\n currentSearch.delete(componentSearchHashId)\n }\n\n const searchString = currentSearch.toString()\n window.history.replaceState(\n window.history.state,\n '',\n window.location.pathname +\n (Array.from(currentSearch).length > 0\n ? `?${searchString.toString()}`\n : ''),\n )\n}\n\n/**\n * Retrieves a query request from the URL by applying the stored diff to the initial query.\n * @param componentIndex\n * @param initQuery - The initial query to apply the diff to\n * @returns A partial QueryBundleRequest with the reconstructed query, or undefined if no diff is stored\n */\nexport async function getQueryRequestFromLink(\n componentIndex: number,\n initQuery: Query,\n): Promise<Partial<QueryBundleRequest> | undefined> {\n const paramKey = getComponentSearchHashId('qw', componentIndex)\n const searchParamValue = new URLSearchParams(window.location.search).get(\n paramKey,\n )\n\n let queryRequest: Partial<QueryBundleRequest> | undefined = undefined\n if (searchParamValue) {\n try {\n // Decompress and parse the diff from the URL\n const jsonString = await decompressString(searchParamValue)\n const diff = JSON.parse(jsonString) as Partial<Query>\n // Apply the diff to the init query to reconstruct the current query\n const query = applyQueryDiff(initQuery, diff)\n if (query.sql) {\n queryRequest = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.QueryBundleRequest',\n entityId: parseEntityIdFromSqlStatement(query.sql),\n query,\n }\n }\n } catch (error) {\n console.error('Failed to parse query diff from URL:', error)\n }\n }\n\n // check for legacy search param for backward compatibility, but prioritize the new format if both are present\n const legacyParamKey = getComponentSearchHashId(\n 'QueryWrapper',\n componentIndex,\n )\n const legacySearchParamValue = new URLSearchParams(\n window.location.search,\n ).get(legacyParamKey)\n if (legacySearchParamValue && !queryRequest) {\n try {\n const query = JSON.parse(legacySearchParamValue) as Query\n if (query.sql) {\n queryRequest = {\n concreteType:\n 'org.sagebionetworks.repo.model.table.QueryBundleRequest',\n entityId: parseEntityIdFromSqlStatement(query.sql),\n query,\n }\n }\n } catch (error) {\n console.error('Failed to parse legacy query from URL:', error)\n }\n }\n return queryRequest\n}\n\n/**\n * Generates a URL with a compressed query diff parameter.\n * @param path - The base path for the URL\n * @param componentIndex - The component index (e.g., 0)\n * @param currentQuery - The query to encode\n * @param initQuery - The initial/default query to compute the diff against\n * @returns A Promise that resolves to the full URL with compressed query parameter\n */\nexport async function generateCompressedQueryURL(\n path: string,\n componentIndex: number,\n currentQuery: Query,\n initQuery: Query,\n): Promise<string> {\n const diff = computeQueryDiff(currentQuery, initQuery)\n if (!diff) {\n // Queries are equal, no need for a parameter\n return path\n }\n\n const jsonString = JSON.stringify(diff)\n const compressed = await compressString(jsonString)\n const paramKey = getComponentSearchHashId('qw', componentIndex)\n\n return `${path}?${paramKey}=${encodeURIComponent(compressed)}`\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getDataFromFromStorage.js","names":[],"sources":["../../../src/utils/functions/getDataFromFromStorage.ts"],"sourcesContent":["import {\n ColumnType,\n ColumnTypeEnum,\n EntityHeader,\n Evaluation,\n UserProfile,\n} from '@sage-bionetworks/synapse-types'\nimport { SynapseConstants } from '../index'\n\nconst getStoredEntityHeaders = (): EntityHeader[] => {\n try {\n const lookUpEntityHeaders: EntityHeader[] = JSON.parse(\n sessionStorage.getItem(SynapseConstants.ENTITY_HEADER_STORAGE_KEY) || '',\n )\n return lookUpEntityHeaders\n } catch (e) {\n return []\n }\n}\n\nconst getStoredUserProfiles = (): UserProfile[] => {\n try {\n const lookUpUserIds: UserProfile[] = JSON.parse(\n sessionStorage.getItem(SynapseConstants.USER_PROFILE_STORAGE_KEY) || '',\n )\n return lookUpUserIds\n } catch (e) {\n return []\n }\n}\n\nconst getStoredEvaluation = (): Evaluation[] => {\n try {\n const lookUpEvaluations: UserProfile[] = JSON.parse(\n sessionStorage.getItem(SynapseConstants.EVALUATIONS_STORAGE_KEY) || '',\n )\n return lookUpEvaluations\n } catch (e) {\n return []\n }\n}\n\nconst getDisplayValueForEntityColumn = (value: string): string => {\n const entity = getStoredEntityHeaders().find(item => item.id === value)\n return entity?.name ?? value\n}\n\nconst getDisplayValueEvaluationIdColumn = (facetValue: string): string => {\n const evaluation = getStoredEvaluation().find(item => item.id === facetValue)\n return evaluation?.name || facetValue\n}\n\nconst getDisplayValueUserIdColumn = (facetValue: string): string => {\n const userProfile = getStoredUserProfiles().find(\n item => item.ownerId === facetValue,\n )\n return userProfile?.userName || facetValue\n}\n\nexport const getDisplayValue = (value: string, columnType: ColumnType) => {\n if (value === SynapseConstants.VALUE_NOT_SET) {\n return SynapseConstants.FRIENDLY_VALUE_NOT_SET\n }\n\n switch (columnType) {\n case ColumnTypeEnum.ENTITYID:\n case ColumnTypeEnum.ENTITYID_LIST:\n return getDisplayValueForEntityColumn(value)\n case ColumnTypeEnum.USERID:\n case ColumnTypeEnum.USERID_LIST:\n return getDisplayValueUserIdColumn(value)\n case ColumnTypeEnum.EVALUATIONID:\n return getDisplayValueEvaluationIdColumn(value)\n default:\n return value\n }\n}\n"],"mappings":";;;;AASA,IAAM,UAA+C;AACnD,KAAI;AAIF,SAH4C,KAAK,MAC/C,eAAe,QAAQ,8BAA2C,IAAI,
|
|
1
|
+
{"version":3,"file":"getDataFromFromStorage.js","names":[],"sources":["../../../src/utils/functions/getDataFromFromStorage.ts"],"sourcesContent":["import {\n ColumnType,\n ColumnTypeEnum,\n EntityHeader,\n Evaluation,\n UserProfile,\n} from '@sage-bionetworks/synapse-types'\nimport { SynapseConstants } from '../index'\n\nconst getStoredEntityHeaders = (): EntityHeader[] => {\n try {\n const lookUpEntityHeaders: EntityHeader[] = JSON.parse(\n sessionStorage.getItem(SynapseConstants.ENTITY_HEADER_STORAGE_KEY) || '',\n )\n return lookUpEntityHeaders\n } catch (e) {\n return []\n }\n}\n\nconst getStoredUserProfiles = (): UserProfile[] => {\n try {\n const lookUpUserIds: UserProfile[] = JSON.parse(\n sessionStorage.getItem(SynapseConstants.USER_PROFILE_STORAGE_KEY) || '',\n )\n return lookUpUserIds\n } catch (e) {\n return []\n }\n}\n\nconst getStoredEvaluation = (): Evaluation[] => {\n try {\n const lookUpEvaluations: UserProfile[] = JSON.parse(\n sessionStorage.getItem(SynapseConstants.EVALUATIONS_STORAGE_KEY) || '',\n )\n return lookUpEvaluations\n } catch (e) {\n return []\n }\n}\n\nconst getDisplayValueForEntityColumn = (value: string): string => {\n const entity = getStoredEntityHeaders().find(item => item.id === value)\n return entity?.name ?? value\n}\n\nconst getDisplayValueEvaluationIdColumn = (facetValue: string): string => {\n const evaluation = getStoredEvaluation().find(item => item.id === facetValue)\n return evaluation?.name || facetValue\n}\n\nconst getDisplayValueUserIdColumn = (facetValue: string): string => {\n const userProfile = getStoredUserProfiles().find(\n item => item.ownerId === facetValue,\n )\n return userProfile?.userName || facetValue\n}\n\nexport const getDisplayValue = (value: string, columnType: ColumnType) => {\n if (value === SynapseConstants.VALUE_NOT_SET) {\n return SynapseConstants.FRIENDLY_VALUE_NOT_SET\n }\n\n switch (columnType) {\n case ColumnTypeEnum.ENTITYID:\n case ColumnTypeEnum.ENTITYID_LIST:\n return getDisplayValueForEntityColumn(value)\n case ColumnTypeEnum.USERID:\n case ColumnTypeEnum.USERID_LIST:\n return getDisplayValueUserIdColumn(value)\n case ColumnTypeEnum.EVALUATIONID:\n return getDisplayValueEvaluationIdColumn(value)\n default:\n return value\n }\n}\n"],"mappings":";;;;AASA,IAAM,UAA+C;AACnD,KAAI;AAIF,SAH4C,KAAK,MAC/C,eAAe,QAAQ,8BAA2C,IAAI,GAEjE;SACG;AACV,SAAO,EAAE;;GAIP,UAA6C;AACjD,KAAI;AAIF,SAHqC,KAAK,MACxC,eAAe,QAAQ,6BAA0C,IAAI,GAEhE;SACG;AACV,SAAO,EAAE;;GAIP,UAA0C;AAC9C,KAAI;AAIF,SAHyC,KAAK,MAC5C,eAAe,QAAQ,iCAAyC,IAAI,GAE/D;SACG;AACV,SAAO,EAAE;;GAIP,KAAkC,MACvB,GAAwB,CAAC,MAAK,MAAQ,EAAK,OAAO,EAC1D,EAAQ,QAAQ,GAGnB,KAAqC,MACtB,GAAqB,CAAC,MAAK,MAAQ,EAAK,OAAO,EAC3D,EAAY,QAAQ,GAGvB,KAA+B,MACf,GAAuB,CAAC,MAC1C,MAAQ,EAAK,YAAY,EAEpB,EAAa,YAAY,GAGrB,KAAmB,GAAe,MAA2B;AACxE,KAAI,MAAU,4CACZ,QAAO;AAGT,SAAQ,GAAR;EACE,KAAK,EAAe;EACpB,KAAK,EAAe,cAClB,QAAO,EAA+B,EAAM;EAC9C,KAAK,EAAe;EACpB,KAAK,EAAe,YAClB,QAAO,EAA4B,EAAM;EAC3C,KAAK,EAAe,aAClB,QAAO,EAAkC,EAAM;EACjD,QACE,QAAO"}
|