Skip to content

Commit e3551dc

Browse files
Added orgHelper and url safe version of org's name
orgHelper provides a nicer abstraction on top of organizations. We also added a url safe version of the org name for path checking.
1 parent fd36b13 commit e3551dc

File tree

7 files changed

+182
-3
lines changed

7 files changed

+182
-3
lines changed

package-lock.json

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "git",
66
"url": "https://github.com/PropelAuth/javascript"
77
},
8-
"version": "1.2.1",
8+
"version": "1.2.2",
99
"keywords": [
1010
"auth",
1111
"user",
@@ -29,7 +29,8 @@
2929
"prettier-plugin-organize-imports": "^2.3.3",
3030
"rollup": "^2.46.0",
3131
"rollup-plugin-terser": "^7.0.2",
32-
"typescript": "^4.2.4"
32+
"typescript": "^4.2.4",
33+
"uuid": "^8.3.2"
3334
},
3435
"browserslist": [
3536
"> 0.2%",

src/OrgHelper.test.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
import { v4 as uuidv4 } from "uuid"
5+
import { getOrgHelper, ORG_SELECTION_LOCAL_STORAGE_KEY } from "./OrgHelper"
6+
7+
beforeEach(() => {
8+
const localStorageMock = (function () {
9+
let store = {}
10+
return {
11+
getItem: function (key) {
12+
return store[key]
13+
},
14+
setItem: function (key, value) {
15+
store[key] = value.toString()
16+
},
17+
clear: function () {
18+
store = {}
19+
},
20+
removeItem: function (key) {
21+
delete store[key]
22+
},
23+
}
24+
})()
25+
Object.defineProperty(window, "localStorage", { value: localStorageMock })
26+
})
27+
28+
afterEach(() => {
29+
localStorage.clear()
30+
})
31+
32+
it("getter methods work", async () => {
33+
const orgs = createOrgs(10)
34+
const orgIdToOrgMemberInfo = createOrgIdToOrgMemberInfo(orgs)
35+
const orgHelper = getOrgHelper(orgIdToOrgMemberInfo)
36+
37+
// Positive cases
38+
for (let org of orgs) {
39+
expect(orgHelper.getOrg(org.orgId)).toStrictEqual(org)
40+
expect(orgHelper.getOrgByName(org.orgName)).toStrictEqual(org)
41+
expect(orgHelper.getOrgByName(org.urlSafeOrgName)).toStrictEqual(org)
42+
}
43+
expect(orgHelper.getOrgs().sort()).toEqual(orgs.sort())
44+
expect(orgHelper.getOrgIds().sort()).toEqual(orgs.map((org) => org.orgId).sort())
45+
46+
// Negative cases
47+
const inheritedProperties = getAllProperties({})
48+
for (let notOrg of inheritedProperties) {
49+
expect(orgHelper.getOrg(notOrg)).toBeFalsy()
50+
expect(orgHelper.getOrgByName(notOrg)).toBeFalsy()
51+
}
52+
for (let i = 0; i < 100; i++) {
53+
expect(orgHelper.getOrg(uuidv4())).toBeFalsy()
54+
expect(orgHelper.getOrgByName(uuidv4())).toBeFalsy()
55+
}
56+
})
57+
58+
function createOrgIdToOrgMemberInfo(orgs) {
59+
let orgIdToOrgMemberInfo = {}
60+
for (let org of orgs) {
61+
orgIdToOrgMemberInfo[org.orgId] = org
62+
}
63+
return orgIdToOrgMemberInfo
64+
}
65+
66+
function createOrgs(numOrgs) {
67+
let orgs = []
68+
for (let i = 0; i < numOrgs; i++) {
69+
orgs.push(createOrg())
70+
}
71+
return orgs
72+
}
73+
74+
function createOrg() {
75+
const orgName = randomString()
76+
const urlSafeOrgName = orgName.toLowerCase()
77+
return {
78+
orgId: uuidv4(),
79+
orgName,
80+
urlSafeOrgName,
81+
userRole: choose(["Owner", "Admin", "Member"]),
82+
}
83+
}
84+
85+
function randomString() {
86+
return (Math.random() + 1).toString(36).substring(3)
87+
}
88+
89+
function choose(choices) {
90+
const index = Math.floor(Math.random() * choices.length)
91+
return choices[index]
92+
}
93+
94+
// https://stackoverflow.com/questions/8024149/is-it-possible-to-get-the-non-enumerable-inherited-property-names-of-an-object
95+
function getAllProperties(obj) {
96+
let allProps = [],
97+
curr = obj
98+
do {
99+
const props = Object.getOwnPropertyNames(curr)
100+
props.forEach(function (prop) {
101+
if (allProps.indexOf(prop) === -1) {
102+
allProps.push(prop)
103+
}
104+
})
105+
} while ((curr = Object.getPrototypeOf(curr)))
106+
return allProps
107+
}

src/OrgHelper.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {OrgIdToOrgMemberInfo, OrgMemberInfo} from "./org";
2+
3+
export type OrgHelper = {
4+
getOrgs: () => OrgMemberInfo[]
5+
getOrgIds: () => string[]
6+
getOrg: (orgId: string) => OrgMemberInfo | undefined
7+
getOrgByName: (orgName: string) => OrgMemberInfo | undefined
8+
}
9+
10+
export function getOrgHelper(
11+
orgIdToOrgMemberInfo: OrgIdToOrgMemberInfo,
12+
): OrgHelper {
13+
return {
14+
getOrg(orgId: string): OrgMemberInfo | undefined {
15+
if (orgIdToOrgMemberInfo.hasOwnProperty(orgId)) {
16+
return orgIdToOrgMemberInfo[orgId]
17+
} else {
18+
return undefined
19+
}
20+
},
21+
getOrgIds(): string[] {
22+
return Object.keys(orgIdToOrgMemberInfo)
23+
},
24+
getOrgs(): OrgMemberInfo[] {
25+
return Object.values(orgIdToOrgMemberInfo)
26+
},
27+
getOrgByName(orgName: string): OrgMemberInfo | undefined {
28+
for (const orgMemberInfo of Object.values(orgIdToOrgMemberInfo)) {
29+
if (orgMemberInfo.orgName === orgName || orgMemberInfo.urlSafeOrgName === orgName) {
30+
return orgMemberInfo
31+
}
32+
}
33+
return undefined
34+
},
35+
}
36+
}

src/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { OrgIdToOrgMemberInfo, UserRole } from "./org"
2+
import {getOrgHelper, OrgHelper} from "./OrgHelper";
23

34
export type User = {
45
userId: string
@@ -19,6 +20,12 @@ export type User = {
1920
export type AuthenticationInfo = {
2021
accessToken: string
2122
expiresAtSeconds: number
23+
orgHelper: OrgHelper,
24+
25+
/**
26+
* You should prefer orgHelper to orgIdToOrgMemberInfo.
27+
* orgHelper provides useful abstractions over this mapping
28+
*/
2229
orgIdToOrgMemberInfo?: OrgIdToOrgMemberInfo
2330
user: User
2431
}
@@ -121,6 +128,8 @@ export function parseJsonConvertingSnakeToCamel(str: string): AuthenticationInfo
121128
this.orgId = value
122129
} else if (key === "org_name") {
123130
this.orgName = value
131+
} else if (key === "url_safe_org_name") {
132+
this.urlSafeOrgName = value
124133
} else if (key === "user_role") {
125134
this.userRole = toUserRole(value)
126135
} else if (key === "access_token") {
@@ -129,6 +138,7 @@ export function parseJsonConvertingSnakeToCamel(str: string): AuthenticationInfo
129138
this.expiresAtSeconds = value
130139
} else if (key === "org_id_to_org_member_info") {
131140
this.orgIdToOrgMemberInfo = value
141+
this.orgHelper = getOrgHelper(value)
132142
} else if (key === "user_id") {
133143
this.userId = value
134144
} else if (key === "email_confirmed") {

src/index.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,33 +113,39 @@ test("client parses org information correctly", async () => {
113113
"922c5c21-be96-484f-9383-ee532dd79d02": {
114114
org_id: "922c5c21-be96-484f-9383-ee532dd79d02",
115115
org_name: "ninetwotwo",
116+
url_safe_org_name: "ninetwotwo",
116117
user_role: "Owner",
117118
},
118119
"fcdb21f0-b1b6-426f-b83c-6cf4b903d737": {
119120
org_id: "fcdb21f0-b1b6-426f-b83c-6cf4b903d737",
120121
org_name: "effcdee",
122+
url_safe_org_name: "effcdee",
121123
user_role: "Admin",
122124
},
123125
"da5903d3-5696-4e4b-920b-bc429b2f75ab": {
124126
org_id: "da5903d3-5696-4e4b-920b-bc429b2f75ab",
125127
org_name: "deeafive",
128+
url_safe_org_name: "deeafive",
126129
user_role: "Member",
127130
},
128131
}
129132
const typeScriptOrgIdToOrgMemberInfo: OrgIdToOrgMemberInfo = {
130133
"922c5c21-be96-484f-9383-ee532dd79d02": {
131134
orgId: "922c5c21-be96-484f-9383-ee532dd79d02",
132135
orgName: "ninetwotwo",
136+
urlSafeOrgName: "ninetwotwo",
133137
userRole: UserRole.Owner,
134138
},
135139
"fcdb21f0-b1b6-426f-b83c-6cf4b903d737": {
136140
orgId: "fcdb21f0-b1b6-426f-b83c-6cf4b903d737",
137141
orgName: "effcdee",
142+
urlSafeOrgName: "effcdee",
138143
userRole: UserRole.Admin,
139144
},
140145
"da5903d3-5696-4e4b-920b-bc429b2f75ab": {
141146
orgId: "da5903d3-5696-4e4b-920b-bc429b2f75ab",
142147
orgName: "deeafive",
148+
urlSafeOrgName: "deeafive",
143149
userRole: UserRole.Member,
144150
}
145151
}
@@ -149,6 +155,7 @@ test("client parses org information correctly", async () => {
149155

150156
const authenticationInfo = await client.getAuthenticationInfoOrNull()
151157
expect(authenticationInfo?.orgIdToOrgMemberInfo).toStrictEqual(typeScriptOrgIdToOrgMemberInfo)
158+
expect(authenticationInfo?.orgHelper.getOrgs()).toStrictEqual(Object.values(typeScriptOrgIdToOrgMemberInfo))
152159
expectCorrectEndpointWasHit(mockHttp, "https://www.example.com/api/v1/refresh_token")
153160
})
154161

@@ -449,6 +456,7 @@ interface ApiUser {
449456
export type ApiOrgMemberInfo = {
450457
org_id: string
451458
org_name: string
459+
url_safe_org_name: string
452460
user_role: string
453461
}
454462
export type ApiOrgIdToOrgMemberInfo = {

src/org.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export type OrgMemberInfo = {
22
orgId: string
33
orgName: string
4+
urlSafeOrgName: string
45
userRole: UserRole
56
}
67
export type OrgIdToOrgMemberInfo = {

0 commit comments

Comments
 (0)